diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b8e73ca7e1e76..6ad6f4e27d82b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -153,7 +153,7 @@ jobs: - name: Install sqlc run: | - curl -sSL https://github.com/kyleconroy/sqlc/releases/download/v1.17.2/sqlc_1.17.2_linux_amd64.tar.gz | sudo tar -C /usr/bin -xz sqlc + curl -sSL https://github.com/kyleconroy/sqlc/releases/download/v1.18.0/sqlc_1.18.0_linux_amd64.tar.gz | sudo tar -C /usr/bin -xz sqlc - name: go install tools run: | diff --git a/cli/testdata/coder_list_--output_json.golden b/cli/testdata/coder_list_--output_json.golden index acf929a70edbb..91f20f1fecff7 100644 --- a/cli/testdata/coder_list_--output_json.golden +++ b/cli/testdata/coder_list_--output_json.golden @@ -35,7 +35,9 @@ "file_id": "[workspace build file ID]", "tags": { "scope": "organization" - } + }, + "queue_position": 0, + "queue_size": 0 }, "reason": "initiator", "resources": [], diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 4ea7036e01129..a3767e335e34e 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -7981,6 +7981,12 @@ const docTemplate = `{ "type": "string", "format": "uuid" }, + "queue_position": { + "type": "integer" + }, + "queue_size": { + "type": "integer" + }, "started_at": { "type": "string", "format": "date-time" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index eea32d65ade16..faf9d09d29a28 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -7159,6 +7159,12 @@ "type": "string", "format": "uuid" }, + "queue_position": { + "type": "integer" + }, + "queue_size": { + "type": "integer" + }, "started_at": { "type": "string", "format": "date-time" diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index dc8b10e5a1f27..ea2539758050a 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1087,6 +1087,11 @@ func (q *querier) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) return q.db.GetProvisionerJobsByIDs(ctx, ids) } +// TODO: we need to add a provisioner job resource +func (q *querier) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]database.GetProvisionerJobsByIDsWithQueuePositionRow, error) { + return q.db.GetProvisionerJobsByIDsWithQueuePosition(ctx, ids) +} + // TODO: We need to create a ProvisionerJob resource type func (q *querier) GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.ProvisionerJob, error) { // if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil { diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index ede8c6cef69d7..1a5e3d01bd3db 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -2051,6 +2051,38 @@ func (q *fakeQuerier) GetProvisionerJobsByIDs(_ context.Context, ids []uuid.UUID return jobs, nil } +func (q *fakeQuerier) GetProvisionerJobsByIDsWithQueuePosition(_ context.Context, ids []uuid.UUID) ([]database.GetProvisionerJobsByIDsWithQueuePositionRow, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + jobs := make([]database.GetProvisionerJobsByIDsWithQueuePositionRow, 0) + queuePosition := int64(1) + for _, job := range q.provisionerJobs { + for _, id := range ids { + if id == job.ID { + job := database.GetProvisionerJobsByIDsWithQueuePositionRow{ + ProvisionerJob: job, + } + if !job.ProvisionerJob.StartedAt.Valid { + job.QueuePosition = queuePosition + } + jobs = append(jobs, job) + break + } + } + if !job.StartedAt.Valid { + queuePosition++ + } + } + for _, job := range jobs { + if !job.ProvisionerJob.StartedAt.Valid { + // Set it to the max position! + job.QueueSize = queuePosition + } + } + return jobs, nil +} + func (q *fakeQuerier) GetProvisionerJobsCreatedAfter(_ context.Context, after time.Time) ([]database.ProvisionerJob, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 416ad3a9a368d..9870e767803cd 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -507,6 +507,13 @@ func (m metricsStore) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UU return jobs, err } +func (m metricsStore) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]database.GetProvisionerJobsByIDsWithQueuePositionRow, error) { + start := time.Now() + r0, r1 := m.s.GetProvisionerJobsByIDsWithQueuePosition(ctx, ids) + m.queryLatencies.WithLabelValues("GetProvisionerJobsByIDsWithQueuePosition").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.ProvisionerJob, error) { start := time.Now() jobs, err := m.s.GetProvisionerJobsCreatedAfter(ctx, createdAt) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 48ba84c92630f..37cc7dd345635 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -928,6 +928,21 @@ func (mr *MockStoreMockRecorder) GetProvisionerJobsByIDs(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerJobsByIDs", reflect.TypeOf((*MockStore)(nil).GetProvisionerJobsByIDs), arg0, arg1) } +// GetProvisionerJobsByIDsWithQueuePosition mocks base method. +func (m *MockStore) GetProvisionerJobsByIDsWithQueuePosition(arg0 context.Context, arg1 []uuid.UUID) ([]database.GetProvisionerJobsByIDsWithQueuePositionRow, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProvisionerJobsByIDsWithQueuePosition", arg0, arg1) + ret0, _ := ret[0].([]database.GetProvisionerJobsByIDsWithQueuePositionRow) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProvisionerJobsByIDsWithQueuePosition indicates an expected call of GetProvisionerJobsByIDsWithQueuePosition. +func (mr *MockStoreMockRecorder) GetProvisionerJobsByIDsWithQueuePosition(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerJobsByIDsWithQueuePosition", reflect.TypeOf((*MockStore)(nil).GetProvisionerJobsByIDsWithQueuePosition), arg0, arg1) +} + // GetProvisionerJobsCreatedAfter mocks base method. func (m *MockStore) GetProvisionerJobsCreatedAfter(arg0 context.Context, arg1 time.Time) ([]database.ProvisionerJob, error) { m.ctrl.T.Helper() diff --git a/coderd/database/models.go b/coderd/database/models.go index c6bb681d5c00f..9f300614731e2 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.17.2 +// sqlc v1.18.0 package database diff --git a/coderd/database/querier.go b/coderd/database/querier.go index f07b5e5b8d59a..4a3cedd6427d8 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.17.2 +// sqlc v1.18.0 package database @@ -90,6 +90,7 @@ type sqlcQuerier interface { GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]ProvisionerJob, error) + GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]GetProvisionerJobsByIDsWithQueuePositionRow, error) GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]ProvisionerJob, error) GetProvisionerLogsAfterID(ctx context.Context, arg GetProvisionerLogsAfterIDParams) ([]ProvisionerJobLog, error) GetQuotaAllowanceForUser(ctx context.Context, userID uuid.UUID) (int64, error) diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 4a11847d5b4aa..1541af6b770c3 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -5,6 +5,8 @@ package database_test import ( "context" "database/sql" + "encoding/json" + "sort" "testing" "time" @@ -314,3 +316,76 @@ func TestDefaultProxy(t *testing.T) { require.NoError(t, err, "get deployment id") require.Equal(t, depID, found) } + +func TestQueuePosition(t *testing.T) { + t.Parallel() + + if testing.Short() { + t.SkipNow() + } + sqlDB := testSQLDB(t) + err := migrations.Up(sqlDB) + require.NoError(t, err) + db := database.New(sqlDB) + ctx := testutil.Context(t, testutil.WaitLong) + + org := dbgen.Organization(t, db, database.Organization{}) + jobCount := 10 + jobs := []database.ProvisionerJob{} + jobIDs := []uuid.UUID{} + for i := 0; i < jobCount; i++ { + job := dbgen.ProvisionerJob(t, db, database.ProvisionerJob{ + OrganizationID: org.ID, + Tags: database.StringMap{}, + }) + jobs = append(jobs, job) + jobIDs = append(jobIDs, job.ID) + + // We need a slight amount of time between each insertion to ensure that + // the queue position is correct... it's sorted by `created_at`. + time.Sleep(time.Millisecond) + } + + queued, err := db.GetProvisionerJobsByIDsWithQueuePosition(ctx, jobIDs) + require.NoError(t, err) + require.Len(t, queued, jobCount) + sort.Slice(queued, func(i, j int) bool { + return queued[i].QueuePosition < queued[j].QueuePosition + }) + // Ensure that the queue positions are correct based on insertion ID! + for index, job := range queued { + require.Equal(t, job.QueuePosition, int64(index+1)) + require.Equal(t, job.ProvisionerJob.ID, jobs[index].ID) + } + + job, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{ + StartedAt: sql.NullTime{ + Time: database.Now(), + Valid: true, + }, + Types: database.AllProvisionerTypeValues(), + WorkerID: uuid.NullUUID{ + UUID: uuid.New(), + Valid: true, + }, + Tags: json.RawMessage("{}"), + }) + require.NoError(t, err) + require.Equal(t, jobs[0].ID, job.ID) + + queued, err = db.GetProvisionerJobsByIDsWithQueuePosition(ctx, jobIDs) + require.NoError(t, err) + require.Len(t, queued, jobCount) + sort.Slice(queued, func(i, j int) bool { + return queued[i].QueuePosition < queued[j].QueuePosition + }) + // Ensure that queue positions are updated now that the first job has been acquired! + for index, job := range queued { + if index == 0 { + require.Equal(t, job.QueuePosition, int64(0)) + continue + } + require.Equal(t, job.QueuePosition, int64(index)) + require.Equal(t, job.ProvisionerJob.ID, jobs[index].ID) + } +} diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index bab3b845b24d9..8557a8b168bd4 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.17.2 +// sqlc v1.18.0 package database @@ -2287,6 +2287,89 @@ func (q *sqlQuerier) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUI return items, nil } +const getProvisionerJobsByIDsWithQueuePosition = `-- name: GetProvisionerJobsByIDsWithQueuePosition :many +WITH unstarted_jobs AS ( + SELECT + id, created_at + FROM + provisioner_jobs + WHERE + started_at IS NULL +), +queue_position AS ( + SELECT + id, + ROW_NUMBER() OVER (ORDER BY created_at ASC) AS queue_position + FROM + unstarted_jobs +), +queue_size AS ( + SELECT COUNT(*) as count FROM unstarted_jobs +) +SELECT + pj.id, pj.created_at, pj.updated_at, pj.started_at, pj.canceled_at, pj.completed_at, pj.error, pj.organization_id, pj.initiator_id, pj.provisioner, pj.storage_method, pj.type, pj.input, pj.worker_id, pj.file_id, pj.tags, pj.error_code, pj.trace_metadata, + COALESCE(qp.queue_position, 0) AS queue_position, + COALESCE(qs.count, 0) AS queue_size +FROM + provisioner_jobs pj +LEFT JOIN + queue_position qp ON qp.id = pj.id +LEFT JOIN + queue_size qs ON TRUE +WHERE + pj.id = ANY($1 :: uuid [ ]) +` + +type GetProvisionerJobsByIDsWithQueuePositionRow struct { + ProvisionerJob ProvisionerJob `db:"provisionerjob" json:"provisionerjob"` + QueuePosition int64 `db:"queue_position" json:"queue_position"` + QueueSize int64 `db:"queue_size" json:"queue_size"` +} + +func (q *sqlQuerier) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]GetProvisionerJobsByIDsWithQueuePositionRow, error) { + rows, err := q.db.QueryContext(ctx, getProvisionerJobsByIDsWithQueuePosition, pq.Array(ids)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetProvisionerJobsByIDsWithQueuePositionRow + for rows.Next() { + var i GetProvisionerJobsByIDsWithQueuePositionRow + if err := rows.Scan( + &i.ProvisionerJob.ID, + &i.ProvisionerJob.CreatedAt, + &i.ProvisionerJob.UpdatedAt, + &i.ProvisionerJob.StartedAt, + &i.ProvisionerJob.CanceledAt, + &i.ProvisionerJob.CompletedAt, + &i.ProvisionerJob.Error, + &i.ProvisionerJob.OrganizationID, + &i.ProvisionerJob.InitiatorID, + &i.ProvisionerJob.Provisioner, + &i.ProvisionerJob.StorageMethod, + &i.ProvisionerJob.Type, + &i.ProvisionerJob.Input, + &i.ProvisionerJob.WorkerID, + &i.ProvisionerJob.FileID, + &i.ProvisionerJob.Tags, + &i.ProvisionerJob.ErrorCode, + &i.ProvisionerJob.TraceMetadata, + &i.QueuePosition, + &i.QueueSize, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getProvisionerJobsCreatedAfter = `-- name: GetProvisionerJobsCreatedAfter :many SELECT id, created_at, updated_at, started_at, canceled_at, completed_at, error, organization_id, initiator_id, provisioner, storage_method, type, input, worker_id, file_id, tags, error_code, trace_metadata FROM provisioner_jobs WHERE created_at > $1 ` diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index f70121a3d2aa4..d2619cf5e9fdb 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -47,6 +47,38 @@ FROM WHERE id = ANY(@ids :: uuid [ ]); +-- name: GetProvisionerJobsByIDsWithQueuePosition :many +WITH unstarted_jobs AS ( + SELECT + id, created_at + FROM + provisioner_jobs + WHERE + started_at IS NULL +), +queue_position AS ( + SELECT + id, + ROW_NUMBER() OVER (ORDER BY created_at ASC) AS queue_position + FROM + unstarted_jobs +), +queue_size AS ( + SELECT COUNT(*) as count FROM unstarted_jobs +) +SELECT + sqlc.embed(pj), + COALESCE(qp.queue_position, 0) AS queue_position, + COALESCE(qs.count, 0) AS queue_size +FROM + provisioner_jobs pj +LEFT JOIN + queue_position qp ON qp.id = pj.id +LEFT JOIN + queue_size qs ON TRUE +WHERE + pj.id = ANY(@ids :: uuid [ ]); + -- name: GetProvisionerJobsCreatedAfter :many SELECT * FROM provisioner_jobs WHERE created_at > $1; diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 3926d353d1017..526097abf0da6 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -195,14 +195,17 @@ func convertProvisionerJobLog(provisionerJobLog database.ProvisionerJobLog) code } } -func convertProvisionerJob(provisionerJob database.ProvisionerJob) codersdk.ProvisionerJob { +func convertProvisionerJob(pj database.GetProvisionerJobsByIDsWithQueuePositionRow) codersdk.ProvisionerJob { + provisionerJob := pj.ProvisionerJob job := codersdk.ProvisionerJob{ - ID: provisionerJob.ID, - CreatedAt: provisionerJob.CreatedAt, - Error: provisionerJob.Error.String, - ErrorCode: codersdk.JobErrorCode(provisionerJob.ErrorCode.String), - FileID: provisionerJob.FileID, - Tags: provisionerJob.Tags, + ID: provisionerJob.ID, + CreatedAt: provisionerJob.CreatedAt, + Error: provisionerJob.Error.String, + ErrorCode: codersdk.JobErrorCode(provisionerJob.ErrorCode.String), + FileID: provisionerJob.FileID, + Tags: provisionerJob.Tags, + QueuePosition: int(pj.QueuePosition), + QueueSize: int(pj.QueueSize), } // Applying values optional to the struct. if provisionerJob.StartedAt.Valid { diff --git a/coderd/provisionerjobs_internal_test.go b/coderd/provisionerjobs_internal_test.go index acbf303efc957..fd05eb3e62219 100644 --- a/coderd/provisionerjobs_internal_test.go +++ b/coderd/provisionerjobs_internal_test.go @@ -126,7 +126,9 @@ func TestConvertProvisionerJob_Unit(t *testing.T) { testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() - actual := convertProvisionerJob(testCase.input) + actual := convertProvisionerJob(database.GetProvisionerJobsByIDsWithQueuePositionRow{ + ProvisionerJob: testCase.input, + }) assert.Equal(t, testCase.expected, actual) }) } diff --git a/coderd/templateversions.go b/coderd/templateversions.go index 41892091a34aa..7af3029d83e12 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -44,8 +44,8 @@ func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() templateVersion := httpmw.TemplateVersionParam(r) - job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) - if err != nil { + jobs, err := api.Database.GetProvisionerJobsByIDsWithQueuePosition(ctx, []uuid.UUID{templateVersion.JobID}) + if err != nil || len(jobs) == 0 { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), @@ -62,7 +62,7 @@ func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) { return } - schemas, err := api.Database.GetParameterSchemasByJobID(ctx, job.ID) + schemas, err := api.Database.GetParameterSchemasByJobID(ctx, jobs[0].ProvisionerJob.ID) if errors.Is(err, sql.ErrNoRows) { err = nil } @@ -79,7 +79,7 @@ func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) { warnings = append(warnings, codersdk.TemplateVersionWarningUnsupportedWorkspaces) } - httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user, warnings)) + httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(jobs[0]), user, warnings)) } // @Summary Patch template version by ID @@ -155,8 +155,8 @@ func (api *API) patchTemplateVersion(rw http.ResponseWriter, r *http.Request) { return } - job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) - if err != nil { + jobs, err := api.Database.GetProvisionerJobsByIDsWithQueuePosition(ctx, []uuid.UUID{templateVersion.JobID}) + if err != nil || len(jobs) == 0 { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), @@ -173,7 +173,7 @@ func (api *API) patchTemplateVersion(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(updatedTemplateVersion, convertProvisionerJob(job), user, nil)) + httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(updatedTemplateVersion, convertProvisionerJob(jobs[0]), user, nil)) } // @Summary Cancel template version by ID @@ -517,7 +517,10 @@ func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Reques return } - httpapi.Write(ctx, rw, http.StatusCreated, convertProvisionerJob(provisionerJob)) + httpapi.Write(ctx, rw, http.StatusCreated, convertProvisionerJob(database.GetProvisionerJobsByIDsWithQueuePositionRow{ + ProvisionerJob: provisionerJob, + QueuePosition: 0, + })) } // @Summary Get template version dry-run by job ID @@ -554,7 +557,7 @@ func (api *API) templateVersionDryRunResources(rw http.ResponseWriter, r *http.R return } - api.provisionerJobResources(rw, r, job) + api.provisionerJobResources(rw, r, job.ProvisionerJob) } // @Summary Get template version dry-run logs by job ID @@ -575,7 +578,7 @@ func (api *API) templateVersionDryRunLogs(rw http.ResponseWriter, r *http.Reques return } - api.provisionerJobLogs(rw, r, job) + api.provisionerJobLogs(rw, r, job.ProvisionerJob) } // @Summary Cancel template version dry-run by job ID @@ -596,18 +599,18 @@ func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http return } if !api.Authorize(r, rbac.ActionUpdate, - rbac.ResourceWorkspace.InOrg(templateVersion.OrganizationID).WithOwner(job.InitiatorID.String())) { + rbac.ResourceWorkspace.InOrg(templateVersion.OrganizationID).WithOwner(job.ProvisionerJob.InitiatorID.String())) { httpapi.ResourceNotFound(rw) return } - if job.CompletedAt.Valid { + if job.ProvisionerJob.CompletedAt.Valid { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Job has already completed.", }) return } - if job.CanceledAt.Valid { + if job.ProvisionerJob.CanceledAt.Valid { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Job has already been marked as canceled.", }) @@ -615,7 +618,7 @@ func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http } err := api.Database.UpdateProvisionerJobWithCancelByID(ctx, database.UpdateProvisionerJobWithCancelByIDParams{ - ID: job.ID, + ID: job.ProvisionerJob.ID, CanceledAt: sql.NullTime{ Time: database.Now(), Valid: true, @@ -623,7 +626,7 @@ func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http CompletedAt: sql.NullTime{ Time: database.Now(), // If the job is running, don't mark it completed! - Valid: !job.WorkerID.Valid, + Valid: !job.ProvisionerJob.WorkerID.Valid, }, }) if err != nil { @@ -639,7 +642,7 @@ func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http }) } -func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Request) (database.ProvisionerJob, bool) { +func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Request) (database.GetProvisionerJobsByIDsWithQueuePositionRow, bool) { var ( ctx = r.Context() templateVersion = httpmw.TemplateVersionParam(r) @@ -652,48 +655,49 @@ func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Re Message: fmt.Sprintf("Job ID %q must be a valid UUID.", jobID), Detail: err.Error(), }) - return database.ProvisionerJob{}, false + return database.GetProvisionerJobsByIDsWithQueuePositionRow{}, false } - job, err := api.Database.GetProvisionerJobByID(ctx, jobUUID) + jobs, err := api.Database.GetProvisionerJobsByIDsWithQueuePosition(ctx, []uuid.UUID{jobUUID}) if httpapi.Is404Error(err) { httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: fmt.Sprintf("Provisioner job %q not found.", jobUUID), }) - return database.ProvisionerJob{}, false + return database.GetProvisionerJobsByIDsWithQueuePositionRow{}, false } - if err != nil { + if err != nil || len(jobs) == 0 { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) - return database.ProvisionerJob{}, false + return database.GetProvisionerJobsByIDsWithQueuePositionRow{}, false } - if job.Type != database.ProvisionerJobTypeTemplateVersionDryRun { + job := jobs[0] + if job.ProvisionerJob.Type != database.ProvisionerJobTypeTemplateVersionDryRun { httpapi.Forbidden(rw) - return database.ProvisionerJob{}, false + return database.GetProvisionerJobsByIDsWithQueuePositionRow{}, false } // Do a workspace resource check since it's basically a workspace dry-run. if !api.Authorize(r, rbac.ActionRead, - rbac.ResourceWorkspace.InOrg(templateVersion.OrganizationID).WithOwner(job.InitiatorID.String())) { + rbac.ResourceWorkspace.InOrg(templateVersion.OrganizationID).WithOwner(job.ProvisionerJob.InitiatorID.String())) { httpapi.Forbidden(rw) - return database.ProvisionerJob{}, false + return database.GetProvisionerJobsByIDsWithQueuePositionRow{}, false } // Verify that the template version is the one used in the request. var input provisionerdserver.TemplateVersionDryRunJob - err = json.Unmarshal(job.Input, &input) + err = json.Unmarshal(job.ProvisionerJob.Input, &input) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error unmarshalling job metadata.", Detail: err.Error(), }) - return database.ProvisionerJob{}, false + return database.GetProvisionerJobsByIDsWithQueuePositionRow{}, false } if input.TemplateVersionID != templateVersion.ID { httpapi.Forbidden(rw) - return database.ProvisionerJob{}, false + return database.GetProvisionerJobsByIDsWithQueuePositionRow{}, false } return job, true @@ -762,7 +766,7 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque for _, version := range versions { jobIDs = append(jobIDs, version.JobID) } - jobs, err := store.GetProvisionerJobsByIDs(ctx, jobIDs) + jobs, err := store.GetProvisionerJobsByIDsWithQueuePosition(ctx, jobIDs) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", @@ -770,9 +774,9 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque }) return err } - jobByID := map[string]database.ProvisionerJob{} + jobByID := map[string]database.GetProvisionerJobsByIDsWithQueuePositionRow{} for _, job := range jobs { - jobByID[job.ID.String()] = job + jobByID[job.ProvisionerJob.ID.String()] = job } for _, version := range versions { @@ -837,8 +841,8 @@ func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) { }) return } - job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) - if err != nil { + jobs, err := api.Database.GetProvisionerJobsByIDsWithQueuePosition(ctx, []uuid.UUID{templateVersion.JobID}) + if err != nil || len(jobs) == 0 { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), @@ -855,7 +859,7 @@ func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user, nil)) + httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(jobs[0]), user, nil)) } // @Summary Get template version by organization, template, and name @@ -911,8 +915,8 @@ func (api *API) templateVersionByOrganizationTemplateAndName(rw http.ResponseWri }) return } - job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) - if err != nil { + jobs, err := api.Database.GetProvisionerJobsByIDsWithQueuePosition(ctx, []uuid.UUID{templateVersion.JobID}) + if err != nil || len(jobs) == 0 { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), @@ -929,7 +933,7 @@ func (api *API) templateVersionByOrganizationTemplateAndName(rw http.ResponseWri return } - httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user, nil)) + httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(jobs[0]), user, nil)) } // @Summary Get previous template version by organization, template, and name @@ -1006,8 +1010,8 @@ func (api *API) previousTemplateVersionByOrganizationTemplateAndName(rw http.Res return } - job, err := api.Database.GetProvisionerJobByID(ctx, previousTemplateVersion.JobID) - if err != nil { + jobs, err := api.Database.GetProvisionerJobsByIDsWithQueuePosition(ctx, []uuid.UUID{previousTemplateVersion.JobID}) + if err != nil || len(jobs) == 0 { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), @@ -1024,7 +1028,7 @@ func (api *API) previousTemplateVersionByOrganizationTemplateAndName(rw http.Res return } - httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(previousTemplateVersion, convertProvisionerJob(job), user, nil)) + httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(previousTemplateVersion, convertProvisionerJob(jobs[0]), user, nil)) } // @Summary Update active template version by template ID @@ -1336,7 +1340,10 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht return } - httpapi.Write(ctx, rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob), user, nil)) + httpapi.Write(ctx, rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(database.GetProvisionerJobsByIDsWithQueuePositionRow{ + ProvisionerJob: provisionerJob, + QueuePosition: 0, + }), user, nil)) } // templateVersionResources returns the workspace agent resources associated diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 4909022cb120d..856aae4bed441 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -377,7 +377,10 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { apiBuild, err := api.convertWorkspaceBuild( *workspaceBuild, workspace, - *provisionerJob, + database.GetProvisionerJobsByIDsWithQueuePositionRow{ + ProvisionerJob: *provisionerJob, + QueuePosition: 0, + }, users, []database.WorkspaceResource{}, []database.WorkspaceResourceMetadatum{}, @@ -610,7 +613,7 @@ func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { type workspaceBuildsData struct { users []database.User - jobs []database.ProvisionerJob + jobs []database.GetProvisionerJobsByIDsWithQueuePositionRow templateVersions []database.TemplateVersion resources []database.WorkspaceResource metadata []database.WorkspaceResourceMetadatum @@ -635,7 +638,7 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W for _, build := range workspaceBuilds { jobIDs = append(jobIDs, build.JobID) } - jobs, err := api.Database.GetProvisionerJobsByIDs(ctx, jobIDs) + jobs, err := api.Database.GetProvisionerJobsByIDsWithQueuePosition(ctx, jobIDs) if err != nil && !errors.Is(err, sql.ErrNoRows) { return workspaceBuildsData{}, xerrors.Errorf("get provisioner jobs: %w", err) } @@ -717,7 +720,7 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W func (api *API) convertWorkspaceBuilds( workspaceBuilds []database.WorkspaceBuild, workspaces []database.Workspace, - jobs []database.ProvisionerJob, + jobs []database.GetProvisionerJobsByIDsWithQueuePositionRow, users []database.User, workspaceResources []database.WorkspaceResource, resourceMetadata []database.WorkspaceResourceMetadatum, @@ -729,9 +732,9 @@ func (api *API) convertWorkspaceBuilds( for _, workspace := range workspaces { workspaceByID[workspace.ID] = workspace } - jobByID := map[uuid.UUID]database.ProvisionerJob{} + jobByID := map[uuid.UUID]database.GetProvisionerJobsByIDsWithQueuePositionRow{} for _, job := range jobs { - jobByID[job.ID] = job + jobByID[job.ProvisionerJob.ID] = job } templateVersionByID := map[uuid.UUID]database.TemplateVersion{} for _, templateVersion := range templateVersions { @@ -778,7 +781,7 @@ func (api *API) convertWorkspaceBuilds( func (api *API) convertWorkspaceBuild( build database.WorkspaceBuild, workspace database.Workspace, - job database.ProvisionerJob, + job database.GetProvisionerJobsByIDsWithQueuePositionRow, users []database.User, workspaceResources []database.WorkspaceResource, resourceMetadata []database.WorkspaceResourceMetadatum, @@ -816,7 +819,7 @@ func (api *API) convertWorkspaceBuild( return codersdk.WorkspaceBuild{}, xerrors.Errorf("build initiator not found for workspace: %q", workspace.Name) } - resources := resourcesByJobID[job.ID] + resources := resourcesByJobID[job.ProvisionerJob.ID] apiResources := make([]codersdk.WorkspaceResource, 0) for _, resource := range resources { agents := agentsByResourceID[resource.ID] diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 981d161bf3e2b..9f499394f79e3 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -487,7 +487,10 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req apiBuild, err := api.convertWorkspaceBuild( *workspaceBuild, workspace, - *provisionerJob, + database.GetProvisionerJobsByIDsWithQueuePositionRow{ + ProvisionerJob: *provisionerJob, + QueuePosition: 0, + }, users, []database.WorkspaceResource{}, []database.WorkspaceResourceMetadatum{}, diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index 040a0318ab4e6..1c9378f718b3a 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -75,17 +75,19 @@ const ( // ProvisionerJob describes the job executed by the provisioning daemon. type ProvisionerJob struct { - ID uuid.UUID `json:"id" format:"uuid"` - CreatedAt time.Time `json:"created_at" format:"date-time"` - StartedAt *time.Time `json:"started_at,omitempty" format:"date-time"` - CompletedAt *time.Time `json:"completed_at,omitempty" format:"date-time"` - CanceledAt *time.Time `json:"canceled_at,omitempty" format:"date-time"` - Error string `json:"error,omitempty"` - ErrorCode JobErrorCode `json:"error_code,omitempty" enums:"MISSING_TEMPLATE_PARAMETER,REQUIRED_TEMPLATE_VARIABLES"` - Status ProvisionerJobStatus `json:"status" enums:"pending,running,succeeded,canceling,canceled,failed"` - WorkerID *uuid.UUID `json:"worker_id,omitempty" format:"uuid"` - FileID uuid.UUID `json:"file_id" format:"uuid"` - Tags map[string]string `json:"tags"` + ID uuid.UUID `json:"id" format:"uuid"` + CreatedAt time.Time `json:"created_at" format:"date-time"` + StartedAt *time.Time `json:"started_at,omitempty" format:"date-time"` + CompletedAt *time.Time `json:"completed_at,omitempty" format:"date-time"` + CanceledAt *time.Time `json:"canceled_at,omitempty" format:"date-time"` + Error string `json:"error,omitempty"` + ErrorCode JobErrorCode `json:"error_code,omitempty" enums:"MISSING_TEMPLATE_PARAMETER,REQUIRED_TEMPLATE_VARIABLES"` + Status ProvisionerJobStatus `json:"status" enums:"pending,running,succeeded,canceling,canceled,failed"` + WorkerID *uuid.UUID `json:"worker_id,omitempty" format:"uuid"` + FileID uuid.UUID `json:"file_id" format:"uuid"` + Tags map[string]string `json:"tags"` + QueuePosition int `json:"queue_position"` + QueueSize int `json:"queue_size"` } // ProvisionerJobLog represents the provisioner log entry annotated with source and level. diff --git a/docs/api/builds.md b/docs/api/builds.md index e58f77251a342..a36337b77397a 100644 --- a/docs/api/builds.md +++ b/docs/api/builds.md @@ -42,6 +42,8 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -196,6 +198,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -736,6 +740,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -895,6 +901,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -1030,6 +1038,8 @@ Status Code **200** | `»» error_code` | [codersdk.JobErrorCode](schemas.md#codersdkjoberrorcode) | false | | | | `»» file_id` | string(uuid) | false | | | | `»» id` | string(uuid) | false | | | +| `»» queue_position` | integer | false | | | +| `»» queue_size` | integer | false | | | | `»» started_at` | string(date-time) | false | | | | `»» status` | [codersdk.ProvisionerJobStatus](schemas.md#codersdkprovisionerjobstatus) | false | | | | `»» tags` | object | false | | | @@ -1228,6 +1238,8 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { diff --git a/docs/api/schemas.md b/docs/api/schemas.md index b05444c94696a..3c5015e82e9e2 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -3173,6 +3173,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -3194,6 +3196,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `error_code` | [codersdk.JobErrorCode](#codersdkjoberrorcode) | false | | | | `file_id` | string | false | | | | `id` | string | false | | | +| `queue_position` | integer | false | | | +| `queue_size` | integer | false | | | | `started_at` | string | false | | | | `status` | [codersdk.ProvisionerJobStatus](#codersdkprovisionerjobstatus) | false | | | | `tags` | object | false | | | @@ -3912,6 +3916,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -4447,6 +4453,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -4991,6 +4999,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -5478,6 +5488,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { diff --git a/docs/api/templates.md b/docs/api/templates.md index b9e3033a83e73..44bf71cff9556 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -374,6 +374,8 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -452,6 +454,8 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -553,6 +557,8 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -852,6 +858,8 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions \ "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -905,6 +913,8 @@ Status Code **200** | `»» error_code` | [codersdk.JobErrorCode](schemas.md#codersdkjoberrorcode) | false | | | | `»» file_id` | string(uuid) | false | | | | `»» id` | string(uuid) | false | | | +| `»» queue_position` | integer | false | | | +| `»» queue_size` | integer | false | | | | `»» started_at` | string(date-time) | false | | | | `»» status` | [codersdk.ProvisionerJobStatus](schemas.md#codersdkprovisionerjobstatus) | false | | | | `»» tags` | object | false | | | @@ -1041,6 +1051,8 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions/{templ "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -1094,6 +1106,8 @@ Status Code **200** | `»» error_code` | [codersdk.JobErrorCode](schemas.md#codersdkjoberrorcode) | false | | | | `»» file_id` | string(uuid) | false | | | | `»» id` | string(uuid) | false | | | +| `»» queue_position` | integer | false | | | +| `»» queue_size` | integer | false | | | | `»» started_at` | string(date-time) | false | | | | `»» status` | [codersdk.ProvisionerJobStatus](schemas.md#codersdkprovisionerjobstatus) | false | | | | `»» tags` | object | false | | | @@ -1174,6 +1188,8 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion} \ "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -1260,6 +1276,8 @@ curl -X PATCH http://coder-server:8080/api/v2/templateversions/{templateversion} "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -1383,6 +1401,8 @@ curl -X POST http://coder-server:8080/api/v2/templateversions/{templateversion}/ "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -1434,6 +1454,8 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index b8c31d1bf83eb..753d3c8a9d4ee 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -66,6 +66,8 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -241,6 +243,8 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -440,6 +444,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { @@ -612,6 +618,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "error_code": "MISSING_TEMPLATE_PARAMETER", "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, "started_at": "2019-08-24T14:15:22Z", "status": "pending", "tags": { diff --git a/dogfood/Dockerfile b/dogfood/Dockerfile index cf9b3a017b7e6..4bb0e45088fbc 100644 --- a/dogfood/Dockerfile +++ b/dogfood/Dockerfile @@ -53,7 +53,7 @@ RUN mkdir --parents "$GOPATH" && \ # charts and values files go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.5.0 && \ # sqlc for Go code generation - go install github.com/kyleconroy/sqlc/cmd/sqlc@v1.17.2 && \ + go install github.com/kyleconroy/sqlc/cmd/sqlc@v1.18.0 && \ # gcr-cleaner-cli used by CI to prune unused images go install github.com/sethvargo/gcr-cleaner/cmd/gcr-cleaner-cli@v0.5.1 && \ # ruleguard for checking custom rules, without needing to run all of diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 961cc8f16e455..2483c527528e5 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -640,6 +640,8 @@ export interface ProvisionerJob { readonly worker_id?: string readonly file_id: string readonly tags: Record + readonly queue_position: number + readonly queue_size: number } // From codersdk/provisionerdaemons.go diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index dd4d8565e2278..56b7808563aa6 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -286,6 +286,8 @@ export const MockProvisionerJob: TypesGen.ProvisionerJob = { file_id: MockOrganization.id, completed_at: "2022-05-17T17:39:01.382927298Z", tags: {}, + queue_position: 0, + queue_size: 0, } export const MockFailedProvisionerJob: TypesGen.ProvisionerJob = {