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`
This commit is contained in:
Sas Swart
2025-10-06 10:56:43 +02:00
committed by GitHub
parent c1357d4e27
commit d17dd5d787
23 changed files with 395 additions and 39 deletions
+23 -4
View File
@@ -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)
+168 -24
View File
@@ -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")
})
})
}
+1
View File
@@ -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]========"
},
+4 -1
View File
@@ -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.
@@ -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]========"
},
+11
View File
@@ -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"
},
+11
View File
@@ -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"
},
+2
View File
@@ -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)
}))
}
+4 -1
View File
@@ -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 {
@@ -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,
+4
View File
@@ -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,
+102
View File
@@ -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,
+8 -4
View File
@@ -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,
+1
View File
@@ -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"`
+6
View File
@@ -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",
+4
View File
@@ -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",
+6
View File
@@ -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",
+11
View File
@@ -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",
+6
View File
@@ -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",
+13 -4
View File
@@ -34,6 +34,15 @@ Filter by job status.
Limit the number of jobs returned.
### -i, --initiator
| | |
|-------------|----------------------------------------------------|
| Type | <code>string</code> |
| Environment | <code>$CODER_PROVISIONER_JOB_LIST_INITIATOR</code> |
Filter by initiator (user ID or username).
### -O, --org
| | |
@@ -45,10 +54,10 @@ Select which organization (uuid or name) to use.
### -c, --column
| | |
|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Type | <code>[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]</code> |
| Default | <code>created at,id,type,template display name,status,queue,tags</code> |
| | |
|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Type | <code>[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]</code> |
| Default | <code>created at,id,type,template display name,status,queue,tags</code> |
Columns to display in table output.
@@ -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.
+2
View File
@@ -2060,6 +2060,7 @@ export interface OrganizationProvisionerJobsOptions {
readonly IDs: readonly string[];
readonly Status: readonly ProvisionerJobStatus[];
readonly Tags: Record<string, string>;
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[];
+1
View File
@@ -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: "",