From af0e171595a0c22d8dddca89cf7a255e48d8a06b Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 4 Feb 2026 15:33:48 +0000 Subject: [PATCH] feat(coderd/agentapi): support terraform-defined subagent ids (#21837) Update `coderd/agentapi` to handle pre-created sub agents --- coderd/agentapi/manifest.go | 6 + coderd/agentapi/subagent.go | 75 ++++-- coderd/agentapi/subagent_test.go | 219 ++++++++++++++++++ coderd/apidoc/docs.go | 6 + coderd/apidoc/swagger.json | 6 + coderd/database/dbauthz/dbauthz.go | 17 +- coderd/database/dbauthz/dbauthz_test.go | 11 + coderd/database/dbmetrics/querymetrics.go | 8 + coderd/database/dbmock/dbmock.go | 14 ++ coderd/database/dump.sql | 4 +- ...14_add_update_agent_api_key_scope.down.sql | 1 + ...0414_add_update_agent_api_key_scope.up.sql | 2 + coderd/database/models.go | 8 +- coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 20 ++ coderd/database/queries/workspaceagents.sql | 8 + coderd/rbac/object_gen.go | 3 + coderd/rbac/policy/policy.go | 2 + coderd/rbac/roles.go | 5 +- coderd/rbac/roles_test.go | 11 +- coderd/rbac/scopes_constants_gen.go | 6 + codersdk/apikey_scopes_gen.go | 2 + codersdk/rbacresources_gen.go | 5 +- docs/reference/api/members.md | 10 +- docs/reference/api/schemas.md | 12 +- site/src/api/rbacresourcesGenerated.ts | 2 + site/src/api/typesGenerated.ts | 8 +- 27 files changed, 432 insertions(+), 40 deletions(-) create mode 100644 coderd/database/migrations/000414_add_update_agent_api_key_scope.down.sql create mode 100644 coderd/database/migrations/000414_add_update_agent_api_key_scope.up.sql diff --git a/coderd/agentapi/manifest.go b/coderd/agentapi/manifest.go index 2221d2bc03..8decc18ffd 100644 --- a/coderd/agentapi/manifest.go +++ b/coderd/agentapi/manifest.go @@ -249,11 +249,17 @@ func dbAppToProto(dbApp database.WorkspaceApp, agent database.WorkspaceAgent, ow func dbAgentDevcontainersToProto(devcontainers []database.WorkspaceAgentDevcontainer) []*agentproto.WorkspaceAgentDevcontainer { ret := make([]*agentproto.WorkspaceAgentDevcontainer, len(devcontainers)) for i, dc := range devcontainers { + var subagentID []byte + if dc.SubagentID.Valid { + subagentID = dc.SubagentID.UUID[:] + } + ret[i] = &agentproto.WorkspaceAgentDevcontainer{ Id: dc.ID[:], Name: dc.Name, WorkspaceFolder: dc.WorkspaceFolder, ConfigPath: dc.ConfigPath, + SubagentId: subagentID, } } return ret diff --git a/coderd/agentapi/subagent.go b/coderd/agentapi/subagent.go index a3f71ccb8a..9e8f9b59c9 100644 --- a/coderd/agentapi/subagent.go +++ b/coderd/agentapi/subagent.go @@ -37,25 +37,6 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create //nolint:gocritic // This gives us only the permissions required to do the job. ctx = dbauthz.AsSubAgentAPI(ctx, a.OrganizationID, a.OwnerID) - parentAgent, err := a.AgentFn(ctx) - if err != nil { - return nil, xerrors.Errorf("get parent agent: %w", err) - } - - agentName := req.Name - if agentName == "" { - return nil, codersdk.ValidationError{ - Field: "name", - Detail: "agent name cannot be empty", - } - } - if !provisioner.AgentNameRegex.MatchString(agentName) { - return nil, codersdk.ValidationError{ - Field: "name", - Detail: fmt.Sprintf("agent name %q does not match regex %q", agentName, provisioner.AgentNameRegex), - } - } - createdAt := a.Clock.Now() displayApps := make([]database.DisplayApp, 0, len(req.DisplayApps)) @@ -83,6 +64,62 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create displayApps = append(displayApps, app) } + parentAgent, err := a.AgentFn(ctx) + if err != nil { + return nil, xerrors.Errorf("get parent agent: %w", err) + } + + // An ID is only given in the request when it is a terraform-defined devcontainer + // that has attached resources. These subagents are pre-provisioned by terraform + // (the agent record already exists), so we update configurable fields like + // display_apps rather than creating a new agent. + if req.Id != nil { + id, err := uuid.FromBytes(req.Id) + if err != nil { + return nil, xerrors.Errorf("parse agent id: %w", err) + } + + subAgent, err := a.Database.GetWorkspaceAgentByID(ctx, id) + if err != nil { + return nil, xerrors.Errorf("get workspace agent by id: %w", err) + } + + // Validate that the subagent belongs to the current parent agent to + // prevent updating subagents from other agents within the same workspace. + if !subAgent.ParentID.Valid || subAgent.ParentID.UUID != parentAgent.ID { + return nil, xerrors.Errorf("subagent does not belong to this parent agent") + } + + if err := a.Database.UpdateWorkspaceAgentDisplayAppsByID(ctx, database.UpdateWorkspaceAgentDisplayAppsByIDParams{ + ID: id, + DisplayApps: displayApps, + UpdatedAt: createdAt, + }); err != nil { + return nil, xerrors.Errorf("update workspace agent display apps: %w", err) + } + + return &agentproto.CreateSubAgentResponse{ + Agent: &agentproto.SubAgent{ + Name: subAgent.Name, + Id: subAgent.ID[:], + AuthToken: subAgent.AuthToken[:], + }, + }, nil + } + + agentName := req.Name + if agentName == "" { + return nil, codersdk.ValidationError{ + Field: "name", + Detail: "agent name cannot be empty", + } + } + if !provisioner.AgentNameRegex.MatchString(agentName) { + return nil, codersdk.ValidationError{ + Field: "name", + Detail: fmt.Sprintf("agent name %q does not match regex %q", agentName, provisioner.AgentNameRegex), + } + } subAgent, err := a.Database.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{ ID: uuid.New(), ParentID: uuid.NullUUID{Valid: true, UUID: parentAgent.ID}, diff --git a/coderd/agentapi/subagent_test.go b/coderd/agentapi/subagent_test.go index 732bc157e9..2c5bc844f2 100644 --- a/coderd/agentapi/subagent_test.go +++ b/coderd/agentapi/subagent_test.go @@ -1132,6 +1132,225 @@ func TestSubAgentAPI(t *testing.T) { require.Equal(t, "Custom App", apps[0].DisplayName) }) + t.Run("CreateSubAgentUpdatesExisting", func(t *testing.T) { + t.Parallel() + + baseChildAgent := database.WorkspaceAgent{ + Name: "existing-child-agent", + Directory: "/workspaces/test", + Architecture: "amd64", + OperatingSystem: "linux", + DisplayApps: []database.DisplayApp{database.DisplayAppVscode}, + } + + type testCase struct { + name string + setup func(t *testing.T, db database.Store, agent database.WorkspaceAgent) *proto.CreateSubAgentRequest + wantErr string + check func(t *testing.T, ctx context.Context, db database.Store, resp *proto.CreateSubAgentResponse, agent database.WorkspaceAgent) + } + + tests := []testCase{ + { + name: "OK", + setup: func(t *testing.T, db database.Store, agent database.WorkspaceAgent) *proto.CreateSubAgentRequest { + // Given: An existing child agent with some display apps. + childAgent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ + ParentID: uuid.NullUUID{Valid: true, UUID: agent.ID}, + ResourceID: agent.ResourceID, + Name: baseChildAgent.Name, + Directory: baseChildAgent.Directory, + Architecture: baseChildAgent.Architecture, + OperatingSystem: baseChildAgent.OperatingSystem, + DisplayApps: baseChildAgent.DisplayApps, + }) + + // When: We call CreateSubAgent with the existing agent's ID and new display apps. + return &proto.CreateSubAgentRequest{ + Id: childAgent.ID[:], + DisplayApps: []proto.CreateSubAgentRequest_DisplayApp{ + proto.CreateSubAgentRequest_WEB_TERMINAL, + proto.CreateSubAgentRequest_SSH_HELPER, + }, + } + }, + check: func(t *testing.T, ctx context.Context, db database.Store, resp *proto.CreateSubAgentResponse, agent database.WorkspaceAgent) { + // Then: The response contains the existing agent's details. + require.NotNil(t, resp.Agent) + require.Equal(t, baseChildAgent.Name, resp.Agent.Name) + + agentID, err := uuid.FromBytes(resp.Agent.Id) + require.NoError(t, err) + + // And: The database agent's display apps are updated. + updatedAgent, err := db.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), agentID) + require.NoError(t, err) + require.Len(t, updatedAgent.DisplayApps, 2) + require.Contains(t, updatedAgent.DisplayApps, database.DisplayAppWebTerminal) + require.Contains(t, updatedAgent.DisplayApps, database.DisplayAppSSHHelper) + }, + }, + { + name: "OK_OtherFieldsNotModified", + setup: func(t *testing.T, db database.Store, agent database.WorkspaceAgent) *proto.CreateSubAgentRequest { + // Given: An existing child agent with specific properties. + childAgent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ + ParentID: uuid.NullUUID{Valid: true, UUID: agent.ID}, + ResourceID: agent.ResourceID, + Name: baseChildAgent.Name, + Directory: baseChildAgent.Directory, + Architecture: baseChildAgent.Architecture, + OperatingSystem: baseChildAgent.OperatingSystem, + DisplayApps: baseChildAgent.DisplayApps, + }) + + // When: We call CreateSubAgent with different values for name, directory, arch, and OS. + return &proto.CreateSubAgentRequest{ + Id: childAgent.ID[:], + Name: "different-name", + Directory: "/different/path", + Architecture: "arm64", + OperatingSystem: "darwin", + DisplayApps: []proto.CreateSubAgentRequest_DisplayApp{ + proto.CreateSubAgentRequest_WEB_TERMINAL, + }, + } + }, + check: func(t *testing.T, ctx context.Context, db database.Store, resp *proto.CreateSubAgentResponse, agent database.WorkspaceAgent) { + // Then: The response contains the original agent name, not the new one. + require.NotNil(t, resp.Agent) + require.Equal(t, baseChildAgent.Name, resp.Agent.Name) + + agentID, err := uuid.FromBytes(resp.Agent.Id) + require.NoError(t, err) + + // And: The database agent's other fields are unchanged. + updatedAgent, err := db.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), agentID) + require.NoError(t, err) + require.Equal(t, baseChildAgent.Name, updatedAgent.Name) + require.Equal(t, baseChildAgent.Directory, updatedAgent.Directory) + require.Equal(t, baseChildAgent.Architecture, updatedAgent.Architecture) + require.Equal(t, baseChildAgent.OperatingSystem, updatedAgent.OperatingSystem) + + // But display apps should be updated. + require.Len(t, updatedAgent.DisplayApps, 1) + require.Equal(t, database.DisplayAppWebTerminal, updatedAgent.DisplayApps[0]) + }, + }, + { + name: "Error/MalformedID", + setup: func(t *testing.T, db database.Store, agent database.WorkspaceAgent) *proto.CreateSubAgentRequest { + // When: We call CreateSubAgent with malformed ID bytes (not 16 bytes). + // uuid.FromBytes requires exactly 16 bytes, so we provide fewer. + return &proto.CreateSubAgentRequest{ + Id: []byte("short"), + } + }, + wantErr: "parse agent id", + }, + { + name: "Error/AgentNotFound", + setup: func(t *testing.T, db database.Store, agent database.WorkspaceAgent) *proto.CreateSubAgentRequest { + // When: We call CreateSubAgent with a non-existent agent ID. + nonExistentID := uuid.New() + return &proto.CreateSubAgentRequest{ + Id: nonExistentID[:], + } + }, + wantErr: "get workspace agent by id", + }, + { + name: "Error/ParentMismatch", + setup: func(t *testing.T, db database.Store, agent database.WorkspaceAgent) *proto.CreateSubAgentRequest { + // Create a second agent (sibling) within the same workspace/resource. + // This sibling has a different parent ID (or no parent). + siblingAgent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ + ParentID: uuid.NullUUID{Valid: false}, // No parent - it's a top-level agent + ResourceID: agent.ResourceID, + Name: "sibling-agent", + Directory: "/workspaces/sibling", + Architecture: "amd64", + OperatingSystem: "linux", + }) + + // Create a child of the sibling agent (not our agent). + childOfSibling := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ + ParentID: uuid.NullUUID{Valid: true, UUID: siblingAgent.ID}, + ResourceID: agent.ResourceID, + Name: "child-of-sibling", + Directory: "/workspaces/test", + Architecture: "amd64", + OperatingSystem: "linux", + }) + + // When: Our API (which is for `agent`) tries to update the child of `siblingAgent`. + return &proto.CreateSubAgentRequest{ + Id: childOfSibling.ID[:], + DisplayApps: []proto.CreateSubAgentRequest_DisplayApp{ + proto.CreateSubAgentRequest_VSCODE, + }, + } + }, + wantErr: "subagent does not belong to this parent agent", + }, + + { + name: "Error/NoParentID", + setup: func(t *testing.T, db database.Store, agent database.WorkspaceAgent) *proto.CreateSubAgentRequest { + // Given: An agent without a parent (a top-level agent). + topLevelAgent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ + ParentID: uuid.NullUUID{Valid: false}, // No parent + ResourceID: agent.ResourceID, + Name: "top-level-agent", + Directory: "/workspaces/test", + Architecture: "amd64", + OperatingSystem: "linux", + }) + + // When: We try to update this agent as if it were a subagent. + return &proto.CreateSubAgentRequest{ + Id: topLevelAgent.ID[:], + DisplayApps: []proto.CreateSubAgentRequest_DisplayApp{ + proto.CreateSubAgentRequest_VSCODE, + }, + } + }, + wantErr: "subagent does not belong to this parent agent", + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + var ( + log = testutil.Logger(t) + clock = quartz.NewMock(t) + + db, org = newDatabaseWithOrg(t) + user, agent = newUserWithWorkspaceAgent(t, db, org) + api = newAgentAPI(t, log, db, clock, user, org, agent) + ) + + req := tc.setup(t, db, agent) + ctx := testutil.Context(t, testutil.WaitShort) + resp, err := api.CreateSubAgent(ctx, req) + + if tc.wantErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.wantErr) + return + } + + require.NoError(t, err) + if tc.check != nil { + tc.check(t, ctx, db, resp, agent) + } + }) + } + }) + t.Run("ListSubAgents", func(t *testing.T) { t.Parallel() diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index f465e48f25..6be8ea8e2b 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12698,6 +12698,7 @@ const docTemplate = `{ "workspace:start", "workspace:stop", "workspace:update", + "workspace:update_agent", "workspace_agent_devcontainers:*", "workspace_agent_devcontainers:create", "workspace_agent_resource_monitor:*", @@ -12716,6 +12717,7 @@ const docTemplate = `{ "workspace_dormant:start", "workspace_dormant:stop", "workspace_dormant:update", + "workspace_dormant:update_agent", "workspace_proxy:*", "workspace_proxy:create", "workspace_proxy:delete", @@ -12900,6 +12902,7 @@ const docTemplate = `{ "APIKeyScopeWorkspaceStart", "APIKeyScopeWorkspaceStop", "APIKeyScopeWorkspaceUpdate", + "APIKeyScopeWorkspaceUpdateAgent", "APIKeyScopeWorkspaceAgentDevcontainersAll", "APIKeyScopeWorkspaceAgentDevcontainersCreate", "APIKeyScopeWorkspaceAgentResourceMonitorAll", @@ -12918,6 +12921,7 @@ const docTemplate = `{ "APIKeyScopeWorkspaceDormantStart", "APIKeyScopeWorkspaceDormantStop", "APIKeyScopeWorkspaceDormantUpdate", + "APIKeyScopeWorkspaceDormantUpdateAgent", "APIKeyScopeWorkspaceProxyAll", "APIKeyScopeWorkspaceProxyCreate", "APIKeyScopeWorkspaceProxyDelete", @@ -17792,6 +17796,7 @@ const docTemplate = `{ "share", "unassign", "update", + "update_agent", "update_personal", "use", "view_insights", @@ -17811,6 +17816,7 @@ const docTemplate = `{ "ActionShare", "ActionUnassign", "ActionUpdate", + "ActionUpdateAgent", "ActionUpdatePersonal", "ActionUse", "ActionViewInsights", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 478582d8d7..2c44e36263 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -11320,6 +11320,7 @@ "workspace:start", "workspace:stop", "workspace:update", + "workspace:update_agent", "workspace_agent_devcontainers:*", "workspace_agent_devcontainers:create", "workspace_agent_resource_monitor:*", @@ -11338,6 +11339,7 @@ "workspace_dormant:start", "workspace_dormant:stop", "workspace_dormant:update", + "workspace_dormant:update_agent", "workspace_proxy:*", "workspace_proxy:create", "workspace_proxy:delete", @@ -11522,6 +11524,7 @@ "APIKeyScopeWorkspaceStart", "APIKeyScopeWorkspaceStop", "APIKeyScopeWorkspaceUpdate", + "APIKeyScopeWorkspaceUpdateAgent", "APIKeyScopeWorkspaceAgentDevcontainersAll", "APIKeyScopeWorkspaceAgentDevcontainersCreate", "APIKeyScopeWorkspaceAgentResourceMonitorAll", @@ -11540,6 +11543,7 @@ "APIKeyScopeWorkspaceDormantStart", "APIKeyScopeWorkspaceDormantStop", "APIKeyScopeWorkspaceDormantUpdate", + "APIKeyScopeWorkspaceDormantUpdateAgent", "APIKeyScopeWorkspaceProxyAll", "APIKeyScopeWorkspaceProxyCreate", "APIKeyScopeWorkspaceProxyDelete", @@ -16218,6 +16222,7 @@ "share", "unassign", "update", + "update_agent", "update_personal", "use", "view_insights", @@ -16237,6 +16242,7 @@ "ActionShare", "ActionUnassign", "ActionUpdate", + "ActionUpdateAgent", "ActionUpdatePersonal", "ActionUse", "ActionViewInsights", diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 812cd51e93..84560dc9b7 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -412,7 +412,7 @@ var ( ByOrgID: map[string]rbac.OrgPermissions{ orgID.String(): { Member: rbac.Permissions(map[string][]policy.Action{ - rbac.ResourceWorkspace.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionCreateAgent, policy.ActionDeleteAgent}, + rbac.ResourceWorkspace.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent}, }), }, }, @@ -442,7 +442,7 @@ var ( rbac.ResourceProvisionerDaemon.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate}, rbac.ResourceUser.Type: rbac.ResourceUser.AvailableActions(), rbac.ResourceWorkspaceDormant.Type: {policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStop}, - rbac.ResourceWorkspace.Type: {policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop, policy.ActionSSH, policy.ActionCreateAgent, policy.ActionDeleteAgent}, + rbac.ResourceWorkspace.Type: {policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop, policy.ActionSSH, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent}, rbac.ResourceWorkspaceProxy.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete}, rbac.ResourceDeploymentConfig.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete}, rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}, @@ -5726,6 +5726,19 @@ func (q *querier) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg da return q.db.UpdateWorkspaceAgentConnectionByID(ctx, arg) } +func (q *querier) UpdateWorkspaceAgentDisplayAppsByID(ctx context.Context, arg database.UpdateWorkspaceAgentDisplayAppsByIDParams) error { + workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.ID) + if err != nil { + return err + } + + if err := q.authorizeContext(ctx, policy.ActionUpdateAgent, workspace); err != nil { + return err + } + + return q.db.UpdateWorkspaceAgentDisplayAppsByID(ctx, arg) +} + func (q *querier) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg database.UpdateWorkspaceAgentLifecycleStateByIDParams) error { workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.ID) if err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 9b9140e998..c6f1ea59dd 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1929,6 +1929,17 @@ func (s *MethodTestSuite) TestWorkspace() { dbm.EXPECT().UpdateWorkspaceAgentStartupByID(gomock.Any(), arg).Return(nil).AnyTimes() check.Args(arg).Asserts(w, policy.ActionUpdate).Returns() })) + s.Run("UpdateWorkspaceAgentDisplayAppsByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { + w := testutil.Fake(s.T(), faker, database.Workspace{}) + agt := testutil.Fake(s.T(), faker, database.WorkspaceAgent{}) + arg := database.UpdateWorkspaceAgentDisplayAppsByIDParams{ + ID: agt.ID, + DisplayApps: []database.DisplayApp{database.DisplayAppVscode}, + } + dbm.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agt.ID).Return(w, nil).AnyTimes() + dbm.EXPECT().UpdateWorkspaceAgentDisplayAppsByID(gomock.Any(), arg).Return(nil).AnyTimes() + check.Args(arg).Asserts(w, policy.ActionUpdateAgent).Returns() + })) s.Run("GetWorkspaceAgentLogsAfter", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { ws := testutil.Fake(s.T(), faker, database.Workspace{}) agt := testutil.Fake(s.T(), faker, database.WorkspaceAgent{}) diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index b7ecd59546..f2289e2b0c 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -3909,6 +3909,14 @@ func (m queryMetricsStore) UpdateWorkspaceAgentConnectionByID(ctx context.Contex return r0 } +func (m queryMetricsStore) UpdateWorkspaceAgentDisplayAppsByID(ctx context.Context, arg database.UpdateWorkspaceAgentDisplayAppsByIDParams) error { + start := time.Now() + r0 := m.s.UpdateWorkspaceAgentDisplayAppsByID(ctx, arg) + m.queryLatencies.WithLabelValues("UpdateWorkspaceAgentDisplayAppsByID").Observe(time.Since(start).Seconds()) + m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateWorkspaceAgentDisplayAppsByID").Inc() + return r0 +} + func (m queryMetricsStore) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg database.UpdateWorkspaceAgentLifecycleStateByIDParams) error { start := time.Now() r0 := m.s.UpdateWorkspaceAgentLifecycleStateByID(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index bf560b513a..5aa7c74dc9 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -7321,6 +7321,20 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceAgentConnectionByID(ctx, arg any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAgentConnectionByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAgentConnectionByID), ctx, arg) } +// UpdateWorkspaceAgentDisplayAppsByID mocks base method. +func (m *MockStore) UpdateWorkspaceAgentDisplayAppsByID(ctx context.Context, arg database.UpdateWorkspaceAgentDisplayAppsByIDParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateWorkspaceAgentDisplayAppsByID", ctx, arg) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateWorkspaceAgentDisplayAppsByID indicates an expected call of UpdateWorkspaceAgentDisplayAppsByID. +func (mr *MockStoreMockRecorder) UpdateWorkspaceAgentDisplayAppsByID(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAgentDisplayAppsByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAgentDisplayAppsByID), ctx, arg) +} + // UpdateWorkspaceAgentLifecycleStateByID mocks base method. func (m *MockStore) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg database.UpdateWorkspaceAgentLifecycleStateByIDParams) error { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index ef17fe96e2..7bca015b7f 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -208,7 +208,9 @@ CREATE TYPE api_key_scope AS ENUM ( 'boundary_usage:*', 'boundary_usage:delete', 'boundary_usage:read', - 'boundary_usage:update' + 'boundary_usage:update', + 'workspace:update_agent', + 'workspace_dormant:update_agent' ); CREATE TYPE app_sharing_level AS ENUM ( diff --git a/coderd/database/migrations/000414_add_update_agent_api_key_scope.down.sql b/coderd/database/migrations/000414_add_update_agent_api_key_scope.down.sql new file mode 100644 index 0000000000..c730ebbe36 --- /dev/null +++ b/coderd/database/migrations/000414_add_update_agent_api_key_scope.down.sql @@ -0,0 +1 @@ +-- No-op for update agent scopes: keep enum values to avoid dependency churn. diff --git a/coderd/database/migrations/000414_add_update_agent_api_key_scope.up.sql b/coderd/database/migrations/000414_add_update_agent_api_key_scope.up.sql new file mode 100644 index 0000000000..6bd4ff35f4 --- /dev/null +++ b/coderd/database/migrations/000414_add_update_agent_api_key_scope.up.sql @@ -0,0 +1,2 @@ +ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:update_agent'; +ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:update_agent'; diff --git a/coderd/database/models.go b/coderd/database/models.go index 3f5ada9693..752114575e 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -217,6 +217,8 @@ const ( ApiKeyScopeBoundaryUsageDelete APIKeyScope = "boundary_usage:delete" ApiKeyScopeBoundaryUsageRead APIKeyScope = "boundary_usage:read" ApiKeyScopeBoundaryUsageUpdate APIKeyScope = "boundary_usage:update" + ApiKeyScopeWorkspaceUpdateAgent APIKeyScope = "workspace:update_agent" + ApiKeyScopeWorkspaceDormantUpdateAgent APIKeyScope = "workspace_dormant:update_agent" ) func (e *APIKeyScope) Scan(src interface{}) error { @@ -453,7 +455,9 @@ func (e APIKeyScope) Valid() bool { ApiKeyScopeBoundaryUsage, ApiKeyScopeBoundaryUsageDelete, ApiKeyScopeBoundaryUsageRead, - ApiKeyScopeBoundaryUsageUpdate: + ApiKeyScopeBoundaryUsageUpdate, + ApiKeyScopeWorkspaceUpdateAgent, + ApiKeyScopeWorkspaceDormantUpdateAgent: return true } return false @@ -659,6 +663,8 @@ func AllAPIKeyScopeValues() []APIKeyScope { ApiKeyScopeBoundaryUsageDelete, ApiKeyScopeBoundaryUsageRead, ApiKeyScopeBoundaryUsageUpdate, + ApiKeyScopeWorkspaceUpdateAgent, + ApiKeyScopeWorkspaceDormantUpdateAgent, } } diff --git a/coderd/database/querier.go b/coderd/database/querier.go index e41ee9bb85..6e4d070851 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -738,6 +738,7 @@ type sqlcQuerier interface { UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (WorkspaceTable, error) UpdateWorkspaceACLByID(ctx context.Context, arg UpdateWorkspaceACLByIDParams) error UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error + UpdateWorkspaceAgentDisplayAppsByID(ctx context.Context, arg UpdateWorkspaceAgentDisplayAppsByIDParams) error UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error UpdateWorkspaceAgentLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentLogOverflowByIDParams) error UpdateWorkspaceAgentMetadata(ctx context.Context, arg UpdateWorkspaceAgentMetadataParams) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 571835ed1d..61388d44fd 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -19306,6 +19306,26 @@ func (q *sqlQuerier) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg return err } +const updateWorkspaceAgentDisplayAppsByID = `-- name: UpdateWorkspaceAgentDisplayAppsByID :exec +UPDATE + workspace_agents +SET + display_apps = $2, updated_at = $3 +WHERE + id = $1 +` + +type UpdateWorkspaceAgentDisplayAppsByIDParams struct { + ID uuid.UUID `db:"id" json:"id"` + DisplayApps []DisplayApp `db:"display_apps" json:"display_apps"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` +} + +func (q *sqlQuerier) UpdateWorkspaceAgentDisplayAppsByID(ctx context.Context, arg UpdateWorkspaceAgentDisplayAppsByIDParams) error { + _, err := q.db.ExecContext(ctx, updateWorkspaceAgentDisplayAppsByID, arg.ID, pq.Array(arg.DisplayApps), arg.UpdatedAt) + return err +} + const updateWorkspaceAgentLifecycleStateByID = `-- name: UpdateWorkspaceAgentLifecycleStateByID :exec UPDATE workspace_agents diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index d4dfa9a7a0..e1cd648dad 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -180,6 +180,14 @@ SET WHERE id = $1; +-- name: UpdateWorkspaceAgentDisplayAppsByID :exec +UPDATE + workspace_agents +SET + display_apps = $2, updated_at = $3 +WHERE + id = $1; + -- name: GetWorkspaceAgentLogsAfter :many SELECT * diff --git a/coderd/rbac/object_gen.go b/coderd/rbac/object_gen.go index 438ab2b069..02d2e9669f 100644 --- a/coderd/rbac/object_gen.go +++ b/coderd/rbac/object_gen.go @@ -370,6 +370,7 @@ var ( // - "ActionWorkspaceStart" :: allows starting a workspace // - "ActionWorkspaceStop" :: allows stopping a workspace // - "ActionUpdate" :: edit workspace settings (scheduling, permissions, parameters) + // - "ActionUpdateAgent" :: update an existing workspace agent ResourceWorkspace = Object{ Type: "workspace", } @@ -403,6 +404,7 @@ var ( // - "ActionWorkspaceStart" :: allows starting a workspace // - "ActionWorkspaceStop" :: allows stopping a workspace // - "ActionUpdate" :: edit workspace settings (scheduling, permissions, parameters) + // - "ActionUpdateAgent" :: update an existing workspace agent ResourceWorkspaceDormant = Object{ Type: "workspace_dormant", } @@ -480,6 +482,7 @@ func AllActions() []policy.Action { policy.ActionShare, policy.ActionUnassign, policy.ActionUpdate, + policy.ActionUpdateAgent, policy.ActionUpdatePersonal, policy.ActionUse, policy.ActionViewInsights, diff --git a/coderd/rbac/policy/policy.go b/coderd/rbac/policy/policy.go index 97b5bbd6e4..48f679874d 100644 --- a/coderd/rbac/policy/policy.go +++ b/coderd/rbac/policy/policy.go @@ -27,6 +27,7 @@ const ( ActionCreateAgent Action = "create_agent" ActionDeleteAgent Action = "delete_agent" + ActionUpdateAgent Action = "update_agent" ActionShare Action = "share" ) @@ -63,6 +64,7 @@ var workspaceActions = map[Action]ActionDefinition{ ActionCreateAgent: "create a new workspace agent", ActionDeleteAgent: "delete an existing workspace agent", + ActionUpdateAgent: "update an existing workspace agent", // Sharing a workspace ActionShare: "share a workspace with other users or groups", diff --git a/coderd/rbac/roles.go b/coderd/rbac/roles.go index 0c7508c64d..2b55f0ad26 100644 --- a/coderd/rbac/roles.go +++ b/coderd/rbac/roles.go @@ -290,7 +290,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) { // This adds back in the Workspace permissions. Permissions(map[string][]policy.Action{ ResourceWorkspace.Type: ownerWorkspaceActions, - ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent}, + ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent}, // PrebuiltWorkspaces are a subset of Workspaces. // Explicitly setting PrebuiltWorkspace permissions for clarity. // Note: even without PrebuiltWorkspace permissions, access is still granted via Workspace permissions. @@ -434,7 +434,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) { // Org admins should not have workspace exec perms. organizationID.String(): { Org: append(allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceAssignRole, ResourceUserSecret, ResourceBoundaryUsage), Permissions(map[string][]policy.Action{ - ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent}, + ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent}, ResourceWorkspace.Type: slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionApplicationConnect, policy.ActionSSH), // PrebuiltWorkspaces are a subset of Workspaces. // Explicitly setting PrebuiltWorkspace permissions for clarity. @@ -972,6 +972,7 @@ func OrgMemberPermissions(workspaceSharingDisabled bool) ( policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent, + policy.ActionUpdateAgent, }, // Can read their own organization member record. ResourceOrganizationMember.Type: { diff --git a/coderd/rbac/roles_test.go b/coderd/rbac/roles_test.go index cad9d548fe..f08930054d 100644 --- a/coderd/rbac/roles_test.go +++ b/coderd/rbac/roles_test.go @@ -294,6 +294,15 @@ func TestRolePermissions(t *testing.T) { false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace}, }, }, + { + Name: "UpdateWorkspaceAgent", + Actions: []policy.Action{policy.ActionUpdateAgent}, + Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()), + AuthorizeMap: map[bool][]hasAuthSubjects{ + true: {owner, orgAdmin, orgAdminBanWorkspace}, + false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor}, + }, + }, { Name: "ShareMyWorkspace", Actions: []policy.Action{policy.ActionShare}, @@ -563,7 +572,7 @@ func TestRolePermissions(t *testing.T) { }, { Name: "WorkspaceDormant", - Actions: append(crud, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent), + Actions: append(crud, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent), Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {orgAdmin, owner}, diff --git a/coderd/rbac/scopes_constants_gen.go b/coderd/rbac/scopes_constants_gen.go index b676b3f0d9..995fe109b4 100644 --- a/coderd/rbac/scopes_constants_gen.go +++ b/coderd/rbac/scopes_constants_gen.go @@ -135,6 +135,7 @@ const ( ScopeWorkspaceStart ScopeName = "workspace:start" ScopeWorkspaceStop ScopeName = "workspace:stop" ScopeWorkspaceUpdate ScopeName = "workspace:update" + ScopeWorkspaceUpdateAgent ScopeName = "workspace:update_agent" ScopeWorkspaceAgentDevcontainersCreate ScopeName = "workspace_agent_devcontainers:create" ScopeWorkspaceAgentResourceMonitorCreate ScopeName = "workspace_agent_resource_monitor:create" ScopeWorkspaceAgentResourceMonitorRead ScopeName = "workspace_agent_resource_monitor:read" @@ -150,6 +151,7 @@ const ( ScopeWorkspaceDormantStart ScopeName = "workspace_dormant:start" ScopeWorkspaceDormantStop ScopeName = "workspace_dormant:stop" ScopeWorkspaceDormantUpdate ScopeName = "workspace_dormant:update" + ScopeWorkspaceDormantUpdateAgent ScopeName = "workspace_dormant:update_agent" ScopeWorkspaceProxyCreate ScopeName = "workspace_proxy:create" ScopeWorkspaceProxyDelete ScopeName = "workspace_proxy:delete" ScopeWorkspaceProxyRead ScopeName = "workspace_proxy:read" @@ -293,6 +295,7 @@ func (e ScopeName) Valid() bool { ScopeWorkspaceStart, ScopeWorkspaceStop, ScopeWorkspaceUpdate, + ScopeWorkspaceUpdateAgent, ScopeWorkspaceAgentDevcontainersCreate, ScopeWorkspaceAgentResourceMonitorCreate, ScopeWorkspaceAgentResourceMonitorRead, @@ -308,6 +311,7 @@ func (e ScopeName) Valid() bool { ScopeWorkspaceDormantStart, ScopeWorkspaceDormantStop, ScopeWorkspaceDormantUpdate, + ScopeWorkspaceDormantUpdateAgent, ScopeWorkspaceProxyCreate, ScopeWorkspaceProxyDelete, ScopeWorkspaceProxyRead, @@ -452,6 +456,7 @@ func AllScopeNameValues() []ScopeName { ScopeWorkspaceStart, ScopeWorkspaceStop, ScopeWorkspaceUpdate, + ScopeWorkspaceUpdateAgent, ScopeWorkspaceAgentDevcontainersCreate, ScopeWorkspaceAgentResourceMonitorCreate, ScopeWorkspaceAgentResourceMonitorRead, @@ -467,6 +472,7 @@ func AllScopeNameValues() []ScopeName { ScopeWorkspaceDormantStart, ScopeWorkspaceDormantStop, ScopeWorkspaceDormantUpdate, + ScopeWorkspaceDormantUpdateAgent, ScopeWorkspaceProxyCreate, ScopeWorkspaceProxyDelete, ScopeWorkspaceProxyRead, diff --git a/codersdk/apikey_scopes_gen.go b/codersdk/apikey_scopes_gen.go index 45b8f01780..0a585bfa53 100644 --- a/codersdk/apikey_scopes_gen.go +++ b/codersdk/apikey_scopes_gen.go @@ -181,6 +181,7 @@ const ( APIKeyScopeWorkspaceStart APIKeyScope = "workspace:start" APIKeyScopeWorkspaceStop APIKeyScope = "workspace:stop" APIKeyScopeWorkspaceUpdate APIKeyScope = "workspace:update" + APIKeyScopeWorkspaceUpdateAgent APIKeyScope = "workspace:update_agent" APIKeyScopeWorkspaceAgentDevcontainersAll APIKeyScope = "workspace_agent_devcontainers:*" APIKeyScopeWorkspaceAgentDevcontainersCreate APIKeyScope = "workspace_agent_devcontainers:create" APIKeyScopeWorkspaceAgentResourceMonitorAll APIKeyScope = "workspace_agent_resource_monitor:*" @@ -199,6 +200,7 @@ const ( APIKeyScopeWorkspaceDormantStart APIKeyScope = "workspace_dormant:start" APIKeyScopeWorkspaceDormantStop APIKeyScope = "workspace_dormant:stop" APIKeyScopeWorkspaceDormantUpdate APIKeyScope = "workspace_dormant:update" + APIKeyScopeWorkspaceDormantUpdateAgent APIKeyScope = "workspace_dormant:update_agent" APIKeyScopeWorkspaceProxyAll APIKeyScope = "workspace_proxy:*" APIKeyScopeWorkspaceProxyCreate APIKeyScope = "workspace_proxy:create" APIKeyScopeWorkspaceProxyDelete APIKeyScope = "workspace_proxy:delete" diff --git a/codersdk/rbacresources_gen.go b/codersdk/rbacresources_gen.go index 6b96928e23..496b66f313 100644 --- a/codersdk/rbacresources_gen.go +++ b/codersdk/rbacresources_gen.go @@ -64,6 +64,7 @@ const ( ActionShare RBACAction = "share" ActionUnassign RBACAction = "unassign" ActionUpdate RBACAction = "update" + ActionUpdateAgent RBACAction = "update_agent" ActionUpdatePersonal RBACAction = "update_personal" ActionUse RBACAction = "use" ActionViewInsights RBACAction = "view_insights" @@ -112,9 +113,9 @@ var RBACResourceActions = map[RBACResource][]RBACAction{ ResourceUser: {ActionCreate, ActionDelete, ActionRead, ActionReadPersonal, ActionUpdate, ActionUpdatePersonal}, ResourceUserSecret: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceWebpushSubscription: {ActionCreate, ActionDelete, ActionRead}, - ResourceWorkspace: {ActionApplicationConnect, ActionCreate, ActionCreateAgent, ActionDelete, ActionDeleteAgent, ActionRead, ActionShare, ActionSSH, ActionWorkspaceStart, ActionWorkspaceStop, ActionUpdate}, + ResourceWorkspace: {ActionApplicationConnect, ActionCreate, ActionCreateAgent, ActionDelete, ActionDeleteAgent, ActionRead, ActionShare, ActionSSH, ActionWorkspaceStart, ActionWorkspaceStop, ActionUpdate, ActionUpdateAgent}, ResourceWorkspaceAgentDevcontainers: {ActionCreate}, ResourceWorkspaceAgentResourceMonitor: {ActionCreate, ActionRead, ActionUpdate}, - ResourceWorkspaceDormant: {ActionApplicationConnect, ActionCreate, ActionCreateAgent, ActionDelete, ActionDeleteAgent, ActionRead, ActionShare, ActionSSH, ActionWorkspaceStart, ActionWorkspaceStop, ActionUpdate}, + ResourceWorkspaceDormant: {ActionApplicationConnect, ActionCreate, ActionCreateAgent, ActionDelete, ActionDeleteAgent, ActionRead, ActionShare, ActionSSH, ActionWorkspaceStart, ActionWorkspaceStop, ActionUpdate, ActionUpdateAgent}, ResourceWorkspaceProxy: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, } diff --git a/docs/reference/api/members.md b/docs/reference/api/members.md index 8704228ddc..f19fb41bf6 100644 --- a/docs/reference/api/members.md +++ b/docs/reference/api/members.md @@ -174,7 +174,7 @@ Status Code **200** | Property | Value(s) | |-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_personal`, `use`, `view_insights` | +| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` | | `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` | To perform this operation, you must be authenticated. [Learn more](authentication.md). @@ -307,7 +307,7 @@ Status Code **200** | Property | Value(s) | |-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_personal`, `use`, `view_insights` | +| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` | | `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` | To perform this operation, you must be authenticated. [Learn more](authentication.md). @@ -440,7 +440,7 @@ Status Code **200** | Property | Value(s) | |-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_personal`, `use`, `view_insights` | +| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` | | `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` | To perform this operation, you must be authenticated. [Learn more](authentication.md). @@ -535,7 +535,7 @@ Status Code **200** | Property | Value(s) | |-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_personal`, `use`, `view_insights` | +| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` | | `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` | To perform this operation, you must be authenticated. [Learn more](authentication.md). @@ -911,7 +911,7 @@ Status Code **200** | Property | Value(s) | |-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_personal`, `use`, `view_insights` | +| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` | | `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` | To perform this operation, you must be authenticated. [Learn more](authentication.md). diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index a766fe2411..02b0efba24 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -862,9 +862,9 @@ #### Enumerated Values -| Value(s) | -|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `aibridge_interception:*`, `aibridge_interception:create`, `aibridge_interception:read`, `aibridge_interception:update`, `all`, `api_key:*`, `api_key:create`, `api_key:delete`, `api_key:read`, `api_key:update`, `application_connect`, `assign_org_role:*`, `assign_org_role:assign`, `assign_org_role:create`, `assign_org_role:delete`, `assign_org_role:read`, `assign_org_role:unassign`, `assign_org_role:update`, `assign_role:*`, `assign_role:assign`, `assign_role:read`, `assign_role:unassign`, `audit_log:*`, `audit_log:create`, `audit_log:read`, `boundary_usage:*`, `boundary_usage:delete`, `boundary_usage:read`, `boundary_usage:update`, `coder:all`, `coder:apikeys.manage_self`, `coder:application_connect`, `coder:templates.author`, `coder:templates.build`, `coder:workspaces.access`, `coder:workspaces.create`, `coder:workspaces.delete`, `coder:workspaces.operate`, `connection_log:*`, `connection_log:read`, `connection_log:update`, `crypto_key:*`, `crypto_key:create`, `crypto_key:delete`, `crypto_key:read`, `crypto_key:update`, `debug_info:*`, `debug_info:read`, `deployment_config:*`, `deployment_config:read`, `deployment_config:update`, `deployment_stats:*`, `deployment_stats:read`, `file:*`, `file:create`, `file:read`, `group:*`, `group:create`, `group:delete`, `group:read`, `group:update`, `group_member:*`, `group_member:read`, `idpsync_settings:*`, `idpsync_settings:read`, `idpsync_settings:update`, `inbox_notification:*`, `inbox_notification:create`, `inbox_notification:read`, `inbox_notification:update`, `license:*`, `license:create`, `license:delete`, `license:read`, `notification_message:*`, `notification_message:create`, `notification_message:delete`, `notification_message:read`, `notification_message:update`, `notification_preference:*`, `notification_preference:read`, `notification_preference:update`, `notification_template:*`, `notification_template:read`, `notification_template:update`, `oauth2_app:*`, `oauth2_app:create`, `oauth2_app:delete`, `oauth2_app:read`, `oauth2_app:update`, `oauth2_app_code_token:*`, `oauth2_app_code_token:create`, `oauth2_app_code_token:delete`, `oauth2_app_code_token:read`, `oauth2_app_secret:*`, `oauth2_app_secret:create`, `oauth2_app_secret:delete`, `oauth2_app_secret:read`, `oauth2_app_secret:update`, `organization:*`, `organization:create`, `organization:delete`, `organization:read`, `organization:update`, `organization_member:*`, `organization_member:create`, `organization_member:delete`, `organization_member:read`, `organization_member:update`, `prebuilt_workspace:*`, `prebuilt_workspace:delete`, `prebuilt_workspace:update`, `provisioner_daemon:*`, `provisioner_daemon:create`, `provisioner_daemon:delete`, `provisioner_daemon:read`, `provisioner_daemon:update`, `provisioner_jobs:*`, `provisioner_jobs:create`, `provisioner_jobs:read`, `provisioner_jobs:update`, `replicas:*`, `replicas:read`, `system:*`, `system:create`, `system:delete`, `system:read`, `system:update`, `tailnet_coordinator:*`, `tailnet_coordinator:create`, `tailnet_coordinator:delete`, `tailnet_coordinator:read`, `tailnet_coordinator:update`, `task:*`, `task:create`, `task:delete`, `task:read`, `task:update`, `template:*`, `template:create`, `template:delete`, `template:read`, `template:update`, `template:use`, `template:view_insights`, `usage_event:*`, `usage_event:create`, `usage_event:read`, `usage_event:update`, `user:*`, `user:create`, `user:delete`, `user:read`, `user:read_personal`, `user:update`, `user:update_personal`, `user_secret:*`, `user_secret:create`, `user_secret:delete`, `user_secret:read`, `user_secret:update`, `webpush_subscription:*`, `webpush_subscription:create`, `webpush_subscription:delete`, `webpush_subscription:read`, `workspace:*`, `workspace:application_connect`, `workspace:create`, `workspace:create_agent`, `workspace:delete`, `workspace:delete_agent`, `workspace:read`, `workspace:share`, `workspace:ssh`, `workspace:start`, `workspace:stop`, `workspace:update`, `workspace_agent_devcontainers:*`, `workspace_agent_devcontainers:create`, `workspace_agent_resource_monitor:*`, `workspace_agent_resource_monitor:create`, `workspace_agent_resource_monitor:read`, `workspace_agent_resource_monitor:update`, `workspace_dormant:*`, `workspace_dormant:application_connect`, `workspace_dormant:create`, `workspace_dormant:create_agent`, `workspace_dormant:delete`, `workspace_dormant:delete_agent`, `workspace_dormant:read`, `workspace_dormant:share`, `workspace_dormant:ssh`, `workspace_dormant:start`, `workspace_dormant:stop`, `workspace_dormant:update`, `workspace_proxy:*`, `workspace_proxy:create`, `workspace_proxy:delete`, `workspace_proxy:read`, `workspace_proxy:update` | +| Value(s) | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `aibridge_interception:*`, `aibridge_interception:create`, `aibridge_interception:read`, `aibridge_interception:update`, `all`, `api_key:*`, `api_key:create`, `api_key:delete`, `api_key:read`, `api_key:update`, `application_connect`, `assign_org_role:*`, `assign_org_role:assign`, `assign_org_role:create`, `assign_org_role:delete`, `assign_org_role:read`, `assign_org_role:unassign`, `assign_org_role:update`, `assign_role:*`, `assign_role:assign`, `assign_role:read`, `assign_role:unassign`, `audit_log:*`, `audit_log:create`, `audit_log:read`, `boundary_usage:*`, `boundary_usage:delete`, `boundary_usage:read`, `boundary_usage:update`, `coder:all`, `coder:apikeys.manage_self`, `coder:application_connect`, `coder:templates.author`, `coder:templates.build`, `coder:workspaces.access`, `coder:workspaces.create`, `coder:workspaces.delete`, `coder:workspaces.operate`, `connection_log:*`, `connection_log:read`, `connection_log:update`, `crypto_key:*`, `crypto_key:create`, `crypto_key:delete`, `crypto_key:read`, `crypto_key:update`, `debug_info:*`, `debug_info:read`, `deployment_config:*`, `deployment_config:read`, `deployment_config:update`, `deployment_stats:*`, `deployment_stats:read`, `file:*`, `file:create`, `file:read`, `group:*`, `group:create`, `group:delete`, `group:read`, `group:update`, `group_member:*`, `group_member:read`, `idpsync_settings:*`, `idpsync_settings:read`, `idpsync_settings:update`, `inbox_notification:*`, `inbox_notification:create`, `inbox_notification:read`, `inbox_notification:update`, `license:*`, `license:create`, `license:delete`, `license:read`, `notification_message:*`, `notification_message:create`, `notification_message:delete`, `notification_message:read`, `notification_message:update`, `notification_preference:*`, `notification_preference:read`, `notification_preference:update`, `notification_template:*`, `notification_template:read`, `notification_template:update`, `oauth2_app:*`, `oauth2_app:create`, `oauth2_app:delete`, `oauth2_app:read`, `oauth2_app:update`, `oauth2_app_code_token:*`, `oauth2_app_code_token:create`, `oauth2_app_code_token:delete`, `oauth2_app_code_token:read`, `oauth2_app_secret:*`, `oauth2_app_secret:create`, `oauth2_app_secret:delete`, `oauth2_app_secret:read`, `oauth2_app_secret:update`, `organization:*`, `organization:create`, `organization:delete`, `organization:read`, `organization:update`, `organization_member:*`, `organization_member:create`, `organization_member:delete`, `organization_member:read`, `organization_member:update`, `prebuilt_workspace:*`, `prebuilt_workspace:delete`, `prebuilt_workspace:update`, `provisioner_daemon:*`, `provisioner_daemon:create`, `provisioner_daemon:delete`, `provisioner_daemon:read`, `provisioner_daemon:update`, `provisioner_jobs:*`, `provisioner_jobs:create`, `provisioner_jobs:read`, `provisioner_jobs:update`, `replicas:*`, `replicas:read`, `system:*`, `system:create`, `system:delete`, `system:read`, `system:update`, `tailnet_coordinator:*`, `tailnet_coordinator:create`, `tailnet_coordinator:delete`, `tailnet_coordinator:read`, `tailnet_coordinator:update`, `task:*`, `task:create`, `task:delete`, `task:read`, `task:update`, `template:*`, `template:create`, `template:delete`, `template:read`, `template:update`, `template:use`, `template:view_insights`, `usage_event:*`, `usage_event:create`, `usage_event:read`, `usage_event:update`, `user:*`, `user:create`, `user:delete`, `user:read`, `user:read_personal`, `user:update`, `user:update_personal`, `user_secret:*`, `user_secret:create`, `user_secret:delete`, `user_secret:read`, `user_secret:update`, `webpush_subscription:*`, `webpush_subscription:create`, `webpush_subscription:delete`, `webpush_subscription:read`, `workspace:*`, `workspace:application_connect`, `workspace:create`, `workspace:create_agent`, `workspace:delete`, `workspace:delete_agent`, `workspace:read`, `workspace:share`, `workspace:ssh`, `workspace:start`, `workspace:stop`, `workspace:update`, `workspace:update_agent`, `workspace_agent_devcontainers:*`, `workspace_agent_devcontainers:create`, `workspace_agent_resource_monitor:*`, `workspace_agent_resource_monitor:create`, `workspace_agent_resource_monitor:read`, `workspace_agent_resource_monitor:update`, `workspace_dormant:*`, `workspace_dormant:application_connect`, `workspace_dormant:create`, `workspace_dormant:create_agent`, `workspace_dormant:delete`, `workspace_dormant:delete_agent`, `workspace_dormant:read`, `workspace_dormant:share`, `workspace_dormant:ssh`, `workspace_dormant:start`, `workspace_dormant:stop`, `workspace_dormant:update`, `workspace_dormant:update_agent`, `workspace_proxy:*`, `workspace_proxy:create`, `workspace_proxy:delete`, `workspace_proxy:read`, `workspace_proxy:update` | ## codersdk.AddLicenseRequest @@ -7046,9 +7046,9 @@ Only certain features set these fields: - FeatureManagedAgentLimit| #### Enumerated Values -| Value(s) | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_personal`, `use`, `view_insights` | +| Value(s) | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` | ## codersdk.RBACResource diff --git a/site/src/api/rbacresourcesGenerated.ts b/site/src/api/rbacresourcesGenerated.ts index 8429003f3d..25a10286f4 100644 --- a/site/src/api/rbacresourcesGenerated.ts +++ b/site/src/api/rbacresourcesGenerated.ts @@ -211,6 +211,7 @@ export const RBACResourceActions: Partial< start: "allows starting a workspace", stop: "allows stopping a workspace", update: "edit workspace settings (scheduling, permissions, parameters)", + update_agent: "update an existing workspace agent", }, workspace_agent_devcontainers: { create: "create workspace agent devcontainers", @@ -232,6 +233,7 @@ export const RBACResourceActions: Partial< start: "allows starting a workspace", stop: "allows stopping a workspace", update: "edit workspace settings (scheduling, permissions, parameters)", + update_agent: "update an existing workspace agent", }, workspace_proxy: { create: "create a workspace proxy", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 6221efa149..57ec4d285e 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -353,6 +353,7 @@ export type APIKeyScope = | "workspace_dormant:start" | "workspace_dormant:stop" | "workspace_dormant:update" + | "workspace_dormant:update_agent" | "workspace_proxy:*" | "workspace_proxy:create" | "workspace_proxy:delete" @@ -363,7 +364,8 @@ export type APIKeyScope = | "workspace:ssh" | "workspace:start" | "workspace:stop" - | "workspace:update"; + | "workspace:update" + | "workspace:update_agent"; export const APIKeyScopes: APIKeyScope[] = [ "aibridge_interception:*", @@ -555,6 +557,7 @@ export const APIKeyScopes: APIKeyScope[] = [ "workspace_dormant:start", "workspace_dormant:stop", "workspace_dormant:update", + "workspace_dormant:update_agent", "workspace_proxy:*", "workspace_proxy:create", "workspace_proxy:delete", @@ -566,6 +569,7 @@ export const APIKeyScopes: APIKeyScope[] = [ "workspace:start", "workspace:stop", "workspace:update", + "workspace:update_agent", ]; // From codersdk/apikey.go @@ -4032,6 +4036,7 @@ export type RBACAction = | "share" | "unassign" | "update" + | "update_agent" | "update_personal" | "use" | "view_insights" @@ -4051,6 +4056,7 @@ export const RBACActions: RBACAction[] = [ "share", "unassign", "update", + "update_agent", "update_personal", "use", "view_insights",