feat(coderd/agentapi): support terraform-defined subagent ids (#21837)

Update `coderd/agentapi` to handle pre-created sub agents
This commit is contained in:
Danielle Maywood
2026-02-04 15:33:48 +00:00
committed by GitHub
parent 29b1aea736
commit af0e171595
27 changed files with 432 additions and 40 deletions
+6
View File
@@ -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
+56 -19
View File
@@ -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},
+219
View File
@@ -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()
+6
View File
@@ -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",
+6
View File
@@ -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",
+15 -2
View File
@@ -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 {
+11
View File
@@ -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{})
@@ -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)
+14
View File
@@ -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()
+3 -1
View File
@@ -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 (
@@ -0,0 +1 @@
-- No-op for update agent scopes: keep enum values to avoid dependency churn.
@@ -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';
+7 -1
View File
@@ -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,
}
}
+1
View File
@@ -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
+20
View File
@@ -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
@@ -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
*
+3
View File
@@ -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,
+2
View File
@@ -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",
+3 -2
View File
@@ -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: {
+10 -1
View File
@@ -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},
+6
View File
@@ -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,
+2
View File
@@ -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"
+3 -2
View File
@@ -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},
}
+5 -5
View File
@@ -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).
+6 -6
View File
@@ -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
+2
View File
@@ -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",
+7 -1
View File
@@ -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",