From 56bdea73b80de4cab341b00f26000c2fbce62904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kayla=20=E3=81=AF=E3=81=AA?= Date: Thu, 5 Mar 2026 13:40:53 -0700 Subject: [PATCH] feat: add workspace acls to task rbac objects (#22311) To allow tasks to be shareable, we need to share both the `task` resource and the `workspace` resource, and their sharing state needs to be kept in sync. We've already implemented all of the necessary ACL functionality for workspaces, so we can just sort of proxy those ACLs back to the task as well. --- coderd/database/check_constraint.go | 4 +- coderd/database/dbauthz/dbauthz.go | 14 +- coderd/database/dump.sql | 55 ++++--- ...7_add_workspace_acl_to_tasks_view.down.sql | 145 +++++++++++++++++ ...427_add_workspace_acl_to_tasks_view.up.sql | 151 ++++++++++++++++++ coderd/database/modelmethods.go | 19 ++- coderd/database/models.go | 2 + coderd/database/queries.sql.go | 16 +- coderd/database/sqlc.yaml | 8 + 9 files changed, 365 insertions(+), 49 deletions(-) create mode 100644 coderd/database/migrations/000427_add_workspace_acl_to_tasks_view.down.sql create mode 100644 coderd/database/migrations/000427_add_workspace_acl_to_tasks_view.up.sql diff --git a/coderd/database/check_constraint.go b/coderd/database/check_constraint.go index 8d0b7189e7..80dfd2bce4 100644 --- a/coderd/database/check_constraint.go +++ b/coderd/database/check_constraint.go @@ -17,9 +17,9 @@ const ( CheckMaxLogsLength CheckConstraint = "max_logs_length" // workspace_agents CheckSubsystemsNotNone CheckConstraint = "subsystems_not_none" // workspace_agents CheckWorkspaceBuildsDeadlineBelowMaxDeadline CheckConstraint = "workspace_builds_deadline_below_max_deadline" // workspace_builds + CheckGroupAclIsObject CheckConstraint = "group_acl_is_object" // workspaces + CheckUserAclIsObject CheckConstraint = "user_acl_is_object" // workspaces CheckTelemetryLockEventTypeConstraint CheckConstraint = "telemetry_lock_event_type_constraint" // telemetry_locks CheckValidationMonotonicOrder CheckConstraint = "validation_monotonic_order" // template_version_parameters CheckUsageEventTypeCheck CheckConstraint = "usage_event_type_check" // usage_events - CheckGroupAclIsObject CheckConstraint = "group_acl_is_object" // workspaces - CheckUserAclIsObject CheckConstraint = "user_acl_is_object" // workspaces ) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 1b7d927eaa..839c439cd6 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -3409,12 +3409,7 @@ func (q *querier) GetTaskSnapshot(ctx context.Context, taskID uuid.UUID) (databa return database.TaskSnapshot{}, err } - obj := rbac.ResourceTask. - WithID(task.ID). - WithOwner(task.OwnerID.String()). - InOrg(task.OrganizationID) - - if err := q.authorizeContext(ctx, policy.ActionRead, obj); err != nil { + if err := q.authorizeContext(ctx, policy.ActionRead, task.RBACObject()); err != nil { return database.TaskSnapshot{}, err } @@ -6635,12 +6630,7 @@ func (q *querier) UpsertTaskSnapshot(ctx context.Context, arg database.UpsertTas return err } - obj := rbac.ResourceTask. - WithID(task.ID). - WithOwner(task.OwnerID.String()). - InOrg(task.OrganizationID) - - if err := q.authorizeContext(ctx, policy.ActionUpdate, obj); err != nil { + if err := q.authorizeContext(ctx, policy.ActionUpdate, task.RBACObject()); err != nil { return err } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 878eba3409..bc0c444012 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -2094,6 +2094,31 @@ CREATE TABLE workspace_builds ( CONSTRAINT workspace_builds_deadline_below_max_deadline CHECK ((((deadline <> '0001-01-01 00:00:00+00'::timestamp with time zone) AND (deadline <= max_deadline)) OR (max_deadline = '0001-01-01 00:00:00+00'::timestamp with time zone))) ); +CREATE TABLE workspaces ( + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + owner_id uuid NOT NULL, + organization_id uuid NOT NULL, + template_id uuid NOT NULL, + deleted boolean DEFAULT false NOT NULL, + name character varying(64) NOT NULL, + autostart_schedule text, + ttl bigint, + last_used_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL, + dormant_at timestamp with time zone, + deleting_at timestamp with time zone, + automatic_updates automatic_updates DEFAULT 'never'::automatic_updates NOT NULL, + favorite boolean DEFAULT false NOT NULL, + next_start_at timestamp with time zone, + group_acl jsonb DEFAULT '{}'::jsonb NOT NULL, + user_acl jsonb DEFAULT '{}'::jsonb NOT NULL, + CONSTRAINT group_acl_is_object CHECK ((jsonb_typeof(group_acl) = 'object'::text)), + CONSTRAINT user_acl_is_object CHECK ((jsonb_typeof(user_acl) = 'object'::text)) +); + +COMMENT ON COLUMN workspaces.favorite IS 'Favorite is true if the workspace owner has favorited the workspace.'; + CREATE VIEW tasks_with_status AS SELECT tasks.id, tasks.organization_id, @@ -2106,6 +2131,8 @@ CREATE VIEW tasks_with_status AS tasks.created_at, tasks.deleted_at, tasks.display_name, + COALESCE(workspaces.group_acl, '{}'::jsonb) AS workspace_group_acl, + COALESCE(workspaces.user_acl, '{}'::jsonb) AS workspace_user_acl, CASE WHEN (tasks.workspace_id IS NULL) THEN 'pending'::task_status WHEN (build_status.status <> 'active'::task_status) THEN build_status.status @@ -2121,7 +2148,8 @@ CREATE VIEW tasks_with_status AS task_owner.owner_username, task_owner.owner_name, task_owner.owner_avatar_url - FROM ((((((((tasks + FROM (((((((((tasks + LEFT JOIN workspaces ON ((workspaces.id = tasks.workspace_id))) CROSS JOIN LATERAL ( SELECT vu.username AS owner_username, vu.name AS owner_name, vu.avatar_url AS owner_avatar_url @@ -2864,31 +2892,6 @@ CREATE VIEW workspace_build_with_user AS COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.'; -CREATE TABLE workspaces ( - id uuid NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - owner_id uuid NOT NULL, - organization_id uuid NOT NULL, - template_id uuid NOT NULL, - deleted boolean DEFAULT false NOT NULL, - name character varying(64) NOT NULL, - autostart_schedule text, - ttl bigint, - last_used_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL, - dormant_at timestamp with time zone, - deleting_at timestamp with time zone, - automatic_updates automatic_updates DEFAULT 'never'::automatic_updates NOT NULL, - favorite boolean DEFAULT false NOT NULL, - next_start_at timestamp with time zone, - group_acl jsonb DEFAULT '{}'::jsonb NOT NULL, - user_acl jsonb DEFAULT '{}'::jsonb NOT NULL, - CONSTRAINT group_acl_is_object CHECK ((jsonb_typeof(group_acl) = 'object'::text)), - CONSTRAINT user_acl_is_object CHECK ((jsonb_typeof(user_acl) = 'object'::text)) -); - -COMMENT ON COLUMN workspaces.favorite IS 'Favorite is true if the workspace owner has favorited the workspace.'; - CREATE VIEW workspace_latest_builds AS SELECT latest_build.id, latest_build.workspace_id, diff --git a/coderd/database/migrations/000427_add_workspace_acl_to_tasks_view.down.sql b/coderd/database/migrations/000427_add_workspace_acl_to_tasks_view.down.sql new file mode 100644 index 0000000000..9e0fe06ab0 --- /dev/null +++ b/coderd/database/migrations/000427_add_workspace_acl_to_tasks_view.down.sql @@ -0,0 +1,145 @@ +-- Fix task status logic: pending provisioner job should give pending task status, not initializing. +-- A task is pending when the provisioner hasn't picked up the job yet. +-- A task is initializing when the provisioner is actively running the job. +DROP VIEW IF EXISTS tasks_with_status; + +CREATE VIEW + tasks_with_status +AS + SELECT + tasks.*, + -- Combine component statuses with precedence: build -> agent -> app. + CASE + WHEN tasks.workspace_id IS NULL THEN 'pending'::task_status + WHEN build_status.status != 'active' THEN build_status.status::task_status + WHEN agent_status.status != 'active' THEN agent_status.status::task_status + ELSE app_status.status::task_status + END AS status, + -- Attach debug information for troubleshooting status. + jsonb_build_object( + 'build', jsonb_build_object( + 'transition', latest_build_raw.transition, + 'job_status', latest_build_raw.job_status, + 'computed', build_status.status + ), + 'agent', jsonb_build_object( + 'lifecycle_state', agent_raw.lifecycle_state, + 'computed', agent_status.status + ), + 'app', jsonb_build_object( + 'health', app_raw.health, + 'computed', app_status.status + ) + ) AS status_debug, + task_app.*, + agent_raw.lifecycle_state AS workspace_agent_lifecycle_state, + app_raw.health AS workspace_app_health, + task_owner.* + FROM + tasks + CROSS JOIN LATERAL ( + SELECT + vu.username AS owner_username, + vu.name AS owner_name, + vu.avatar_url AS owner_avatar_url + FROM + visible_users vu + WHERE + vu.id = tasks.owner_id + ) task_owner + LEFT JOIN LATERAL ( + SELECT + task_app.workspace_build_number, + task_app.workspace_agent_id, + task_app.workspace_app_id + FROM + task_workspace_apps task_app + WHERE + task_id = tasks.id + ORDER BY + task_app.workspace_build_number DESC + LIMIT 1 + ) task_app ON TRUE + + -- Join the raw data for computing task status. + LEFT JOIN LATERAL ( + SELECT + workspace_build.transition, + provisioner_job.job_status, + workspace_build.job_id + FROM + workspace_builds workspace_build + JOIN + provisioner_jobs provisioner_job + ON provisioner_job.id = workspace_build.job_id + WHERE + workspace_build.workspace_id = tasks.workspace_id + AND workspace_build.build_number = task_app.workspace_build_number + ) latest_build_raw ON TRUE + LEFT JOIN LATERAL ( + SELECT + workspace_agent.lifecycle_state + FROM + workspace_agents workspace_agent + WHERE + workspace_agent.id = task_app.workspace_agent_id + ) agent_raw ON TRUE + LEFT JOIN LATERAL ( + SELECT + workspace_app.health + FROM + workspace_apps workspace_app + WHERE + workspace_app.id = task_app.workspace_app_id + ) app_raw ON TRUE + + -- Compute the status for each component. + CROSS JOIN LATERAL ( + SELECT + CASE + WHEN latest_build_raw.job_status IS NULL THEN 'pending'::task_status + WHEN latest_build_raw.job_status IN ('failed', 'canceling', 'canceled') THEN 'error'::task_status + WHEN + latest_build_raw.transition IN ('stop', 'delete') + AND latest_build_raw.job_status = 'succeeded' THEN 'paused'::task_status + -- Job is pending (not picked up by provisioner yet). + WHEN + latest_build_raw.transition = 'start' + AND latest_build_raw.job_status = 'pending' THEN 'pending'::task_status + -- Job is running or done, defer to agent/app status. + WHEN + latest_build_raw.transition = 'start' + AND latest_build_raw.job_status IN ('running', 'succeeded') THEN 'active'::task_status + ELSE 'unknown'::task_status + END AS status + ) build_status + CROSS JOIN LATERAL ( + SELECT + CASE + -- No agent or connecting. + WHEN + agent_raw.lifecycle_state IS NULL + OR agent_raw.lifecycle_state IN ('created', 'starting') THEN 'initializing'::task_status + -- Agent is running, defer to app status. + -- NOTE(mafredri): The start_error/start_timeout states means connected, but some startup script failed. + -- This may or may not affect the task status but this has to be caught by app health check. + WHEN agent_raw.lifecycle_state IN ('ready', 'start_timeout', 'start_error') THEN 'active'::task_status + -- If the agent is shutting down or turned off, this is an unknown state because we would expect a stop + -- build to be running. + -- This is essentially equal to: `IN ('shutting_down', 'shutdown_timeout', 'shutdown_error', 'off')`, + -- but we cannot use them because the values were added in a migration. + WHEN agent_raw.lifecycle_state NOT IN ('created', 'starting', 'ready', 'start_timeout', 'start_error') THEN 'unknown'::task_status + ELSE 'unknown'::task_status + END AS status + ) agent_status + CROSS JOIN LATERAL ( + SELECT + CASE + WHEN app_raw.health = 'initializing' THEN 'initializing'::task_status + WHEN app_raw.health = 'unhealthy' THEN 'error'::task_status + WHEN app_raw.health IN ('healthy', 'disabled') THEN 'active'::task_status + ELSE 'unknown'::task_status + END AS status + ) app_status + WHERE + tasks.deleted_at IS NULL; diff --git a/coderd/database/migrations/000427_add_workspace_acl_to_tasks_view.up.sql b/coderd/database/migrations/000427_add_workspace_acl_to_tasks_view.up.sql new file mode 100644 index 0000000000..1b62aad2f7 --- /dev/null +++ b/coderd/database/migrations/000427_add_workspace_acl_to_tasks_view.up.sql @@ -0,0 +1,151 @@ +-- Fix task status logic: pending provisioner job should give pending task status, not initializing. +-- A task is pending when the provisioner hasn't picked up the job yet. +-- A task is initializing when the provisioner is actively running the job. +DROP VIEW IF EXISTS tasks_with_status; + +CREATE VIEW + tasks_with_status +AS + SELECT + tasks.*, + coalesce(workspaces.group_acl, '{}'::jsonb) as workspace_group_acl, + coalesce(workspaces.user_acl, '{}'::jsonb) as workspace_user_acl, + -- Combine component statuses with precedence: build -> agent -> app. + CASE + WHEN tasks.workspace_id IS NULL THEN 'pending'::task_status + WHEN build_status.status != 'active' THEN build_status.status::task_status + WHEN agent_status.status != 'active' THEN agent_status.status::task_status + ELSE app_status.status::task_status + END AS status, + -- Attach debug information for troubleshooting status. + jsonb_build_object( + 'build', jsonb_build_object( + 'transition', latest_build_raw.transition, + 'job_status', latest_build_raw.job_status, + 'computed', build_status.status + ), + 'agent', jsonb_build_object( + 'lifecycle_state', agent_raw.lifecycle_state, + 'computed', agent_status.status + ), + 'app', jsonb_build_object( + 'health', app_raw.health, + 'computed', app_status.status + ) + ) AS status_debug, + task_app.*, + agent_raw.lifecycle_state AS workspace_agent_lifecycle_state, + app_raw.health AS workspace_app_health, + task_owner.* + FROM + tasks + + LEFT JOIN + workspaces ON workspaces.id = tasks.workspace_id + + CROSS JOIN LATERAL ( + SELECT + vu.username AS owner_username, + vu.name AS owner_name, + vu.avatar_url AS owner_avatar_url + FROM + visible_users vu + WHERE + vu.id = tasks.owner_id + ) task_owner + LEFT JOIN LATERAL ( + SELECT + task_app.workspace_build_number, + task_app.workspace_agent_id, + task_app.workspace_app_id + FROM + task_workspace_apps task_app + WHERE + task_id = tasks.id + ORDER BY + task_app.workspace_build_number DESC + LIMIT 1 + ) task_app ON TRUE + + -- Join the raw data for computing task status. + LEFT JOIN LATERAL ( + SELECT + workspace_build.transition, + provisioner_job.job_status, + workspace_build.job_id + FROM + workspace_builds workspace_build + JOIN + provisioner_jobs provisioner_job + ON provisioner_job.id = workspace_build.job_id + WHERE + workspace_build.workspace_id = tasks.workspace_id + AND workspace_build.build_number = task_app.workspace_build_number + ) latest_build_raw ON TRUE + LEFT JOIN LATERAL ( + SELECT + workspace_agent.lifecycle_state + FROM + workspace_agents workspace_agent + WHERE + workspace_agent.id = task_app.workspace_agent_id + ) agent_raw ON TRUE + LEFT JOIN LATERAL ( + SELECT + workspace_app.health + FROM + workspace_apps workspace_app + WHERE + workspace_app.id = task_app.workspace_app_id + ) app_raw ON TRUE + + -- Compute the status for each component. + CROSS JOIN LATERAL ( + SELECT + CASE + WHEN latest_build_raw.job_status IS NULL THEN 'pending'::task_status + WHEN latest_build_raw.job_status IN ('failed', 'canceling', 'canceled') THEN 'error'::task_status + WHEN + latest_build_raw.transition IN ('stop', 'delete') + AND latest_build_raw.job_status = 'succeeded' THEN 'paused'::task_status + -- Job is pending (not picked up by provisioner yet). + WHEN + latest_build_raw.transition = 'start' + AND latest_build_raw.job_status = 'pending' THEN 'pending'::task_status + -- Job is running or done, defer to agent/app status. + WHEN + latest_build_raw.transition = 'start' + AND latest_build_raw.job_status IN ('running', 'succeeded') THEN 'active'::task_status + ELSE 'unknown'::task_status + END AS status + ) build_status + CROSS JOIN LATERAL ( + SELECT + CASE + -- No agent or connecting. + WHEN + agent_raw.lifecycle_state IS NULL + OR agent_raw.lifecycle_state IN ('created', 'starting') THEN 'initializing'::task_status + -- Agent is running, defer to app status. + -- NOTE(mafredri): The start_error/start_timeout states means connected, but some startup script failed. + -- This may or may not affect the task status but this has to be caught by app health check. + WHEN agent_raw.lifecycle_state IN ('ready', 'start_timeout', 'start_error') THEN 'active'::task_status + -- If the agent is shutting down or turned off, this is an unknown state because we would expect a stop + -- build to be running. + -- This is essentially equal to: `IN ('shutting_down', 'shutdown_timeout', 'shutdown_error', 'off')`, + -- but we cannot use them because the values were added in a migration. + WHEN agent_raw.lifecycle_state NOT IN ('created', 'starting', 'ready', 'start_timeout', 'start_error') THEN 'unknown'::task_status + ELSE 'unknown'::task_status + END AS status + ) agent_status + CROSS JOIN LATERAL ( + SELECT + CASE + WHEN app_raw.health = 'initializing' THEN 'initializing'::task_status + WHEN app_raw.health = 'unhealthy' THEN 'error'::task_status + WHEN app_raw.health IN ('healthy', 'disabled') THEN 'active'::task_status + ELSE 'unknown'::task_status + END AS status + ) app_status + WHERE + tasks.deleted_at IS NULL; diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index 8c6d7e0bd1..3408ab20d5 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -155,14 +155,23 @@ func (t Task) TaskTable() TaskTable { } func (t Task) RBACObject() rbac.Object { - return t.TaskTable().RBACObject() -} - -func (t TaskTable) RBACObject() rbac.Object { - return rbac.ResourceTask. + obj := rbac.ResourceTask. WithID(t.ID). WithOwner(t.OwnerID.String()). InOrg(t.OrganizationID) + + if rbac.WorkspaceACLDisabled() { + return obj + } + + if t.WorkspaceGroupACL != nil { + obj = obj.WithGroupACL(t.WorkspaceGroupACL.RBACACL()) + } + if t.WorkspaceUserACL != nil { + obj = obj.WithACLUserList(t.WorkspaceUserACL.RBACACL()) + } + + return obj } func (c Chat) RBACObject() rbac.Object { diff --git a/coderd/database/models.go b/coderd/database/models.go index 20c1d09700..b37ff85d3d 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -4494,6 +4494,8 @@ type Task struct { CreatedAt time.Time `db:"created_at" json:"created_at"` DeletedAt sql.NullTime `db:"deleted_at" json:"deleted_at"` DisplayName string `db:"display_name" json:"display_name"` + WorkspaceGroupACL WorkspaceACL `db:"workspace_group_acl" json:"workspace_group_acl"` + WorkspaceUserACL WorkspaceACL `db:"workspace_user_acl" json:"workspace_user_acl"` Status TaskStatus `db:"status" json:"status"` StatusDebug json.RawMessage `db:"status_debug" json:"status_debug"` WorkspaceBuildNumber sql.NullInt32 `db:"workspace_build_number" json:"workspace_build_number"` diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 088c1cdf12..6805478525 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -15196,7 +15196,7 @@ func (q *sqlQuerier) DeleteTask(ctx context.Context, arg DeleteTaskParams) (uuid } const getTaskByID = `-- name: GetTaskByID :one -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, display_name, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE id = $1::uuid +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, display_name, workspace_group_acl, workspace_user_acl, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE id = $1::uuid ` func (q *sqlQuerier) GetTaskByID(ctx context.Context, id uuid.UUID) (Task, error) { @@ -15214,6 +15214,8 @@ func (q *sqlQuerier) GetTaskByID(ctx context.Context, id uuid.UUID) (Task, error &i.CreatedAt, &i.DeletedAt, &i.DisplayName, + &i.WorkspaceGroupACL, + &i.WorkspaceUserACL, &i.Status, &i.StatusDebug, &i.WorkspaceBuildNumber, @@ -15229,7 +15231,7 @@ func (q *sqlQuerier) GetTaskByID(ctx context.Context, id uuid.UUID) (Task, error } const getTaskByOwnerIDAndName = `-- name: GetTaskByOwnerIDAndName :one -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, display_name, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, display_name, workspace_group_acl, workspace_user_acl, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE owner_id = $1::uuid AND deleted_at IS NULL @@ -15256,6 +15258,8 @@ func (q *sqlQuerier) GetTaskByOwnerIDAndName(ctx context.Context, arg GetTaskByO &i.CreatedAt, &i.DeletedAt, &i.DisplayName, + &i.WorkspaceGroupACL, + &i.WorkspaceUserACL, &i.Status, &i.StatusDebug, &i.WorkspaceBuildNumber, @@ -15271,7 +15275,7 @@ func (q *sqlQuerier) GetTaskByOwnerIDAndName(ctx context.Context, arg GetTaskByO } const getTaskByWorkspaceID = `-- name: GetTaskByWorkspaceID :one -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, display_name, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE workspace_id = $1::uuid +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, display_name, workspace_group_acl, workspace_user_acl, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE workspace_id = $1::uuid ` func (q *sqlQuerier) GetTaskByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (Task, error) { @@ -15289,6 +15293,8 @@ func (q *sqlQuerier) GetTaskByWorkspaceID(ctx context.Context, workspaceID uuid. &i.CreatedAt, &i.DeletedAt, &i.DisplayName, + &i.WorkspaceGroupACL, + &i.WorkspaceUserACL, &i.Status, &i.StatusDebug, &i.WorkspaceBuildNumber, @@ -15568,7 +15574,7 @@ func (q *sqlQuerier) InsertTask(ctx context.Context, arg InsertTaskParams) (Task } const listTasks = `-- name: ListTasks :many -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, display_name, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status tws +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, display_name, workspace_group_acl, workspace_user_acl, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status tws WHERE tws.deleted_at IS NULL AND CASE WHEN $1::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.owner_id = $1::UUID ELSE TRUE END AND CASE WHEN $2::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.organization_id = $2::UUID ELSE TRUE END @@ -15603,6 +15609,8 @@ func (q *sqlQuerier) ListTasks(ctx context.Context, arg ListTasksParams) ([]Task &i.CreatedAt, &i.DeletedAt, &i.DisplayName, + &i.WorkspaceGroupACL, + &i.WorkspaceUserACL, &i.Status, &i.StatusDebug, &i.WorkspaceBuildNumber, diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index 78d20574c6..bc5e216726 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -82,6 +82,12 @@ sql: - column: "template_usage_stats.app_usage_mins" go_type: type: "StringMapOfInt" + - column: "tasks_with_status.workspace_user_acl" + go_type: + type: "WorkspaceACL" + - column: "tasks_with_status.workspace_group_acl" + go_type: + type: "WorkspaceACL" - column: "workspaces.user_acl" go_type: type: "WorkspaceACL" @@ -186,6 +192,8 @@ sql: jwt: JWT user_acl: UserACL group_acl: GroupACL + workspace_user_acl: WorkspaceUserACL + workspace_group_acl: WorkspaceGroupACL user_acl_display_info: UserACLDisplayInfo group_acl_display_info: GroupACLDisplayInfo troubleshooting_url: TroubleshootingURL