Skip to content

Commit 61bd341

Browse files
authored
chore: change max share level on existing port shares (#12411)
1 parent 5106d9f commit 61bd341

File tree

10 files changed

+246
-1
lines changed

10 files changed

+246
-1
lines changed

coderd/database/dbauthz/dbauthz.go

+26
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,19 @@ func (q *querier) DeleteWorkspaceAgentPortShare(ctx context.Context, arg databas
915915
return q.db.DeleteWorkspaceAgentPortShare(ctx, arg)
916916
}
917917

918+
func (q *querier) DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Context, templateID uuid.UUID) error {
919+
template, err := q.db.GetTemplateByID(ctx, templateID)
920+
if err != nil {
921+
return err
922+
}
923+
924+
if err := q.authorizeContext(ctx, rbac.ActionUpdate, template); err != nil {
925+
return err
926+
}
927+
928+
return q.db.DeleteWorkspaceAgentPortSharesByTemplate(ctx, templateID)
929+
}
930+
918931
func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error {
919932
fetch := func(ctx context.Context, id uuid.UUID) (database.Workspace, error) {
920933
return q.db.GetWorkspaceByID(ctx, id)
@@ -2614,6 +2627,19 @@ func (q *querier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID
26142627
return q.db.ListWorkspaceAgentPortShares(ctx, workspaceID)
26152628
}
26162629

2630+
func (q *querier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error {
2631+
template, err := q.db.GetTemplateByID(ctx, templateID)
2632+
if err != nil {
2633+
return err
2634+
}
2635+
2636+
if err := q.authorizeContext(ctx, rbac.ActionUpdate, template); err != nil {
2637+
return err
2638+
}
2639+
2640+
return q.db.ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx, templateID)
2641+
}
2642+
26172643
func (q *querier) RegisterWorkspaceProxy(ctx context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
26182644
fetch := func(ctx context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
26192645
return q.db.GetWorkspaceProxyByID(ctx, arg.ID)

coderd/database/dbauthz/dbauthz_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -1635,6 +1635,20 @@ func (s *MethodTestSuite) TestWorkspacePortSharing() {
16351635
Port: ps.Port,
16361636
}).Asserts(ws, rbac.ActionUpdate).Returns()
16371637
}))
1638+
s.Run("DeleteWorkspaceAgentPortSharesByTemplate", s.Subtest(func(db database.Store, check *expects) {
1639+
u := dbgen.User(s.T(), db, database.User{})
1640+
t := dbgen.Template(s.T(), db, database.Template{})
1641+
ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID, TemplateID: t.ID})
1642+
_ = dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID})
1643+
check.Args(t.ID).Asserts(t, rbac.ActionUpdate).Returns()
1644+
}))
1645+
s.Run("ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate", s.Subtest(func(db database.Store, check *expects) {
1646+
u := dbgen.User(s.T(), db, database.User{})
1647+
t := dbgen.Template(s.T(), db, database.Template{})
1648+
ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID, TemplateID: t.ID})
1649+
_ = dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID})
1650+
check.Args(t.ID).Asserts(t, rbac.ActionUpdate).Returns()
1651+
}))
16381652
}
16391653

