From d17dd5d7879f44518f9f0ce1ff2bd462e25d5ddc Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Mon, 6 Oct 2025 10:56:43 +0200 Subject: [PATCH] feat: add filtering by initiator to provisioner job listing in the CLI (#20137) Relates to https://github.com/coder/internal/issues/934 This PR provides a mechanism to filter provisioner jobs according to who initiated the job. This will be used to find pending prebuild jobs when prebuilds have overwhelmed the provisioner job queue. They can then be canceled. If prebuilds are overwhelming provisioners, the following steps will be taken: ```bash # pause prebuild reconciliation to limit provisioner queue pollution: coder prebuilds pause # cancel pending provisioner jobs to clear the queue coder provisioner jobs list --initiator="prebuilds" --status="pending" | jq ... | xargs -n1 -I{} coder provisioner jobs cancel {} # push a fixed template and wait for the import to complete coder templates push ... # push a fixed template # resume prebuild reconciliation coder prebuilds resume ``` This interface differs somewhat from what was specified in the issue, but still provides a mechanism that addresses the issue. The original proposal was made by myself and this simpler implementation makes sense. I might add a `--search` parameter in a follow-up if there is appetite for it. Potential follow ups: * Support for this usage: `coder provisioner jobs list --search "initiator:prebuilds status:pending"` * Adding the same parameters to `coder provisioner jobs cancel` as a convenience feature so that operators don't have to pipe through `jq` and `xargs` --- cli/provisionerjobs.go | 27 ++- cli/provisionerjobs_test.go | 192 +++++++++++++++--- cli/testdata/coder_list_--output_json.golden | 1 + .../coder_provisioner_jobs_list_--help.golden | 5 +- ...provisioner_jobs_list_--output_json.golden | 2 + coderd/apidoc/docs.go | 11 + coderd/apidoc/swagger.json | 11 + coderd/database/dbauthz/dbauthz_test.go | 2 + coderd/database/queries.sql.go | 5 +- coderd/database/queries/provisionerjobs.sql | 1 + coderd/provisionerjobs.go | 4 + coderd/provisionerjobs_test.go | 102 ++++++++++ codersdk/organizations.go | 12 +- codersdk/provisionerdaemons.go | 1 + docs/reference/api/builds.md | 6 + docs/reference/api/organizations.md | 4 + docs/reference/api/schemas.md | 6 + docs/reference/api/templates.md | 11 + docs/reference/api/workspaces.md | 6 + docs/reference/cli/provisioner_jobs_list.md | 17 +- .../coder_provisioner_jobs_list_--help.golden | 5 +- site/src/api/typesGenerated.ts | 2 + site/src/testHelpers/entities.ts | 1 + 23 files changed, 395 insertions(+), 39 deletions(-) diff --git a/cli/provisionerjobs.go b/cli/provisionerjobs.go index 3ce7da20b7..3f441a1758 100644 --- a/cli/provisionerjobs.go +++ b/cli/provisionerjobs.go @@ -43,8 +43,9 @@ func (r *RootCmd) provisionerJobsList() *serpent.Command { cliui.TableFormat([]provisionerJobRow{}, []string{"created at", "id", "type", "template display name", "status", "queue", "tags"}), cliui.JSONFormat(), ) - status []string - limit int64 + status []string + limit int64 + initiator string ) cmd := &serpent.Command{ @@ -65,9 +66,20 @@ func (r *RootCmd) provisionerJobsList() *serpent.Command { return xerrors.Errorf("current organization: %w", err) } + var initiatorID *uuid.UUID + + if initiator != "" { + user, err := client.User(ctx, initiator) + if err != nil { + return xerrors.Errorf("initiator not found: %s", initiator) + } + initiatorID = &user.ID + } + jobs, err := client.OrganizationProvisionerJobs(ctx, org.ID, &codersdk.OrganizationProvisionerJobsOptions{ - Status: slice.StringEnums[codersdk.ProvisionerJobStatus](status), - Limit: int(limit), + Status: slice.StringEnums[codersdk.ProvisionerJobStatus](status), + Limit: int(limit), + InitiatorID: initiatorID, }) if err != nil { return xerrors.Errorf("list provisioner jobs: %w", err) @@ -122,6 +134,13 @@ func (r *RootCmd) provisionerJobsList() *serpent.Command { Default: "50", Value: serpent.Int64Of(&limit), }, + { + Flag: "initiator", + FlagShorthand: "i", + Env: "CODER_PROVISIONER_JOB_LIST_INITIATOR", + Description: "Filter by initiator (user ID or username).", + Value: serpent.StringOf(&initiator), + }, }...) orgContext.AttachOptions(cmd) diff --git a/cli/provisionerjobs_test.go b/cli/provisionerjobs_test.go index 4db42e8e3c..57072a6156 100644 --- a/cli/provisionerjobs_test.go +++ b/cli/provisionerjobs_test.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/json" "fmt" + "strings" "testing" "time" @@ -26,33 +27,32 @@ import ( func TestProvisionerJobs(t *testing.T) { t.Parallel() - db, ps := dbtestutil.NewDB(t) - client, _, coderdAPI := coderdtest.NewWithAPI(t, &coderdtest.Options{ - IncludeProvisionerDaemon: false, - Database: db, - Pubsub: ps, - }) - owner := coderdtest.CreateFirstUser(t, client) - templateAdminClient, templateAdmin := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgTemplateAdmin(owner.OrganizationID)) - memberClient, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - - // These CLI tests are related to provisioner job CRUD operations and as such - // do not require the overhead of starting a provisioner. Other provisioner job - // functionalities (acquisition etc.) are tested elsewhere. - template := dbgen.Template(t, db, database.Template{ - OrganizationID: owner.OrganizationID, - CreatedBy: owner.UserID, - AllowUserCancelWorkspaceJobs: true, - }) - version := dbgen.TemplateVersion(t, db, database.TemplateVersion{ - OrganizationID: owner.OrganizationID, - CreatedBy: owner.UserID, - TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true}, - }) - t.Run("Cancel", func(t *testing.T) { t.Parallel() + db, ps := dbtestutil.NewDB(t) + client, _, coderdAPI := coderdtest.NewWithAPI(t, &coderdtest.Options{ + IncludeProvisionerDaemon: false, + Database: db, + Pubsub: ps, + }) + owner := coderdtest.CreateFirstUser(t, client) + templateAdminClient, templateAdmin := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgTemplateAdmin(owner.OrganizationID)) + memberClient, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + + // These CLI tests are related to provisioner job CRUD operations and as such + // do not require the overhead of starting a provisioner. Other provisioner job + // functionalities (acquisition etc.) are tested elsewhere. + template := dbgen.Template(t, db, database.Template{ + OrganizationID: owner.OrganizationID, + CreatedBy: owner.UserID, + AllowUserCancelWorkspaceJobs: true, + }) + version := dbgen.TemplateVersion(t, db, database.TemplateVersion{ + OrganizationID: owner.OrganizationID, + CreatedBy: owner.UserID, + TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true}, + }) // Test helper to create a provisioner job of a given type with a given input. prepareJob := func(t *testing.T, jobType database.ProvisionerJobType, input json.RawMessage) database.ProvisionerJob { t.Helper() @@ -178,4 +178,148 @@ func TestProvisionerJobs(t *testing.T) { }) } }) + + t.Run("List", func(t *testing.T) { + t.Parallel() + + db, ps := dbtestutil.NewDB(t) + client, _, coderdAPI := coderdtest.NewWithAPI(t, &coderdtest.Options{ + IncludeProvisionerDaemon: false, + Database: db, + Pubsub: ps, + }) + owner := coderdtest.CreateFirstUser(t, client) + _, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + + // These CLI tests are related to provisioner job CRUD operations and as such + // do not require the overhead of starting a provisioner. Other provisioner job + // functionalities (acquisition etc.) are tested elsewhere. + template := dbgen.Template(t, db, database.Template{ + OrganizationID: owner.OrganizationID, + CreatedBy: owner.UserID, + AllowUserCancelWorkspaceJobs: true, + }) + version := dbgen.TemplateVersion(t, db, database.TemplateVersion{ + OrganizationID: owner.OrganizationID, + CreatedBy: owner.UserID, + TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true}, + }) + // Create some test jobs + job1 := dbgen.ProvisionerJob(t, db, coderdAPI.Pubsub, database.ProvisionerJob{ + OrganizationID: owner.OrganizationID, + InitiatorID: owner.UserID, + Type: database.ProvisionerJobTypeTemplateVersionImport, + Input: []byte(`{"template_version_id":"` + version.ID.String() + `"}`), + Tags: database.StringMap{provisionersdk.TagScope: provisionersdk.ScopeOrganization}, + }) + + job2 := dbgen.ProvisionerJob(t, db, coderdAPI.Pubsub, database.ProvisionerJob{ + OrganizationID: owner.OrganizationID, + InitiatorID: member.ID, + Type: database.ProvisionerJobTypeWorkspaceBuild, + Input: []byte(`{"workspace_build_id":"` + uuid.New().String() + `"}`), + Tags: database.StringMap{provisionersdk.TagScope: provisionersdk.ScopeOrganization}, + }) + // Test basic list command + t.Run("Basic", func(t *testing.T) { + t.Parallel() + + inv, root := clitest.New(t, "provisioner", "jobs", "list") + clitest.SetupConfig(t, client, root) + var buf bytes.Buffer + inv.Stdout = &buf + err := inv.Run() + require.NoError(t, err) + + // Should contain both jobs + output := buf.String() + assert.Contains(t, output, job1.ID.String()) + assert.Contains(t, output, job2.ID.String()) + }) + + // Test list with JSON output + t.Run("JSON", func(t *testing.T) { + t.Parallel() + + inv, root := clitest.New(t, "provisioner", "jobs", "list", "--output", "json") + clitest.SetupConfig(t, client, root) + var buf bytes.Buffer + inv.Stdout = &buf + err := inv.Run() + require.NoError(t, err) + + // Parse JSON output + var jobs []codersdk.ProvisionerJob + err = json.Unmarshal(buf.Bytes(), &jobs) + require.NoError(t, err) + + // Should contain both jobs + jobIDs := make([]uuid.UUID, len(jobs)) + for i, job := range jobs { + jobIDs[i] = job.ID + } + assert.Contains(t, jobIDs, job1.ID) + assert.Contains(t, jobIDs, job2.ID) + }) + + // Test list with limit + t.Run("Limit", func(t *testing.T) { + t.Parallel() + + inv, root := clitest.New(t, "provisioner", "jobs", "list", "--limit", "1") + clitest.SetupConfig(t, client, root) + var buf bytes.Buffer + inv.Stdout = &buf + err := inv.Run() + require.NoError(t, err) + + // Should contain at most 1 job + output := buf.String() + jobCount := 0 + if strings.Contains(output, job1.ID.String()) { + jobCount++ + } + if strings.Contains(output, job2.ID.String()) { + jobCount++ + } + assert.LessOrEqual(t, jobCount, 1) + }) + + // Test list with initiator filter + t.Run("InitiatorFilter", func(t *testing.T) { + t.Parallel() + + // Get owner user details to access username + ctx := testutil.Context(t, testutil.WaitShort) + ownerUser, err := client.User(ctx, owner.UserID.String()) + require.NoError(t, err) + + // Test filtering by initiator (using username) + inv, root := clitest.New(t, "provisioner", "jobs", "list", "--initiator", ownerUser.Username) + clitest.SetupConfig(t, client, root) + var buf bytes.Buffer + inv.Stdout = &buf + err = inv.Run() + require.NoError(t, err) + + // Should only contain job1 (initiated by owner) + output := buf.String() + assert.Contains(t, output, job1.ID.String()) + assert.NotContains(t, output, job2.ID.String()) + }) + + // Test list with invalid user + t.Run("InvalidUser", func(t *testing.T) { + t.Parallel() + + // Test with non-existent user + inv, root := clitest.New(t, "provisioner", "jobs", "list", "--initiator", "nonexistent-user") + clitest.SetupConfig(t, client, root) + var buf bytes.Buffer + inv.Stdout = &buf + err := inv.Run() + require.Error(t, err) + assert.Contains(t, err.Error(), "initiator not found: nonexistent-user") + }) + }) } diff --git a/cli/testdata/coder_list_--output_json.golden b/cli/testdata/coder_list_--output_json.golden index 82b73f7b24..66afcf563d 100644 --- a/cli/testdata/coder_list_--output_json.golden +++ b/cli/testdata/coder_list_--output_json.golden @@ -45,6 +45,7 @@ "queue_position": 0, "queue_size": 0, "organization_id": "===========[first org ID]===========", + "initiator_id": "==========[first user ID]===========", "input": { "workspace_build_id": "========[workspace build ID]========" }, diff --git a/cli/testdata/coder_provisioner_jobs_list_--help.golden b/cli/testdata/coder_provisioner_jobs_list_--help.golden index 8e22f78e97..3a581bd880 100644 --- a/cli/testdata/coder_provisioner_jobs_list_--help.golden +++ b/cli/testdata/coder_provisioner_jobs_list_--help.golden @@ -11,9 +11,12 @@ OPTIONS: -O, --org string, $CODER_ORGANIZATION Select which organization (uuid or name) to use. - -c, --column [id|created at|started at|completed at|canceled at|error|error code|status|worker id|worker name|file id|tags|queue position|queue size|organization id|template version id|workspace build id|type|available workers|template version name|template id|template name|template display name|template icon|workspace id|workspace name|logs overflowed|organization|queue] (default: created at,id,type,template display name,status,queue,tags) + -c, --column [id|created at|started at|completed at|canceled at|error|error code|status|worker id|worker name|file id|tags|queue position|queue size|organization id|initiator id|template version id|workspace build id|type|available workers|template version name|template id|template name|template display name|template icon|workspace id|workspace name|logs overflowed|organization|queue] (default: created at,id,type,template display name,status,queue,tags) Columns to display in table output. + -i, --initiator string, $CODER_PROVISIONER_JOB_LIST_INITIATOR + Filter by initiator (user ID or username). + -l, --limit int, $CODER_PROVISIONER_JOB_LIST_LIMIT (default: 50) Limit the number of jobs returned. diff --git a/cli/testdata/coder_provisioner_jobs_list_--output_json.golden b/cli/testdata/coder_provisioner_jobs_list_--output_json.golden index 6ccf672360..3ee6c25e34 100644 --- a/cli/testdata/coder_provisioner_jobs_list_--output_json.golden +++ b/cli/testdata/coder_provisioner_jobs_list_--output_json.golden @@ -15,6 +15,7 @@ "queue_position": 0, "queue_size": 0, "organization_id": "===========[first org ID]===========", + "initiator_id": "==========[first user ID]===========", "input": { "template_version_id": "============[version ID]============" }, @@ -45,6 +46,7 @@ "queue_position": 0, "queue_size": 0, "organization_id": "===========[first org ID]===========", + "initiator_id": "==========[first user ID]===========", "input": { "workspace_build_id": "========[workspace build ID]========" }, diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index b93c647dad..289e4c9a3f 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -3744,6 +3744,13 @@ const docTemplate = `{ "description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})", "name": "tags", "in": "query" + }, + { + "type": "string", + "format": "uuid", + "description": "Filter results by initiator", + "name": "initiator", + "in": "query" } ], "responses": { @@ -15974,6 +15981,10 @@ const docTemplate = `{ "type": "string", "format": "uuid" }, + "initiator_id": { + "type": "string", + "format": "uuid" + }, "input": { "$ref": "#/definitions/codersdk.ProvisionerJobInput" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 731e1720c0..4b3d4c86aa 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -3299,6 +3299,13 @@ "description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})", "name": "tags", "in": "query" + }, + { + "type": "string", + "format": "uuid", + "description": "Filter results by initiator", + "name": "initiator", + "in": "query" } ], "responses": { @@ -14532,6 +14539,10 @@ "type": "string", "format": "uuid" }, + "initiator_id": { + "type": "string", + "format": "uuid" + }, "input": { "$ref": "#/definitions/codersdk.ProvisionerJobInput" }, diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 730d5f3198..39350ad948 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -2484,10 +2484,12 @@ func (s *MethodTestSuite) TestExtraMethods() { ds, err := db.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner(context.Background(), database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams{ OrganizationID: org.ID, + InitiatorID: uuid.Nil, }) s.NoError(err, "get provisioner jobs by org") check.Args(database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams{ OrganizationID: org.ID, + InitiatorID: uuid.Nil, }).Asserts(j1, policy.ActionRead, j2, policy.ActionRead).Returns(ds) })) } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 6bf1a3e25d..3612375b8b 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -9834,6 +9834,7 @@ WHERE AND (COALESCE(array_length($2::uuid[], 1), 0) = 0 OR pj.id = ANY($2::uuid[])) AND (COALESCE(array_length($3::provisioner_job_status[], 1), 0) = 0 OR pj.job_status = ANY($3::provisioner_job_status[])) AND ($4::tagset = 'null'::tagset OR provisioner_tagset_contains(pj.tags::tagset, $4::tagset)) + AND ($5::uuid = '00000000-0000-0000-0000-000000000000'::uuid OR pj.initiator_id = $5::uuid) GROUP BY pj.id, qp.queue_position, @@ -9849,7 +9850,7 @@ GROUP BY ORDER BY pj.created_at DESC LIMIT - $5::int + $6::int ` type GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams struct { @@ -9857,6 +9858,7 @@ type GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerPar IDs []uuid.UUID `db:"ids" json:"ids"` Status []ProvisionerJobStatus `db:"status" json:"status"` Tags StringMap `db:"tags" json:"tags"` + InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` Limit sql.NullInt32 `db:"limit" json:"limit"` } @@ -9881,6 +9883,7 @@ func (q *sqlQuerier) GetProvisionerJobsByOrganizationAndStatusWithQueuePositionA pq.Array(arg.IDs), pq.Array(arg.Status), arg.Tags, + arg.InitiatorID, arg.Limit, ) if err != nil { diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index dfc95a0bb4..02d67d628a 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -224,6 +224,7 @@ WHERE AND (COALESCE(array_length(@ids::uuid[], 1), 0) = 0 OR pj.id = ANY(@ids::uuid[])) AND (COALESCE(array_length(@status::provisioner_job_status[], 1), 0) = 0 OR pj.job_status = ANY(@status::provisioner_job_status[])) AND (@tags::tagset = 'null'::tagset OR provisioner_tagset_contains(pj.tags::tagset, @tags::tagset)) + AND (@initiator_id::uuid = '00000000-0000-0000-0000-000000000000'::uuid OR pj.initiator_id = @initiator_id::uuid) GROUP BY pj.id, qp.queue_position, diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index e9ab526098..4ba923dae2 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -76,6 +76,7 @@ func (api *API) provisionerJob(rw http.ResponseWriter, r *http.Request) { // @Param ids query []string false "Filter results by job IDs" format(uuid) // @Param status query codersdk.ProvisionerJobStatus false "Filter results by status" enums(pending,running,succeeded,canceling,canceled,failed) // @Param tags query object false "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})" +// @Param initiator query string false "Filter results by initiator" format(uuid) // @Success 200 {array} codersdk.ProvisionerJob // @Router /organizations/{organization}/provisionerjobs [get] func (api *API) provisionerJobs(rw http.ResponseWriter, r *http.Request) { @@ -110,6 +111,7 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt ids = p.UUIDs(qp, nil, "ids") } tags := p.JSONStringMap(qp, database.StringMap{}, "tags") + initiatorID := p.UUID(qp, uuid.Nil, "initiator_id") p.ErrorExcessParams(qp) if len(p.Errors) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ @@ -125,6 +127,7 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt Limit: sql.NullInt32{Int32: limit, Valid: limit > 0}, IDs: ids, Tags: tags, + InitiatorID: initiatorID, }) if err != nil { if httpapi.Is404Error(err) { @@ -355,6 +358,7 @@ func convertProvisionerJob(pj database.GetProvisionerJobsByIDsWithQueuePositionR job := codersdk.ProvisionerJob{ ID: provisionerJob.ID, OrganizationID: provisionerJob.OrganizationID, + InitiatorID: provisionerJob.InitiatorID, CreatedAt: provisionerJob.CreatedAt, Type: codersdk.ProvisionerJobType(provisionerJob.Type), Error: provisionerJob.Error.String, diff --git a/coderd/provisionerjobs_test.go b/coderd/provisionerjobs_test.go index 98da3ae558..829c72aa4d 100644 --- a/coderd/provisionerjobs_test.go +++ b/coderd/provisionerjobs_test.go @@ -58,6 +58,8 @@ func TestProvisionerJobs(t *testing.T) { StartedAt: sql.NullTime{Time: dbtime.Now(), Valid: true}, Type: database.ProvisionerJobTypeWorkspaceBuild, Input: json.RawMessage(`{"workspace_build_id":"` + wbID.String() + `"}`), + InitiatorID: member.ID, + Tags: database.StringMap{"initiatorTest": "true"}, }) dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{ ID: wbID, @@ -71,6 +73,7 @@ func TestProvisionerJobs(t *testing.T) { dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{ OrganizationID: owner.OrganizationID, Tags: database.StringMap{"count": strconv.Itoa(i)}, + InitiatorID: owner.UserID, }) } @@ -165,6 +168,94 @@ func TestProvisionerJobs(t *testing.T) { require.Len(t, jobs, 1) }) + t.Run("Initiator", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + + jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{ + InitiatorID: &member.ID, + }) + require.NoError(t, err) + require.GreaterOrEqual(t, len(jobs), 1) + require.Equal(t, member.ID, jobs[0].InitiatorID) + }) + + t.Run("InitiatorWithOtherFilters", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + + // Test filtering by initiator ID combined with status filter + jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{ + InitiatorID: &owner.UserID, + Status: []codersdk.ProvisionerJobStatus{codersdk.ProvisionerJobSucceeded}, + }) + require.NoError(t, err) + + // Verify all returned jobs have the correct initiator and status + for _, job := range jobs { + require.Equal(t, owner.UserID, job.InitiatorID) + require.Equal(t, codersdk.ProvisionerJobSucceeded, job.Status) + } + }) + + t.Run("InitiatorWithLimit", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + + // Test filtering by initiator ID with limit + jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{ + InitiatorID: &owner.UserID, + Limit: 1, + }) + require.NoError(t, err) + require.Len(t, jobs, 1) + + // Verify the returned job has the correct initiator + require.Equal(t, owner.UserID, jobs[0].InitiatorID) + }) + + t.Run("InitiatorWithTags", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + + // Test filtering by initiator ID combined with tags + jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{ + InitiatorID: &member.ID, + Tags: map[string]string{"initiatorTest": "true"}, + }) + require.NoError(t, err) + require.Len(t, jobs, 1) + + // Verify the returned job has the correct initiator and tags + require.Equal(t, member.ID, jobs[0].InitiatorID) + require.Equal(t, "true", jobs[0].Tags["initiatorTest"]) + }) + + t.Run("InitiatorNotFound", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + + // Test with non-existent initiator ID + nonExistentID := uuid.New() + jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{ + InitiatorID: &nonExistentID, + }) + require.NoError(t, err) + require.Len(t, jobs, 0) + }) + + t.Run("InitiatorNil", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + + // Test with nil initiator ID (should return all jobs) + jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{ + InitiatorID: nil, + }) + require.NoError(t, err) + require.GreaterOrEqual(t, len(jobs), 50) // Should return all jobs (up to default limit) + }) + t.Run("Limit", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitMedium) @@ -185,6 +276,17 @@ func TestProvisionerJobs(t *testing.T) { require.Error(t, err) require.Len(t, jobs, 0) }) + + t.Run("MemberDeniedWithInitiator", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + // Member should not be able to access jobs even with initiator filter + jobs, err := memberClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{ + InitiatorID: &member.ID, + }) + require.Error(t, err) + require.Len(t, jobs, 0) + }) }) // Ensures that when a provisioner job is in the succeeded state, diff --git a/codersdk/organizations.go b/codersdk/organizations.go index bca87c7bd4..291bb9ac1b 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -397,10 +397,11 @@ func (c *Client) OrganizationProvisionerDaemons(ctx context.Context, organizatio } type OrganizationProvisionerJobsOptions struct { - Limit int - IDs []uuid.UUID - Status []ProvisionerJobStatus - Tags map[string]string + Limit int + IDs []uuid.UUID + Status []ProvisionerJobStatus + Tags map[string]string + InitiatorID *uuid.UUID } func (c *Client) OrganizationProvisionerJobs(ctx context.Context, organizationID uuid.UUID, opts *OrganizationProvisionerJobsOptions) ([]ProvisionerJob, error) { @@ -422,6 +423,9 @@ func (c *Client) OrganizationProvisionerJobs(ctx context.Context, organizationID } qp.Add("tags", string(tagsRaw)) } + if opts.InitiatorID != nil { + qp.Add("initiator_id", opts.InitiatorID.String()) + } } res, err := c.Request(ctx, http.MethodGet, diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index b3cefa0911..19f8cae546 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -198,6 +198,7 @@ type ProvisionerJob struct { QueuePosition int `json:"queue_position" table:"queue position"` QueueSize int `json:"queue_size" table:"queue size"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid" table:"organization id"` + InitiatorID uuid.UUID `json:"initiator_id" format:"uuid" table:"initiator id"` Input ProvisionerJobInput `json:"input" table:"input,recursive_inline"` Type ProvisionerJobType `json:"type" table:"type"` AvailableWorkers []uuid.UUID `json:"available_workers,omitempty" format:"uuid" table:"available workers"` diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md index 232509052b..41df0b9efa 100644 --- a/docs/reference/api/builds.md +++ b/docs/reference/api/builds.md @@ -48,6 +48,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -288,6 +289,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1019,6 +1021,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1332,6 +1335,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1551,6 +1555,7 @@ Status Code **200** | `»» error_code` | [codersdk.JobErrorCode](schemas.md#codersdkjoberrorcode) | false | | | | `»» file_id` | string(uuid) | false | | | | `»» id` | string(uuid) | false | | | +| `»» initiator_id` | string(uuid) | false | | | | `»» input` | [codersdk.ProvisionerJobInput](schemas.md#codersdkprovisionerjobinput) | false | | | | `»»» error` | string | false | | | | `»»» template_version_id` | string(uuid) | false | | | @@ -1829,6 +1834,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", diff --git a/docs/reference/api/organizations.md b/docs/reference/api/organizations.md index d418a1fcba..ffd6f78405 100644 --- a/docs/reference/api/organizations.md +++ b/docs/reference/api/organizations.md @@ -366,6 +366,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi | `ids` | query | array(uuid) | false | Filter results by job IDs | | `status` | query | string | false | Filter results by status | | `tags` | query | object | false | Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'}) | +| `initiator` | query | string(uuid) | false | Filter results by initiator | #### Enumerated Values @@ -402,6 +403,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -454,6 +456,7 @@ Status Code **200** | `» error_code` | [codersdk.JobErrorCode](schemas.md#codersdkjoberrorcode) | false | | | | `» file_id` | string(uuid) | false | | | | `» id` | string(uuid) | false | | | +| `» initiator_id` | string(uuid) | false | | | | `» input` | [codersdk.ProvisionerJobInput](schemas.md#codersdkprovisionerjobinput) | false | | | | `»» error` | string | false | | | | `»» template_version_id` | string(uuid) | false | | | @@ -531,6 +534,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 98324941a1..33cb280ae1 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -6390,6 +6390,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit| "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -6432,6 +6433,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit| | `error_code` | [codersdk.JobErrorCode](#codersdkjoberrorcode) | false | | | | `file_id` | string | false | | | | `id` | string | false | | | +| `initiator_id` | string | false | | | | `input` | [codersdk.ProvisionerJobInput](#codersdkprovisionerjobinput) | false | | | | `logs_overflowed` | boolean | false | | | | `metadata` | [codersdk.ProvisionerJobMetadata](#codersdkprovisionerjobmetadata) | false | | | @@ -8118,6 +8120,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -9386,6 +9389,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -10553,6 +10557,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -11389,6 +11394,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index efc59cf7b5..9d69949ac7 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -475,6 +475,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -575,6 +576,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -699,6 +701,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1306,6 +1309,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions \ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1382,6 +1386,7 @@ Status Code **200** | `»» error_code` | [codersdk.JobErrorCode](schemas.md#codersdkjoberrorcode) | false | | | | `»» file_id` | string(uuid) | false | | | | `»» id` | string(uuid) | false | | | +| `»» initiator_id` | string(uuid) | false | | | | `»» input` | [codersdk.ProvisionerJobInput](schemas.md#codersdkprovisionerjobinput) | false | | | | `»»» error` | string | false | | | | `»»» template_version_id` | string(uuid) | false | | | @@ -1589,6 +1594,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions/{templ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1665,6 +1671,7 @@ Status Code **200** | `»» error_code` | [codersdk.JobErrorCode](schemas.md#codersdkjoberrorcode) | false | | | | `»» file_id` | string(uuid) | false | | | | `»» id` | string(uuid) | false | | | +| `»» initiator_id` | string(uuid) | false | | | | `»» input` | [codersdk.ProvisionerJobInput](schemas.md#codersdkprovisionerjobinput) | false | | | | `»»» error` | string | false | | | | `»»» template_version_id` | string(uuid) | false | | | @@ -1762,6 +1769,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion} \ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1871,6 +1879,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templateversions/{templateversion} "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -2069,6 +2078,7 @@ curl -X POST http://coder-server:8080/api/v2/templateversions/{templateversion}/ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -2143,6 +2153,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", diff --git a/docs/reference/api/workspaces.md b/docs/reference/api/workspaces.md index 455fefcb57..e2a95613ed 100644 --- a/docs/reference/api/workspaces.md +++ b/docs/reference/api/workspaces.md @@ -103,6 +103,7 @@ of the template will be used. "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -393,6 +394,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -708,6 +710,7 @@ of the template will be used. "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1001,6 +1004,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1275,6 +1279,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", @@ -1824,6 +1829,7 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \ "error_code": "REQUIRED_TEMPLATE_VARIABLES", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", "input": { "error": "string", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", diff --git a/docs/reference/cli/provisioner_jobs_list.md b/docs/reference/cli/provisioner_jobs_list.md index a0bff8554d..0167dd467d 100644 --- a/docs/reference/cli/provisioner_jobs_list.md +++ b/docs/reference/cli/provisioner_jobs_list.md @@ -34,6 +34,15 @@ Filter by job status. Limit the number of jobs returned. +### -i, --initiator + +| | | +|-------------|----------------------------------------------------| +| Type | string | +| Environment | $CODER_PROVISIONER_JOB_LIST_INITIATOR | + +Filter by initiator (user ID or username). + ### -O, --org | | | @@ -45,10 +54,10 @@ Select which organization (uuid or name) to use. ### -c, --column -| | | -|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Type | [id\|created at\|started at\|completed at\|canceled at\|error\|error code\|status\|worker id\|worker name\|file id\|tags\|queue position\|queue size\|organization id\|template version id\|workspace build id\|type\|available workers\|template version name\|template id\|template name\|template display name\|template icon\|workspace id\|workspace name\|logs overflowed\|organization\|queue] | -| Default | created at,id,type,template display name,status,queue,tags | +| | | +|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Type | [id\|created at\|started at\|completed at\|canceled at\|error\|error code\|status\|worker id\|worker name\|file id\|tags\|queue position\|queue size\|organization id\|initiator id\|template version id\|workspace build id\|type\|available workers\|template version name\|template id\|template name\|template display name\|template icon\|workspace id\|workspace name\|logs overflowed\|organization\|queue] | +| Default | created at,id,type,template display name,status,queue,tags | Columns to display in table output. diff --git a/enterprise/cli/testdata/coder_provisioner_jobs_list_--help.golden b/enterprise/cli/testdata/coder_provisioner_jobs_list_--help.golden index 8e22f78e97..3a581bd880 100644 --- a/enterprise/cli/testdata/coder_provisioner_jobs_list_--help.golden +++ b/enterprise/cli/testdata/coder_provisioner_jobs_list_--help.golden @@ -11,9 +11,12 @@ OPTIONS: -O, --org string, $CODER_ORGANIZATION Select which organization (uuid or name) to use. - -c, --column [id|created at|started at|completed at|canceled at|error|error code|status|worker id|worker name|file id|tags|queue position|queue size|organization id|template version id|workspace build id|type|available workers|template version name|template id|template name|template display name|template icon|workspace id|workspace name|logs overflowed|organization|queue] (default: created at,id,type,template display name,status,queue,tags) + -c, --column [id|created at|started at|completed at|canceled at|error|error code|status|worker id|worker name|file id|tags|queue position|queue size|organization id|initiator id|template version id|workspace build id|type|available workers|template version name|template id|template name|template display name|template icon|workspace id|workspace name|logs overflowed|organization|queue] (default: created at,id,type,template display name,status,queue,tags) Columns to display in table output. + -i, --initiator string, $CODER_PROVISIONER_JOB_LIST_INITIATOR + Filter by initiator (user ID or username). + -l, --limit int, $CODER_PROVISIONER_JOB_LIST_LIMIT (default: 50) Limit the number of jobs returned. diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 0519c9c136..e7c612078b 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2060,6 +2060,7 @@ export interface OrganizationProvisionerJobsOptions { readonly IDs: readonly string[]; readonly Status: readonly ProvisionerJobStatus[]; readonly Tags: Record; + readonly InitiatorID: string | null; } // From codersdk/idpsync.go @@ -2379,6 +2380,7 @@ export interface ProvisionerJob { readonly queue_position: number; readonly queue_size: number; readonly organization_id: string; + readonly initiator_id: string; readonly input: ProvisionerJobInput; readonly type: ProvisionerJobType; readonly available_workers?: readonly string[]; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 19c414bdd3..0b8d87f811 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -679,6 +679,7 @@ export const MockProvisionerJob: TypesGen.ProvisionerJob = { status: "succeeded", file_id: MockOrganization.id, completed_at: "2022-05-17T17:39:01.382927298Z", + initiator_id: MockUserMember.id, tags: { scope: "organization", owner: "",