chore: change max share level on existing port shares (#12411)

This commit is contained in:
Garrett Delfosse 2024-03-05 13:47:01 -05:00 committed by GitHub
parent 5106d9fc47
commit 61bd341a36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 246 additions and 1 deletions

View File

@ -915,6 +915,19 @@ func (q *querier) DeleteWorkspaceAgentPortShare(ctx context.Context, arg databas
return q.db.DeleteWorkspaceAgentPortShare(ctx, arg)
}
func (q *querier) DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Context, templateID uuid.UUID) error {
template, err := q.db.GetTemplateByID(ctx, templateID)
if err != nil {
return err
}
if err := q.authorizeContext(ctx, rbac.ActionUpdate, template); err != nil {
return err
}
return q.db.DeleteWorkspaceAgentPortSharesByTemplate(ctx, templateID)
}
func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error {
fetch := func(ctx context.Context, id uuid.UUID) (database.Workspace, error) {
return q.db.GetWorkspaceByID(ctx, id)
@ -2614,6 +2627,19 @@ func (q *querier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID
return q.db.ListWorkspaceAgentPortShares(ctx, workspaceID)
}
func (q *querier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error {
template, err := q.db.GetTemplateByID(ctx, templateID)
if err != nil {
return err
}
if err := q.authorizeContext(ctx, rbac.ActionUpdate, template); err != nil {
return err
}
return q.db.ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx, templateID)
}
func (q *querier) RegisterWorkspaceProxy(ctx context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
fetch := func(ctx context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
return q.db.GetWorkspaceProxyByID(ctx, arg.ID)

View File

@ -1635,6 +1635,20 @@ func (s *MethodTestSuite) TestWorkspacePortSharing() {
Port: ps.Port,
}).Asserts(ws, rbac.ActionUpdate).Returns()
}))
s.Run("DeleteWorkspaceAgentPortSharesByTemplate", s.Subtest(func(db database.Store, check *expects) {
u := dbgen.User(s.T(), db, database.User{})
t := dbgen.Template(s.T(), db, database.Template{})
ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID, TemplateID: t.ID})
_ = dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID})
check.Args(t.ID).Asserts(t, rbac.ActionUpdate).Returns()
}))
s.Run("ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate", s.Subtest(func(db database.Store, check *expects) {
u := dbgen.User(s.T(), db, database.User{})
t := dbgen.Template(s.T(), db, database.Template{})
ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID, TemplateID: t.ID})
_ = dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID})
check.Args(t.ID).Asserts(t, rbac.ActionUpdate).Returns()
}))
}
func (s *MethodTestSuite) TestExtraMethods() {

View File

@ -1455,6 +1455,30 @@ func (q *FakeQuerier) DeleteWorkspaceAgentPortShare(_ context.Context, arg datab
return nil
}
func (q *FakeQuerier) DeleteWorkspaceAgentPortSharesByTemplate(_ context.Context, templateID uuid.UUID) error {
err := validateDatabaseType(templateID)
if err != nil {
return err
}
q.mutex.Lock()
defer q.mutex.Unlock()
for _, workspace := range q.workspaces {
if workspace.TemplateID != templateID {
continue
}
for i, share := range q.workspaceAgentPortShares {
if share.WorkspaceID != workspace.ID {
continue
}
q.workspaceAgentPortShares = append(q.workspaceAgentPortShares[:i], q.workspaceAgentPortShares[i+1:]...)
}
}
return nil
}
func (q *FakeQuerier) FavoriteWorkspace(_ context.Context, arg uuid.UUID) error {
err := validateDatabaseType(arg)
if err != nil {
@ -6339,6 +6363,33 @@ func (q *FakeQuerier) ListWorkspaceAgentPortShares(_ context.Context, workspaceI
return shares, nil
}
func (q *FakeQuerier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(_ context.Context, templateID uuid.UUID) error {
err := validateDatabaseType(templateID)
if err != nil {
return err
}
q.mutex.Lock()
defer q.mutex.Unlock()
for _, workspace := range q.workspaces {
if workspace.TemplateID != templateID {
continue
}
for i, share := range q.workspaceAgentPortShares {
if share.WorkspaceID != workspace.ID {
continue
}
if share.ShareLevel == database.AppSharingLevelPublic {
share.ShareLevel = database.AppSharingLevelAuthenticated
}
q.workspaceAgentPortShares[i] = share
}
}
return nil
}
func (q *FakeQuerier) RegisterWorkspaceProxy(_ context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
q.mutex.Lock()
defer q.mutex.Unlock()

View File

@ -321,6 +321,13 @@ func (m metricsStore) DeleteWorkspaceAgentPortShare(ctx context.Context, arg dat
return r0
}
func (m metricsStore) DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Context, templateID uuid.UUID) error {
start := time.Now()
r0 := m.s.DeleteWorkspaceAgentPortSharesByTemplate(ctx, templateID)
m.queryLatencies.WithLabelValues("DeleteWorkspaceAgentPortSharesByTemplate").Observe(time.Since(start).Seconds())
return r0
}
func (m metricsStore) FavoriteWorkspace(ctx context.Context, arg uuid.UUID) error {
start := time.Now()
r0 := m.s.FavoriteWorkspace(ctx, arg)
@ -1698,6 +1705,13 @@ func (m metricsStore) ListWorkspaceAgentPortShares(ctx context.Context, workspac
return r0, r1
}
func (m metricsStore) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error {
start := time.Now()
r0 := m.s.ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx, templateID)
m.queryLatencies.WithLabelValues("ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate").Observe(time.Since(start).Seconds())
return r0
}
func (m metricsStore) RegisterWorkspaceProxy(ctx context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
start := time.Now()
proxy, err := m.s.RegisterWorkspaceProxy(ctx, arg)

View File

@ -542,6 +542,20 @@ func (mr *MockStoreMockRecorder) DeleteWorkspaceAgentPortShare(arg0, arg1 any) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWorkspaceAgentPortShare", reflect.TypeOf((*MockStore)(nil).DeleteWorkspaceAgentPortShare), arg0, arg1)
}
// DeleteWorkspaceAgentPortSharesByTemplate mocks base method.
func (m *MockStore) DeleteWorkspaceAgentPortSharesByTemplate(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteWorkspaceAgentPortSharesByTemplate", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteWorkspaceAgentPortSharesByTemplate indicates an expected call of DeleteWorkspaceAgentPortSharesByTemplate.
func (mr *MockStoreMockRecorder) DeleteWorkspaceAgentPortSharesByTemplate(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWorkspaceAgentPortSharesByTemplate", reflect.TypeOf((*MockStore)(nil).DeleteWorkspaceAgentPortSharesByTemplate), arg0, arg1)
}
// FavoriteWorkspace mocks base method.
func (m *MockStore) FavoriteWorkspace(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()
@ -3588,6 +3602,20 @@ func (mr *MockStoreMockRecorder) Ping(arg0 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockStore)(nil).Ping), arg0)
}
// ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate mocks base method.
func (m *MockStore) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate indicates an expected call of ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate.
func (mr *MockStoreMockRecorder) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate", reflect.TypeOf((*MockStore)(nil).ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate), arg0, arg1)
}
// RegisterWorkspaceProxy mocks base method.
func (m *MockStore) RegisterWorkspaceProxy(arg0 context.Context, arg1 database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
m.ctrl.T.Helper()

View File

@ -80,6 +80,7 @@ type sqlcQuerier interface {
DeleteTailnetPeer(ctx context.Context, arg DeleteTailnetPeerParams) (DeleteTailnetPeerRow, error)
DeleteTailnetTunnel(ctx context.Context, arg DeleteTailnetTunnelParams) (DeleteTailnetTunnelRow, error)
DeleteWorkspaceAgentPortShare(ctx context.Context, arg DeleteWorkspaceAgentPortShareParams) error
DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Context, templateID uuid.UUID) error
FavoriteWorkspace(ctx context.Context, id uuid.UUID) error
GetAPIKeyByID(ctx context.Context, id string) (APIKey, error)
// there is no unique constraint on empty token names
@ -330,6 +331,7 @@ type sqlcQuerier interface {
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error)
ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error)
ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error
RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error)
RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error
RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error

View File

@ -8483,6 +8483,15 @@ func (q *sqlQuerier) DeleteWorkspaceAgentPortShare(ctx context.Context, arg Dele
return err
}
const deleteWorkspaceAgentPortSharesByTemplate = `-- name: DeleteWorkspaceAgentPortSharesByTemplate :exec
DELETE FROM workspace_agent_port_share WHERE workspace_id IN (SELECT id FROM workspaces WHERE template_id = $1)
`
func (q *sqlQuerier) DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Context, templateID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteWorkspaceAgentPortSharesByTemplate, templateID)
return err
}
const getWorkspaceAgentPortShare = `-- name: GetWorkspaceAgentPortShare :one
SELECT workspace_id, agent_name, port, share_level FROM workspace_agent_port_share WHERE workspace_id = $1 AND agent_name = $2 AND port = $3
`
@ -8537,6 +8546,15 @@ func (q *sqlQuerier) ListWorkspaceAgentPortShares(ctx context.Context, workspace
return items, nil
}
const reduceWorkspaceAgentShareLevelToAuthenticatedByTemplate = `-- name: ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate :exec
UPDATE workspace_agent_port_share SET share_level = 'authenticated' WHERE share_level = 'public' AND workspace_id IN (SELECT id FROM workspaces WHERE template_id = $1)
`
func (q *sqlQuerier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, reduceWorkspaceAgentShareLevelToAuthenticatedByTemplate, templateID)
return err
}
const upsertWorkspaceAgentPortShare = `-- name: UpsertWorkspaceAgentPortShare :one
INSERT INTO workspace_agent_port_share (workspace_id, agent_name, port, share_level)
VALUES ($1, $2, $3, $4)

View File

@ -11,3 +11,9 @@ DELETE FROM workspace_agent_port_share WHERE workspace_id = $1 AND agent_name =
INSERT INTO workspace_agent_port_share (workspace_id, agent_name, port, share_level)
VALUES ($1, $2, $3, $4)
ON CONFLICT (workspace_id, agent_name, port) DO UPDATE SET share_level = $4 RETURNING *;
-- name: ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate :exec
UPDATE workspace_agent_port_share SET share_level = 'authenticated' WHERE share_level = 'public' AND workspace_id IN (SELECT id FROM workspaces WHERE template_id = $1);
-- name: DeleteWorkspaceAgentPortSharesByTemplate :exec
DELETE FROM workspace_agent_port_share WHERE workspace_id IN (SELECT id FROM workspaces WHERE template_id = $1);

View File

@ -698,6 +698,21 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
delete(groupACL, template.OrganizationID.String())
}
if template.MaxPortSharingLevel != maxPortShareLevel {
switch maxPortShareLevel {
case database.AppSharingLevelOwner:
err = tx.DeleteWorkspaceAgentPortSharesByTemplate(ctx, template.ID)
if err != nil {
return xerrors.Errorf("delete workspace agent port shares by template: %w", err)
}
case database.AppSharingLevelAuthenticated:
err = tx.ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx, template.ID)
if err != nil {
return xerrors.Errorf("reduce workspace agent share level to authenticated by template: %w", err)
}
}
}
var err error
err = tx.UpdateTemplateMetaByID(ctx, database.UpdateTemplateMetaByIDParams{
ID: template.ID,

View File

@ -22,6 +22,7 @@ import (
"github.com/coder/coder/v2/enterprise/coderd/license"
"github.com/coder/coder/v2/enterprise/coderd/schedule"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
)
@ -143,9 +144,12 @@ func TestTemplates(t *testing.T) {
t.Run("MaxPortShareLevel", func(t *testing.T) {
t.Parallel()
cfg := coderdtest.DeploymentValues(t)
cfg.Experiments = []string{"shared-ports"}
owner, user := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
IncludeProvisionerDaemon: true,
DeploymentValues: cfg,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
@ -154,9 +158,43 @@ func TestTemplates(t *testing.T) {
},
})
client, _ := coderdtest.CreateAnotherUser(t, owner, user.OrganizationID, rbac.RoleTemplateAdmin())
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "example",
},
},
}, {
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "some",
Type: "example",
Agents: []*proto.Agent{{
Id: "something",
Auth: &proto.Agent_Token{
Token: uuid.NewString(),
},
Name: "test",
}},
}, {
Name: "another",
Type: "example",
}},
},
},
}},
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
ws, err := client.Workspace(context.Background(), ws.ID)
require.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -175,6 +213,39 @@ func TestTemplates(t *testing.T) {
MaxPortShareLevel: &level,
})
require.ErrorContains(t, err, "invalid max port sharing level")
// Create public port share
_, err = client.UpsertWorkspaceAgentPortShare(ctx, ws.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
AgentName: ws.LatestBuild.Resources[0].Agents[0].Name,
Port: 8080,
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
})
require.NoError(t, err)
// Reduce max level to authenticated
level = codersdk.WorkspaceAgentPortShareLevelAuthenticated
_, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
MaxPortShareLevel: &level,
})
require.NoError(t, err)
// Ensure previously public port is now authenticated
wpsr, err := client.GetWorkspaceAgentPortShares(ctx, ws.ID)
require.NoError(t, err)
require.Len(t, wpsr.Shares, 1)
assert.Equal(t, codersdk.WorkspaceAgentPortShareLevelAuthenticated, wpsr.Shares[0].ShareLevel)
// reduce max level to owner
level = codersdk.WorkspaceAgentPortShareLevelOwner
_, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
MaxPortShareLevel: &level,
})
require.NoError(t, err)
// Ensure previously authenticated port is removed
wpsr, err = client.GetWorkspaceAgentPortShares(ctx, ws.ID)
require.NoError(t, err)
require.Empty(t, wpsr.Shares)
})
t.Run("BlockDisablingAutoOffWithMaxTTL", func(t *testing.T) {