16401654
func (s *MethodTestSuite) TestExtraMethods() {

coderd/database/dbmem/dbmem.go

+51
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,30 @@ func (q *FakeQuerier) DeleteWorkspaceAgentPortShare(_ context.Context, arg datab
14551455
return nil
14561456
}
14571457

1458+
func (q *FakeQuerier) DeleteWorkspaceAgentPortSharesByTemplate(_ context.Context, templateID uuid.UUID) error {
1459+
err := validateDatabaseType(templateID)
1460+
if err != nil {
1461+
return err
1462+
}
1463+
1464+
q.mutex.Lock()
1465+
defer q.mutex.Unlock()
1466+
1467+
for _, workspace := range q.workspaces {
1468+
if workspace.TemplateID != templateID {
1469+
continue
1470+
}
1471+
for i, share := range q.workspaceAgentPortShares {
1472+
if share.WorkspaceID != workspace.ID {
1473+
continue
1474+
}
1475+
q.workspaceAgentPortShares = append(q.workspaceAgentPortShares[:i], q.workspaceAgentPortShares[i+1:]...)
1476+
}
1477+
}
1478+
1479+
return nil
1480+
}
1481+
14581482
func (q *FakeQuerier) FavoriteWorkspace(_ context.Context, arg uuid.UUID) error {
14591483
err := validateDatabaseType(arg)
14601484
if err != nil {
@@ -6339,6 +6363,33 @@ func (q *FakeQuerier) ListWorkspaceAgentPortShares(_ context.Context, workspaceI
63396363
return shares, nil
63406364
}
63416365

6366+
func (q *FakeQuerier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(_ context.Context, templateID uuid.UUID) error {
6367+
err := validateDatabaseType(templateID)
6368+
if err != nil {
6369+
return err
6370+
}
6371+
6372+
q.mutex.Lock()
6373+
defer q.mutex.Unlock()
6374+
6375+
for _, workspace := range q.workspaces {
6376+
if workspace.TemplateID != templateID {
6377+
continue
6378+
}
6379+
for i, share := range q.workspaceAgentPortShares {
6380+
if share.WorkspaceID != workspace.ID {
6381+
continue
6382+
}
6383+
if share.ShareLevel == database.AppSharingLevelPublic {
6384+
share.ShareLevel = database.AppSharingLevelAuthenticated
6385+
}
6386+
q.workspaceAgentPortShares[i] = share
6387+
}
6388+
}
6389+
6390+
return nil
6391+
}
6392+
63426393
func (q *FakeQuerier) RegisterWorkspaceProxy(_ context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
63436394
q.mutex.Lock()
63446395
defer q.mutex.Unlock()

coderd/database/dbmetrics/dbmetrics.go

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbmock/dbmock.go

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/querier.go

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspaceagentportshare.sql

+6
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ DELETE FROM workspace_agent_port_share WHERE workspace_id = $1 AND agent_name =
1111
INSERT INTO workspace_agent_port_share (workspace_id, agent_name, port, share_level)
1212
VALUES ($1, $2, $3, $4)
1313
ON CONFLICT (workspace_id, agent_name, port) DO UPDATE SET share_level = $4 RETURNING *;
14+
15+
-- name: ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate :exec
16+
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);
17+
18+
-- name: DeleteWorkspaceAgentPortSharesByTemplate :exec
19+
DELETE FROM workspace_agent_port_share WHERE workspace_id IN (SELECT id FROM workspaces WHERE template_id = $1);

coderd/templates.go

+15
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,21 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
698698
delete(groupACL, template.OrganizationID.String())
699699
}
700700

701+
if template.MaxPortSharingLevel != maxPortShareLevel {
702+
switch maxPortShareLevel {
703+
case database.AppSharingLevelOwner:
704+
err = tx.DeleteWorkspaceAgentPortSharesByTemplate(ctx, template.ID)
705+
if err != nil {
706+
return xerrors.Errorf("delete workspace agent port shares by template: %w", err)
707+
}
708+
case database.AppSharingLevelAuthenticated:
709+
err = tx.ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx, template.ID)
710+
if err != nil {
711+
return xerrors.Errorf("reduce workspace agent share level to authenticated by template: %w", err)
712+
}
713+
}
714+
}
715+
701716
var err error
702717
err = tx.UpdateTemplateMetaByID(ctx, database.UpdateTemplateMetaByIDParams{
703718
ID: template.ID,

enterprise/coderd/templates_test.go

+72-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/coder/coder/v2/enterprise/coderd/license"
2323
"github.com/coder/coder/v2/enterprise/coderd/schedule"
2424
"github.com/coder/coder/v2/provisioner/echo"
25+
"github.com/coder/coder/v2/provisionersdk/proto"
2526
"github.com/coder/coder/v2/testutil"
2627
)
2728

@@ -143,9 +144,12 @@ func TestTemplates(t *testing.T) {
143144
t.Run("MaxPortShareLevel", func(t *testing.T) {
144145
t.Parallel()
145146

147+
cfg := coderdtest.DeploymentValues(t)
148+
cfg.Experiments = []string{"shared-ports"}
146149
owner, user := coderdenttest.New(t, &coderdenttest.Options{
147150
Options: &coderdtest.Options{
148151
IncludeProvisionerDaemon: true,
152+
DeploymentValues: cfg,
149153
},
150154
LicenseOptions: &coderdenttest.LicenseOptions{
151155
Features: license.Features{
@@ -154,9 +158,43 @@ func TestTemplates(t *testing.T) {
154158
},
155159
})
156160
client, _ := coderdtest.CreateAnotherUser(t, owner, user.OrganizationID, rbac.RoleTemplateAdmin())
157-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
161+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
162+
Parse: echo.ParseComplete,
163+
ProvisionPlan: echo.PlanComplete,
164+
ProvisionApply: []*proto.Response{{
165+
Type: &proto.Response_Log{
166+
Log: &proto.Log{
167+
Level: proto.LogLevel_INFO,
168+
Output: "example",
169+
},
170+
},
171+
}, {
172+
Type: &proto.Response_Apply{
173+
Apply: &proto.ApplyComplete{
174+
Resources: []*proto.Resource{{
175+
Name: "some",
176+
Type: "example",
177+
Agents: []*proto.Agent{{
178+
Id: "something",
179+
Auth: &proto.Agent_Token{
180+
Token: uuid.NewString(),
181+
},
182+
Name: "test",
183+
}},
184+
}, {
185+
Name: "another",
186+
Type: "example",
187+
}},
188+
},
189+
},
190+
}},
191+
})
158192
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
159193
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
194+
ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
195+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
196+
ws, err := client.Workspace(context.Background(), ws.ID)
197+
require.NoError(t, err)
160198

161199
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
162200
defer cancel()
@@ -175,6 +213,39 @@ func TestTemplates(t *testing.T) {
175213
MaxPortShareLevel: &level,
176214
})
177215
require.ErrorContains(t, err, "invalid max port sharing level")
216+
217+
// Create public port share
218+
_, err = client.UpsertWorkspaceAgentPortShare(ctx, ws.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
219+
AgentName: ws.LatestBuild.Resources[0].Agents[0].Name,
220+
Port: 8080,
221+
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
222+
})
223+
require.NoError(t, err)
224+
225+
// Reduce max level to authenticated
226+
level = codersdk.WorkspaceAgentPortShareLevelAuthenticated
227+
_, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
228+
MaxPortShareLevel: &level,
229+
})
230+
require.NoError(t, err)
231+
232+
// Ensure previously public port is now authenticated
233+
wpsr, err := client.GetWorkspaceAgentPortShares(ctx, ws.ID)
234+
require.NoError(t, err)
235+
require.Len(t, wpsr.Shares, 1)
236+
assert.Equal(t, codersdk.WorkspaceAgentPortShareLevelAuthenticated, wpsr.Shares[0].ShareLevel)
237+
238+
// reduce max level to owner
239+
level = codersdk.WorkspaceAgentPortShareLevelOwner
240+
_, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
241+
MaxPortShareLevel: &level,
242+
})
243+
require.NoError(t, err)
244+
245+
// Ensure previously authenticated port is removed
246+
wpsr, err = client.GetWorkspaceAgentPortShares(ctx, ws.ID)
247+
require.NoError(t, err)
248+
require.Empty(t, wpsr.Shares)
178249
})
179250

180251
t.Run("BlockDisablingAutoOffWithMaxTTL", func(t *testing.T) {

0 commit comments

Comments
 (0)