mirror of https://github.com/coder/coder.git
chore: compute job status as column (#10024)
* chore: provisioner job status as column * use provisioner job status for workspace searching
This commit is contained in:
parent
d5040441aa
commit
5021e23105
|
@ -9124,7 +9124,8 @@ const docTemplate = `{
|
|||
"succeeded",
|
||||
"canceling",
|
||||
"canceled",
|
||||
"failed"
|
||||
"failed",
|
||||
"unknown"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ProvisionerJobPending",
|
||||
|
@ -9132,7 +9133,8 @@ const docTemplate = `{
|
|||
"ProvisionerJobSucceeded",
|
||||
"ProvisionerJobCanceling",
|
||||
"ProvisionerJobCanceled",
|
||||
"ProvisionerJobFailed"
|
||||
"ProvisionerJobFailed",
|
||||
"ProvisionerJobUnknown"
|
||||
]
|
||||
},
|
||||
"codersdk.ProvisionerLogLevel": {
|
||||
|
|
|
@ -8211,7 +8211,8 @@
|
|||
"succeeded",
|
||||
"canceling",
|
||||
"canceled",
|
||||
"failed"
|
||||
"failed",
|
||||
"unknown"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ProvisionerJobPending",
|
||||
|
@ -8219,7 +8220,8 @@
|
|||
"ProvisionerJobSucceeded",
|
||||
"ProvisionerJobCanceling",
|
||||
"ProvisionerJobCanceled",
|
||||
"ProvisionerJobFailed"
|
||||
"ProvisionerJobFailed",
|
||||
"ProvisionerJobUnknown"
|
||||
]
|
||||
},
|
||||
"codersdk.ProvisionerLogLevel": {
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
|
||||
|
@ -303,7 +302,7 @@ func getNextTransition(
|
|||
// isEligibleForAutostart returns true if the workspace should be autostarted.
|
||||
func isEligibleForAutostart(ws database.Workspace, build database.WorkspaceBuild, job database.ProvisionerJob, templateSchedule schedule.TemplateScheduleOptions, currentTick time.Time) bool {
|
||||
// Don't attempt to autostart failed workspaces.
|
||||
if db2sdk.ProvisionerJobStatus(job) == codersdk.ProvisionerJobFailed {
|
||||
if codersdk.ProvisionerJobStatus(job.JobStatus) == codersdk.ProvisionerJobFailed {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -337,7 +336,7 @@ func isEligibleForAutostart(ws database.Workspace, build database.WorkspaceBuild
|
|||
|
||||
// isEligibleForAutostart returns true if the workspace should be autostopped.
|
||||
func isEligibleForAutostop(ws database.Workspace, build database.WorkspaceBuild, job database.ProvisionerJob, currentTick time.Time) bool {
|
||||
if db2sdk.ProvisionerJobStatus(job) == codersdk.ProvisionerJobFailed {
|
||||
if codersdk.ProvisionerJobStatus(job.JobStatus) == codersdk.ProvisionerJobFailed {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -379,7 +378,7 @@ func isEligibleForFailedStop(build database.WorkspaceBuild, job database.Provisi
|
|||
// If the template has specified a failure TLL.
|
||||
return templateSchedule.FailureTTL > 0 &&
|
||||
// And the job resulted in failure.
|
||||
db2sdk.ProvisionerJobStatus(job) == codersdk.ProvisionerJobFailed &&
|
||||
codersdk.ProvisionerJobStatus(job.JobStatus) == codersdk.ProvisionerJobFailed &&
|
||||
build.Transition == database.WorkspaceTransitionStart &&
|
||||
// And sufficient time has elapsed since the job has completed.
|
||||
job.CompletedAt.Valid &&
|
||||
|
|
|
@ -71,31 +71,6 @@ func TemplateVersionParameter(param database.TemplateVersionParameter) (codersdk
|
|||
}, nil
|
||||
}
|
||||
|
||||
func ProvisionerJobStatus(provisionerJob database.ProvisionerJob) codersdk.ProvisionerJobStatus {
|
||||
// The case where jobs are hung is handled by the unhang package. We can't
|
||||
// just return Failed here when it's hung because that doesn't reflect in
|
||||
// the database.
|
||||
switch {
|
||||
case provisionerJob.CanceledAt.Valid:
|
||||
if !provisionerJob.CompletedAt.Valid {
|
||||
return codersdk.ProvisionerJobCanceling
|
||||
}
|
||||
if provisionerJob.Error.String == "" {
|
||||
return codersdk.ProvisionerJobCanceled
|
||||
}
|
||||
return codersdk.ProvisionerJobFailed
|
||||
case !provisionerJob.StartedAt.Valid:
|
||||
return codersdk.ProvisionerJobPending
|
||||
case provisionerJob.CompletedAt.Valid:
|
||||
if provisionerJob.Error.String == "" {
|
||||
return codersdk.ProvisionerJobSucceeded
|
||||
}
|
||||
return codersdk.ProvisionerJobFailed
|
||||
default:
|
||||
return codersdk.ProvisionerJobRunning
|
||||
}
|
||||
}
|
||||
|
||||
func User(user database.User, organizationIDs []uuid.UUID) codersdk.User {
|
||||
convertedUser := codersdk.User{
|
||||
ID: user.ID,
|
||||
|
|
|
@ -4,13 +4,17 @@ import (
|
|||
"crypto/rand"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||
|
@ -110,11 +114,36 @@ func TestProvisionerJobStatus(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
// Share db for all job inserts.
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
|
||||
for i, tc := range cases {
|
||||
tc := tc
|
||||
i := i
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := db2sdk.ProvisionerJobStatus(tc.job)
|
||||
// Populate standard fields
|
||||
now := dbtime.Now().Round(time.Minute)
|
||||
tc.job.ID = uuid.New()
|
||||
tc.job.CreatedAt = now
|
||||
tc.job.UpdatedAt = now
|
||||
tc.job.InitiatorID = org.ID
|
||||
tc.job.OrganizationID = org.ID
|
||||
tc.job.Input = []byte("{}")
|
||||
tc.job.Provisioner = database.ProvisionerTypeEcho
|
||||
// Unique tags for each job.
|
||||
tc.job.Tags = map[string]string{fmt.Sprintf("%d", i): "true"}
|
||||
|
||||
inserted := dbgen.ProvisionerJob(t, db, nil, tc.job)
|
||||
// Make sure the inserted job has the right values.
|
||||
require.Equal(t, tc.job.StartedAt.Time.UTC(), inserted.StartedAt.Time.UTC(), "started at")
|
||||
require.Equal(t, tc.job.CompletedAt.Time.UTC(), inserted.CompletedAt.Time.UTC(), "completed at")
|
||||
require.Equal(t, tc.job.CanceledAt.Time.UTC(), inserted.CanceledAt.Time.UTC(), "cancelled at")
|
||||
require.Equal(t, tc.job.Error, inserted.Error, "error")
|
||||
require.Equal(t, tc.job.ErrorCode, inserted.ErrorCode, "error code")
|
||||
|
||||
actual := codersdk.ProvisionerJobStatus(inserted.JobStatus)
|
||||
require.Equal(t, tc.status, actual)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
|
@ -604,16 +603,6 @@ func (q *FakeQuerier) getGroupByIDNoLock(_ context.Context, id uuid.UUID) (datab
|
|||
return database.Group{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
// isNull is only used in dbfake, so reflect is ok. Use this to make the logic
|
||||
// look more similar to the postgres.
|
||||
func isNull(v interface{}) bool {
|
||||
return !isNotNull(v)
|
||||
}
|
||||
|
||||
func isNotNull(v interface{}) bool {
|
||||
return reflect.ValueOf(v).FieldByName("Valid").Bool()
|
||||
}
|
||||
|
||||
// ErrUnimplemented is returned by methods only used by the enterprise/tailnet.pgCoord. This coordinator explicitly
|
||||
// depends on postgres triggers that announce changes on the pubsub. Implementing support for this in the fake
|
||||
// database would strongly couple the FakeQuerier to the pubsub, which is undesirable. Furthermore, it makes little
|
||||
|
@ -695,6 +684,36 @@ func minTime(t, u time.Time) time.Time {
|
|||
return u
|
||||
}
|
||||
|
||||
func provisonerJobStatus(j database.ProvisionerJob) database.ProvisionerJobStatus {
|
||||
if isNotNull(j.CompletedAt) {
|
||||
if j.Error.String != "" {
|
||||
return database.ProvisionerJobStatusFailed
|
||||
}
|
||||
if isNotNull(j.CanceledAt) {
|
||||
return database.ProvisionerJobStatusCanceled
|
||||
}
|
||||
return database.ProvisionerJobStatusSucceeded
|
||||
}
|
||||
|
||||
if isNotNull(j.CanceledAt) {
|
||||
return database.ProvisionerJobStatusCanceling
|
||||
}
|
||||
if isNull(j.StartedAt) {
|
||||
return database.ProvisionerJobStatusPending
|
||||
}
|
||||
return database.ProvisionerJobStatusRunning
|
||||
}
|
||||
|
||||
// isNull is only used in dbfake, so reflect is ok. Use this to make the logic
|
||||
// look more similar to the postgres.
|
||||
func isNull(v interface{}) bool {
|
||||
return !isNotNull(v)
|
||||
}
|
||||
|
||||
func isNotNull(v interface{}) bool {
|
||||
return reflect.ValueOf(v).FieldByName("Valid").Bool()
|
||||
}
|
||||
|
||||
func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error {
|
||||
return xerrors.New("AcquireLock must only be called within a transaction")
|
||||
}
|
||||
|
@ -748,6 +767,7 @@ func (q *FakeQuerier) AcquireProvisionerJob(_ context.Context, arg database.Acqu
|
|||
provisionerJob.StartedAt = arg.StartedAt
|
||||
provisionerJob.UpdatedAt = arg.StartedAt.Time
|
||||
provisionerJob.WorkerID = arg.WorkerID
|
||||
provisionerJob.JobStatus = provisonerJobStatus(provisionerJob)
|
||||
q.provisionerJobs[index] = provisionerJob
|
||||
return provisionerJob, nil
|
||||
}
|
||||
|
@ -4077,7 +4097,7 @@ func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, no
|
|||
if err != nil {
|
||||
return nil, xerrors.Errorf("get provisioner job by ID: %w", err)
|
||||
}
|
||||
if db2sdk.ProvisionerJobStatus(job) == codersdk.ProvisionerJobFailed {
|
||||
if codersdk.ProvisionerJobStatus(job.JobStatus) == codersdk.ProvisionerJobFailed {
|
||||
workspaces = append(workspaces, workspace)
|
||||
continue
|
||||
}
|
||||
|
@ -4464,6 +4484,7 @@ func (q *FakeQuerier) InsertProvisionerJob(_ context.Context, arg database.Inser
|
|||
Input: arg.Input,
|
||||
Tags: arg.Tags,
|
||||
}
|
||||
job.JobStatus = provisonerJobStatus(job)
|
||||
q.provisionerJobs = append(q.provisionerJobs, job)
|
||||
return job, nil
|
||||
}
|
||||
|
@ -5393,6 +5414,7 @@ func (q *FakeQuerier) UpdateProvisionerJobByID(_ context.Context, arg database.U
|
|||
continue
|
||||
}
|
||||
job.UpdatedAt = arg.UpdatedAt
|
||||
job.JobStatus = provisonerJobStatus(job)
|
||||
q.provisionerJobs[index] = job
|
||||
return nil
|
||||
}
|
||||
|
@ -5413,6 +5435,7 @@ func (q *FakeQuerier) UpdateProvisionerJobWithCancelByID(_ context.Context, arg
|
|||
}
|
||||
job.CanceledAt = arg.CanceledAt
|
||||
job.CompletedAt = arg.CompletedAt
|
||||
job.JobStatus = provisonerJobStatus(job)
|
||||
q.provisionerJobs[index] = job
|
||||
return nil
|
||||
}
|
||||
|
@ -5435,6 +5458,7 @@ func (q *FakeQuerier) UpdateProvisionerJobWithCompleteByID(_ context.Context, ar
|
|||
job.CompletedAt = arg.CompletedAt
|
||||
job.Error = arg.Error
|
||||
job.ErrorCode = arg.ErrorCode
|
||||
job.JobStatus = provisonerJobStatus(job)
|
||||
q.provisionerJobs[index] = job
|
||||
return nil
|
||||
}
|
||||
|
@ -6604,61 +6628,30 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
|
|||
// This logic should match the logic in the workspace.sql file.
|
||||
var statusMatch bool
|
||||
switch database.WorkspaceStatus(arg.Status) {
|
||||
case database.WorkspaceStatusPending:
|
||||
statusMatch = isNull(job.StartedAt)
|
||||
case database.WorkspaceStatusStarting:
|
||||
statusMatch = isNotNull(job.StartedAt) &&
|
||||
isNull(job.CanceledAt) &&
|
||||
isNull(job.CompletedAt) &&
|
||||
time.Since(job.UpdatedAt) < 30*time.Second &&
|
||||
statusMatch = job.JobStatus == database.ProvisionerJobStatusRunning &&
|
||||
build.Transition == database.WorkspaceTransitionStart
|
||||
|
||||
case database.WorkspaceStatusRunning:
|
||||
statusMatch = isNotNull(job.CompletedAt) &&
|
||||
isNull(job.CanceledAt) &&
|
||||
isNull(job.Error) &&
|
||||
build.Transition == database.WorkspaceTransitionStart
|
||||
|
||||
case database.WorkspaceStatusStopping:
|
||||
statusMatch = isNotNull(job.StartedAt) &&
|
||||
isNull(job.CanceledAt) &&
|
||||
isNull(job.CompletedAt) &&
|
||||
time.Since(job.UpdatedAt) < 30*time.Second &&
|
||||
statusMatch = job.JobStatus == database.ProvisionerJobStatusRunning &&
|
||||
build.Transition == database.WorkspaceTransitionStop
|
||||
|
||||
case database.WorkspaceStatusStopped:
|
||||
statusMatch = isNotNull(job.CompletedAt) &&
|
||||
isNull(job.CanceledAt) &&
|
||||
isNull(job.Error) &&
|
||||
build.Transition == database.WorkspaceTransitionStop
|
||||
case database.WorkspaceStatusFailed:
|
||||
statusMatch = (isNotNull(job.CanceledAt) && isNotNull(job.Error)) ||
|
||||
(isNotNull(job.CompletedAt) && isNotNull(job.Error))
|
||||
|
||||
case database.WorkspaceStatusCanceling:
|
||||
statusMatch = isNotNull(job.CanceledAt) &&
|
||||
isNull(job.CompletedAt)
|
||||
|
||||
case database.WorkspaceStatusCanceled:
|
||||
statusMatch = isNotNull(job.CanceledAt) &&
|
||||
isNotNull(job.CompletedAt)
|
||||
|
||||
case database.WorkspaceStatusDeleted:
|
||||
statusMatch = isNotNull(job.StartedAt) &&
|
||||
isNull(job.CanceledAt) &&
|
||||
isNotNull(job.CompletedAt) &&
|
||||
time.Since(job.UpdatedAt) < 30*time.Second &&
|
||||
build.Transition == database.WorkspaceTransitionDelete &&
|
||||
isNull(job.Error)
|
||||
|
||||
case database.WorkspaceStatusDeleting:
|
||||
statusMatch = isNull(job.CompletedAt) &&
|
||||
isNull(job.CanceledAt) &&
|
||||
isNull(job.Error) &&
|
||||
statusMatch = job.JobStatus == database.ProvisionerJobStatusRunning &&
|
||||
build.Transition == database.WorkspaceTransitionDelete
|
||||
|
||||
case "started":
|
||||
statusMatch = job.JobStatus == database.ProvisionerJobStatusSucceeded &&
|
||||
build.Transition == database.WorkspaceTransitionStart
|
||||
case database.WorkspaceStatusDeleted:
|
||||
statusMatch = job.JobStatus == database.ProvisionerJobStatusSucceeded &&
|
||||
build.Transition == database.WorkspaceTransitionDelete
|
||||
case database.WorkspaceStatusStopped:
|
||||
statusMatch = job.JobStatus == database.ProvisionerJobStatusSucceeded &&
|
||||
build.Transition == database.WorkspaceTransitionStop
|
||||
case database.WorkspaceStatusRunning:
|
||||
statusMatch = job.JobStatus == database.ProvisionerJobStatusSucceeded &&
|
||||
build.Transition == database.WorkspaceTransitionStart
|
||||
default:
|
||||
return nil, xerrors.Errorf("unknown workspace status in filter: %q", arg.Status)
|
||||
statusMatch = job.JobStatus == database.ProvisionerJobStatus(arg.Status)
|
||||
}
|
||||
if !statusMatch {
|
||||
continue
|
||||
|
|
|
@ -328,16 +328,16 @@ func GroupMember(t testing.TB, db database.Store, orig database.GroupMember) dat
|
|||
// ProvisionerJob is a bit more involved to get the values such as "completedAt", "startedAt", "cancelledAt" set. ps
|
||||
// can be set to nil if you are SURE that you don't require a provisionerdaemon to acquire the job in your test.
|
||||
func ProvisionerJob(t testing.TB, db database.Store, ps pubsub.Pubsub, orig database.ProvisionerJob) database.ProvisionerJob {
|
||||
id := takeFirst(orig.ID, uuid.New())
|
||||
jobID := takeFirst(orig.ID, uuid.New())
|
||||
// Always set some tags to prevent Acquire from grabbing jobs it should not.
|
||||
if !orig.StartedAt.Time.IsZero() {
|
||||
if orig.Tags == nil {
|
||||
orig.Tags = make(database.StringMap)
|
||||
}
|
||||
// Make sure when we acquire the job, we only get this one.
|
||||
orig.Tags[id.String()] = "true"
|
||||
orig.Tags[jobID.String()] = "true"
|
||||
}
|
||||
jobID := takeFirst(orig.ID, uuid.New())
|
||||
|
||||
job, err := db.InsertProvisionerJob(genCtx, database.InsertProvisionerJobParams{
|
||||
ID: jobID,
|
||||
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
|
||||
|
@ -365,6 +365,8 @@ func ProvisionerJob(t testing.TB, db database.Store, ps pubsub.Pubsub, orig data
|
|||
WorkerID: uuid.NullUUID{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// There is no easy way to make sure we acquire the correct job.
|
||||
require.Equal(t, jobID, job.ID, "acquired incorrect job")
|
||||
}
|
||||
|
||||
if !orig.CompletedAt.Time.IsZero() || orig.Error.String != "" {
|
||||
|
|
|
@ -89,6 +89,18 @@ CREATE TYPE parameter_type_system AS ENUM (
|
|||
'hcl'
|
||||
);
|
||||
|
||||
CREATE TYPE provisioner_job_status AS ENUM (
|
||||
'pending',
|
||||
'running',
|
||||
'succeeded',
|
||||
'canceling',
|
||||
'canceled',
|
||||
'failed',
|
||||
'unknown'
|
||||
);
|
||||
|
||||
COMMENT ON TYPE provisioner_job_status IS 'Computed status of a provisioner job. Jobs could be stuck in a hung state, these states do not guarantee any transition to another state.';
|
||||
|
||||
CREATE TYPE provisioner_job_type AS ENUM (
|
||||
'template_version_import',
|
||||
'workspace_build',
|
||||
|
@ -500,9 +512,27 @@ CREATE TABLE provisioner_jobs (
|
|||
file_id uuid NOT NULL,
|
||||
tags jsonb DEFAULT '{"scope": "organization"}'::jsonb NOT NULL,
|
||||
error_code text,
|
||||
trace_metadata jsonb
|
||||
trace_metadata jsonb,
|
||||
job_status provisioner_job_status GENERATED ALWAYS AS (
|
||||
CASE
|
||||
WHEN (completed_at IS NOT NULL) THEN
|
||||
CASE
|
||||
WHEN (error <> ''::text) THEN 'failed'::provisioner_job_status
|
||||
WHEN (canceled_at IS NOT NULL) THEN 'canceled'::provisioner_job_status
|
||||
ELSE 'succeeded'::provisioner_job_status
|
||||
END
|
||||
ELSE
|
||||
CASE
|
||||
WHEN (error <> ''::text) THEN 'failed'::provisioner_job_status
|
||||
WHEN (canceled_at IS NOT NULL) THEN 'canceling'::provisioner_job_status
|
||||
WHEN (started_at IS NULL) THEN 'pending'::provisioner_job_status
|
||||
ELSE 'running'::provisioner_job_status
|
||||
END
|
||||
END) STORED NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN provisioner_jobs.job_status IS 'Computed column to track the status of the job.';
|
||||
|
||||
CREATE TABLE replicas (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE provisioner_jobs DROP COLUMN job_status;
|
||||
DROP TYPE provisioner_job_status;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,38 @@
|
|||
BEGIN;
|
||||
|
||||
CREATE TYPE provisioner_job_status AS ENUM ('pending', 'running', 'succeeded', 'canceling', 'canceled', 'failed', 'unknown');
|
||||
COMMENT ON TYPE provisioner_job_status IS 'Computed status of a provisioner job. Jobs could be stuck in a hung state, these states do not guarantee any transition to another state.';
|
||||
|
||||
ALTER TABLE provisioner_jobs ADD COLUMN
|
||||
job_status provisioner_job_status NOT NULL GENERATED ALWAYS AS (
|
||||
CASE
|
||||
-- Completed means it is not in an "-ing" state
|
||||
WHEN completed_at IS NOT NULL THEN
|
||||
CASE
|
||||
-- The order of these checks are important.
|
||||
-- Check the error first, then cancelled, then completed.
|
||||
WHEN error != '' THEN 'failed'::provisioner_job_status
|
||||
WHEN canceled_at IS NOT NULL THEN 'canceled'::provisioner_job_status
|
||||
ELSE 'succeeded'::provisioner_job_status
|
||||
END
|
||||
-- Not completed means it is in some "-ing" state
|
||||
ELSE
|
||||
CASE
|
||||
-- This should never happen because all errors set
|
||||
-- should also set a completed_at timestamp.
|
||||
-- But if there is an error, we should always return
|
||||
-- a failed state.
|
||||
WHEN error != '' THEN 'failed'::provisioner_job_status
|
||||
WHEN canceled_at IS NOT NULL THEN 'canceling'::provisioner_job_status
|
||||
-- Not done and not started means it is pending
|
||||
WHEN started_at IS NULL THEN 'pending'::provisioner_job_status
|
||||
ELSE 'running'::provisioner_job_status
|
||||
END
|
||||
END
|
||||
-- Stored so we do not have to recompute it every time.
|
||||
) STORED;
|
||||
|
||||
|
||||
COMMENT ON COLUMN provisioner_jobs.job_status IS 'Computed column to track the status of the job.';
|
||||
|
||||
COMMIT;
|
|
@ -837,6 +837,80 @@ func AllParameterTypeSystemValues() []ParameterTypeSystem {
|
|||
}
|
||||
}
|
||||
|
||||
// Computed status of a provisioner job. Jobs could be stuck in a hung state, these states do not guarantee any transition to another state.
|
||||
type ProvisionerJobStatus string
|
||||
|
||||
const (
|
||||
ProvisionerJobStatusPending ProvisionerJobStatus = "pending"
|
||||
ProvisionerJobStatusRunning ProvisionerJobStatus = "running"
|
||||
ProvisionerJobStatusSucceeded ProvisionerJobStatus = "succeeded"
|
||||
ProvisionerJobStatusCanceling ProvisionerJobStatus = "canceling"
|
||||
ProvisionerJobStatusCanceled ProvisionerJobStatus = "canceled"
|
||||
ProvisionerJobStatusFailed ProvisionerJobStatus = "failed"
|
||||
ProvisionerJobStatusUnknown ProvisionerJobStatus = "unknown"
|
||||
)
|
||||
|
||||
func (e *ProvisionerJobStatus) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = ProvisionerJobStatus(s)
|
||||
case string:
|
||||
*e = ProvisionerJobStatus(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for ProvisionerJobStatus: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullProvisionerJobStatus struct {
|
||||
ProvisionerJobStatus ProvisionerJobStatus `json:"provisioner_job_status"`
|
||||
Valid bool `json:"valid"` // Valid is true if ProvisionerJobStatus is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullProvisionerJobStatus) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.ProvisionerJobStatus, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.ProvisionerJobStatus.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullProvisionerJobStatus) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.ProvisionerJobStatus), nil
|
||||
}
|
||||
|
||||
func (e ProvisionerJobStatus) Valid() bool {
|
||||
switch e {
|
||||
case ProvisionerJobStatusPending,
|
||||
ProvisionerJobStatusRunning,
|
||||
ProvisionerJobStatusSucceeded,
|
||||
ProvisionerJobStatusCanceling,
|
||||
ProvisionerJobStatusCanceled,
|
||||
ProvisionerJobStatusFailed,
|
||||
ProvisionerJobStatusUnknown:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AllProvisionerJobStatusValues() []ProvisionerJobStatus {
|
||||
return []ProvisionerJobStatus{
|
||||
ProvisionerJobStatusPending,
|
||||
ProvisionerJobStatusRunning,
|
||||
ProvisionerJobStatusSucceeded,
|
||||
ProvisionerJobStatusCanceling,
|
||||
ProvisionerJobStatusCanceled,
|
||||
ProvisionerJobStatusFailed,
|
||||
ProvisionerJobStatusUnknown,
|
||||
}
|
||||
}
|
||||
|
||||
type ProvisionerJobType string
|
||||
|
||||
const (
|
||||
|
@ -1671,6 +1745,8 @@ type ProvisionerJob struct {
|
|||
Tags StringMap `db:"tags" json:"tags"`
|
||||
ErrorCode sql.NullString `db:"error_code" json:"error_code"`
|
||||
TraceMetadata pqtype.NullRawMessage `db:"trace_metadata" json:"trace_metadata"`
|
||||
// Computed column to track the status of the job.
|
||||
JobStatus ProvisionerJobStatus `db:"job_status" json:"job_status"`
|
||||
}
|
||||
|
||||
type ProvisionerJobLog struct {
|
||||
|
|
|
@ -2988,7 +2988,7 @@ WHERE
|
|||
SKIP LOCKED
|
||||
LIMIT
|
||||
1
|
||||
) RETURNING 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
|
||||
) RETURNING 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, job_status
|
||||
`
|
||||
|
||||
type AcquireProvisionerJobParams struct {
|
||||
|
@ -3031,13 +3031,14 @@ func (q *sqlQuerier) AcquireProvisionerJob(ctx context.Context, arg AcquireProvi
|
|||
&i.Tags,
|
||||
&i.ErrorCode,
|
||||
&i.TraceMetadata,
|
||||
&i.JobStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getHungProvisionerJobs = `-- name: GetHungProvisionerJobs :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
|
||||
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, job_status
|
||||
FROM
|
||||
provisioner_jobs
|
||||
WHERE
|
||||
|
@ -3074,6 +3075,7 @@ func (q *sqlQuerier) GetHungProvisionerJobs(ctx context.Context, updatedAt time.
|
|||
&i.Tags,
|
||||
&i.ErrorCode,
|
||||
&i.TraceMetadata,
|
||||
&i.JobStatus,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3090,7 +3092,7 @@ func (q *sqlQuerier) GetHungProvisionerJobs(ctx context.Context, updatedAt time.
|
|||
|
||||
const getProvisionerJobByID = `-- name: GetProvisionerJobByID :one
|
||||
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
|
||||
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, job_status
|
||||
FROM
|
||||
provisioner_jobs
|
||||
WHERE
|
||||
|
@ -3119,13 +3121,14 @@ func (q *sqlQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (P
|
|||
&i.Tags,
|
||||
&i.ErrorCode,
|
||||
&i.TraceMetadata,
|
||||
&i.JobStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getProvisionerJobsByIDs = `-- name: GetProvisionerJobsByIDs :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
|
||||
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, job_status
|
||||
FROM
|
||||
provisioner_jobs
|
||||
WHERE
|
||||
|
@ -3160,6 +3163,7 @@ func (q *sqlQuerier) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUI
|
|||
&i.Tags,
|
||||
&i.ErrorCode,
|
||||
&i.TraceMetadata,
|
||||
&i.JobStatus,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3194,7 +3198,7 @@ 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,
|
||||
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, pj.job_status,
|
||||
COALESCE(qp.queue_position, 0) AS queue_position,
|
||||
COALESCE(qs.count, 0) AS queue_size
|
||||
FROM
|
||||
|
@ -3241,6 +3245,7 @@ func (q *sqlQuerier) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Contex
|
|||
&i.ProvisionerJob.Tags,
|
||||
&i.ProvisionerJob.ErrorCode,
|
||||
&i.ProvisionerJob.TraceMetadata,
|
||||
&i.ProvisionerJob.JobStatus,
|
||||
&i.QueuePosition,
|
||||
&i.QueueSize,
|
||||
); err != nil {
|
||||
|
@ -3258,7 +3263,7 @@ func (q *sqlQuerier) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Contex
|
|||
}
|
||||
|
||||
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
|
||||
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, job_status FROM provisioner_jobs WHERE created_at > $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]ProvisionerJob, error) {
|
||||
|
@ -3289,6 +3294,7 @@ func (q *sqlQuerier) GetProvisionerJobsCreatedAfter(ctx context.Context, created
|
|||
&i.Tags,
|
||||
&i.ErrorCode,
|
||||
&i.TraceMetadata,
|
||||
&i.JobStatus,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3320,7 +3326,7 @@ INSERT INTO
|
|||
trace_metadata
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING 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
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING 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, job_status
|
||||
`
|
||||
|
||||
type InsertProvisionerJobParams struct {
|
||||
|
@ -3373,6 +3379,7 @@ func (q *sqlQuerier) InsertProvisionerJob(ctx context.Context, arg InsertProvisi
|
|||
&i.Tags,
|
||||
&i.ErrorCode,
|
||||
&i.TraceMetadata,
|
||||
&i.JobStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
@ -9841,7 +9848,8 @@ LEFT JOIN LATERAL (
|
|||
provisioner_jobs.updated_at,
|
||||
provisioner_jobs.canceled_at,
|
||||
provisioner_jobs.completed_at,
|
||||
provisioner_jobs.error
|
||||
provisioner_jobs.error,
|
||||
provisioner_jobs.job_status
|
||||
FROM
|
||||
workspace_builds
|
||||
LEFT JOIN
|
||||
|
@ -9873,63 +9881,42 @@ WHERE
|
|||
AND CASE
|
||||
WHEN $2 :: text != '' THEN
|
||||
CASE
|
||||
WHEN $2 = 'pending' THEN
|
||||
latest_build.started_at IS NULL
|
||||
-- Some workspace specific status refer to the transition
|
||||
-- type. By default, the standard provisioner job status
|
||||
-- search strings are supported.
|
||||
-- 'running' states
|
||||
WHEN $2 = 'starting' THEN
|
||||
latest_build.started_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.completed_at IS NULL AND
|
||||
latest_build.updated_at - INTERVAL '30 seconds' < NOW() AND
|
||||
latest_build.job_status = 'running'::provisioner_job_status AND
|
||||
latest_build.transition = 'start'::workspace_transition
|
||||
|
||||
WHEN $2 = 'running' THEN
|
||||
latest_build.completed_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.error IS NULL AND
|
||||
latest_build.transition = 'start'::workspace_transition
|
||||
|
||||
WHEN $2 = 'stopping' THEN
|
||||
latest_build.started_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.completed_at IS NULL AND
|
||||
latest_build.updated_at - INTERVAL '30 seconds' < NOW() AND
|
||||
latest_build.job_status = 'running'::provisioner_job_status AND
|
||||
latest_build.transition = 'stop'::workspace_transition
|
||||
|
||||
WHEN $2 = 'stopped' THEN
|
||||
latest_build.completed_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.error IS NULL AND
|
||||
latest_build.transition = 'stop'::workspace_transition
|
||||
|
||||
WHEN $2 = 'failed' THEN
|
||||
(latest_build.canceled_at IS NOT NULL AND
|
||||
latest_build.error IS NOT NULL) OR
|
||||
(latest_build.completed_at IS NOT NULL AND
|
||||
latest_build.error IS NOT NULL)
|
||||
|
||||
WHEN $2 = 'canceling' THEN
|
||||
latest_build.canceled_at IS NOT NULL AND
|
||||
latest_build.completed_at IS NULL
|
||||
|
||||
WHEN $2 = 'canceled' THEN
|
||||
latest_build.canceled_at IS NOT NULL AND
|
||||
latest_build.completed_at IS NOT NULL
|
||||
|
||||
WHEN $2 = 'deleted' THEN
|
||||
latest_build.started_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.completed_at IS NOT NULL AND
|
||||
latest_build.updated_at - INTERVAL '30 seconds' < NOW() AND
|
||||
latest_build.transition = 'delete'::workspace_transition AND
|
||||
-- If the error field is not null, the status is 'failed'
|
||||
latest_build.error IS NULL
|
||||
|
||||
WHEN $2 = 'deleting' THEN
|
||||
latest_build.completed_at IS NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.error IS NULL AND
|
||||
latest_build.job_status = 'running' AND
|
||||
latest_build.transition = 'delete'::workspace_transition
|
||||
|
||||
-- 'succeeded' states
|
||||
WHEN $2 = 'deleted' THEN
|
||||
latest_build.job_status = 'succeeded'::provisioner_job_status AND
|
||||
latest_build.transition = 'delete'::workspace_transition
|
||||
WHEN $2 = 'stopped' THEN
|
||||
latest_build.job_status = 'succeeded'::provisioner_job_status AND
|
||||
latest_build.transition = 'stop'::workspace_transition
|
||||
WHEN $2 = 'started' THEN
|
||||
latest_build.job_status = 'succeeded'::provisioner_job_status AND
|
||||
latest_build.transition = 'start'::workspace_transition
|
||||
|
||||
-- Special case where the provisioner status and workspace status
|
||||
-- differ. A workspace is "running" if the job is "succeeded" and
|
||||
-- the transition is "start". This is because a workspace starts
|
||||
-- running when a job is complete.
|
||||
WHEN $2 = 'running' THEN
|
||||
latest_build.job_status = 'succeeded'::provisioner_job_status AND
|
||||
latest_build.transition = 'start'::workspace_transition
|
||||
|
||||
WHEN $2 != '' THEN
|
||||
-- By default just match the job status exactly
|
||||
latest_build.job_status = $2::provisioner_job_status
|
||||
ELSE
|
||||
true
|
||||
END
|
||||
|
|
|
@ -96,7 +96,8 @@ LEFT JOIN LATERAL (
|
|||
provisioner_jobs.updated_at,
|
||||
provisioner_jobs.canceled_at,
|
||||
provisioner_jobs.completed_at,
|
||||
provisioner_jobs.error
|
||||
provisioner_jobs.error,
|
||||
provisioner_jobs.job_status
|
||||
FROM
|
||||
workspace_builds
|
||||
LEFT JOIN
|
||||
|
@ -128,63 +129,42 @@ WHERE
|
|||
AND CASE
|
||||
WHEN @status :: text != '' THEN
|
||||
CASE
|
||||
WHEN @status = 'pending' THEN
|
||||
latest_build.started_at IS NULL
|
||||
-- Some workspace specific status refer to the transition
|
||||
-- type. By default, the standard provisioner job status
|
||||
-- search strings are supported.
|
||||
-- 'running' states
|
||||
WHEN @status = 'starting' THEN
|
||||
latest_build.started_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.completed_at IS NULL AND
|
||||
latest_build.updated_at - INTERVAL '30 seconds' < NOW() AND
|
||||
latest_build.job_status = 'running'::provisioner_job_status AND
|
||||
latest_build.transition = 'start'::workspace_transition
|
||||
|
||||
WHEN @status = 'running' THEN
|
||||
latest_build.completed_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.error IS NULL AND
|
||||
latest_build.transition = 'start'::workspace_transition
|
||||
|
||||
WHEN @status = 'stopping' THEN
|
||||
latest_build.started_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.completed_at IS NULL AND
|
||||
latest_build.updated_at - INTERVAL '30 seconds' < NOW() AND
|
||||
latest_build.job_status = 'running'::provisioner_job_status AND
|
||||
latest_build.transition = 'stop'::workspace_transition
|
||||
|
||||
WHEN @status = 'stopped' THEN
|
||||
latest_build.completed_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.error IS NULL AND
|
||||
latest_build.transition = 'stop'::workspace_transition
|
||||
|
||||
WHEN @status = 'failed' THEN
|
||||
(latest_build.canceled_at IS NOT NULL AND
|
||||
latest_build.error IS NOT NULL) OR
|
||||
(latest_build.completed_at IS NOT NULL AND
|
||||
latest_build.error IS NOT NULL)
|
||||
|
||||
WHEN @status = 'canceling' THEN
|
||||
latest_build.canceled_at IS NOT NULL AND
|
||||
latest_build.completed_at IS NULL
|
||||
|
||||
WHEN @status = 'canceled' THEN
|
||||
latest_build.canceled_at IS NOT NULL AND
|
||||
latest_build.completed_at IS NOT NULL
|
||||
|
||||
WHEN @status = 'deleted' THEN
|
||||
latest_build.started_at IS NOT NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.completed_at IS NOT NULL AND
|
||||
latest_build.updated_at - INTERVAL '30 seconds' < NOW() AND
|
||||
latest_build.transition = 'delete'::workspace_transition AND
|
||||
-- If the error field is not null, the status is 'failed'
|
||||
latest_build.error IS NULL
|
||||
|
||||
WHEN @status = 'deleting' THEN
|
||||
latest_build.completed_at IS NULL AND
|
||||
latest_build.canceled_at IS NULL AND
|
||||
latest_build.error IS NULL AND
|
||||
latest_build.job_status = 'running' AND
|
||||
latest_build.transition = 'delete'::workspace_transition
|
||||
|
||||
-- 'succeeded' states
|
||||
WHEN @status = 'deleted' THEN
|
||||
latest_build.job_status = 'succeeded'::provisioner_job_status AND
|
||||
latest_build.transition = 'delete'::workspace_transition
|
||||
WHEN @status = 'stopped' THEN
|
||||
latest_build.job_status = 'succeeded'::provisioner_job_status AND
|
||||
latest_build.transition = 'stop'::workspace_transition
|
||||
WHEN @status = 'started' THEN
|
||||
latest_build.job_status = 'succeeded'::provisioner_job_status AND
|
||||
latest_build.transition = 'start'::workspace_transition
|
||||
|
||||
-- Special case where the provisioner status and workspace status
|
||||
-- differ. A workspace is "running" if the job is "succeeded" and
|
||||
-- the transition is "start". This is because a workspace starts
|
||||
-- running when a job is complete.
|
||||
WHEN @status = 'running' THEN
|
||||
latest_build.job_status = 'succeeded'::provisioner_job_status AND
|
||||
latest_build.transition = 'start'::workspace_transition
|
||||
|
||||
WHEN @status != '' THEN
|
||||
-- By default just match the job status exactly
|
||||
latest_build.job_status = @status::provisioner_job_status
|
||||
ELSE
|
||||
true
|
||||
END
|
||||
|
|
|
@ -10,13 +10,14 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"tailscale.com/tailcfg"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/tailnet"
|
||||
|
@ -119,7 +120,7 @@ func Workspaces(ctx context.Context, registerer prometheus.Registerer, db databa
|
|||
|
||||
gauge.Reset()
|
||||
for _, job := range jobs {
|
||||
status := db2sdk.ProvisionerJobStatus(job)
|
||||
status := codersdk.ProvisionerJobStatus(job.JobStatus)
|
||||
gauge.WithLabelValues(string(status)).Add(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import (
|
|||
"cdr.dev/slog"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/pubsub"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
|
@ -258,7 +257,7 @@ func convertProvisionerJob(pj database.GetProvisionerJobsByIDsWithQueuePositionR
|
|||
if provisionerJob.WorkerID.Valid {
|
||||
job.WorkerID = &provisionerJob.WorkerID.UUID
|
||||
}
|
||||
job.Status = db2sdk.ProvisionerJobStatus(provisionerJob)
|
||||
job.Status = codersdk.ProvisionerJobStatus(pj.ProvisionerJob.JobStatus)
|
||||
|
||||
return job
|
||||
}
|
||||
|
@ -282,7 +281,7 @@ func fetchAndWriteLogs(ctx context.Context, db database.Store, jobID uuid.UUID,
|
|||
}
|
||||
|
||||
func jobIsComplete(logger slog.Logger, job database.ProvisionerJob) bool {
|
||||
status := db2sdk.ProvisionerJobStatus(job)
|
||||
status := codersdk.ProvisionerJobStatus(job.JobStatus)
|
||||
switch status {
|
||||
case codersdk.ProvisionerJobCanceled:
|
||||
return true
|
||||
|
|
|
@ -44,8 +44,10 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
|||
expected codersdk.ProvisionerJob
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: database.ProvisionerJob{},
|
||||
name: "empty",
|
||||
input: database.ProvisionerJob{
|
||||
JobStatus: database.ProvisionerJobStatusPending,
|
||||
},
|
||||
expected: codersdk.ProvisionerJob{
|
||||
Status: codersdk.ProvisionerJobPending,
|
||||
},
|
||||
|
@ -55,6 +57,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
|||
input: database.ProvisionerJob{
|
||||
CanceledAt: validNullTimeMock,
|
||||
CompletedAt: invalidNullTimeMock,
|
||||
JobStatus: database.ProvisionerJobStatusCanceling,
|
||||
},
|
||||
expected: codersdk.ProvisionerJob{
|
||||
CanceledAt: &validNullTimeMock.Time,
|
||||
|
@ -67,6 +70,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
|||
CanceledAt: validNullTimeMock,
|
||||
CompletedAt: validNullTimeMock,
|
||||
Error: errorMock,
|
||||
JobStatus: database.ProvisionerJobStatusFailed,
|
||||
},
|
||||
expected: codersdk.ProvisionerJob{
|
||||
CanceledAt: &validNullTimeMock.Time,
|
||||
|
@ -80,6 +84,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
|||
input: database.ProvisionerJob{
|
||||
CanceledAt: validNullTimeMock,
|
||||
CompletedAt: validNullTimeMock,
|
||||
JobStatus: database.ProvisionerJobStatusCanceled,
|
||||
},
|
||||
expected: codersdk.ProvisionerJob{
|
||||
CanceledAt: &validNullTimeMock.Time,
|
||||
|
@ -91,6 +96,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
|||
name: "job pending",
|
||||
input: database.ProvisionerJob{
|
||||
StartedAt: invalidNullTimeMock,
|
||||
JobStatus: database.ProvisionerJobStatusPending,
|
||||
},
|
||||
expected: codersdk.ProvisionerJob{
|
||||
Status: codersdk.ProvisionerJobPending,
|
||||
|
@ -102,6 +108,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
|||
CompletedAt: validNullTimeMock,
|
||||
StartedAt: validNullTimeMock,
|
||||
Error: errorMock,
|
||||
JobStatus: database.ProvisionerJobStatusFailed,
|
||||
},
|
||||
expected: codersdk.ProvisionerJob{
|
||||
CompletedAt: &validNullTimeMock.Time,
|
||||
|
@ -115,6 +122,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
|||
input: database.ProvisionerJob{
|
||||
CompletedAt: validNullTimeMock,
|
||||
StartedAt: validNullTimeMock,
|
||||
JobStatus: database.ProvisionerJobStatusSucceeded,
|
||||
},
|
||||
expected: codersdk.ProvisionerJob{
|
||||
CompletedAt: &validNullTimeMock.Time,
|
||||
|
@ -156,7 +164,8 @@ func Test_logFollower_completeBeforeFollow(t *testing.T) {
|
|||
Time: now.Add(-time.Second),
|
||||
Valid: true,
|
||||
},
|
||||
Error: sql.NullString{},
|
||||
Error: sql.NullString{},
|
||||
JobStatus: database.ProvisionerJobStatusSucceeded,
|
||||
}
|
||||
|
||||
// we need an HTTP server to get a websocket
|
||||
|
@ -217,6 +226,7 @@ func Test_logFollower_completeBeforeSubscribe(t *testing.T) {
|
|||
CanceledAt: sql.NullTime{},
|
||||
CompletedAt: sql.NullTime{},
|
||||
Error: sql.NullString{},
|
||||
JobStatus: database.ProvisionerJobStatusRunning,
|
||||
}
|
||||
|
||||
// we need an HTTP server to get a websocket
|
||||
|
@ -238,6 +248,7 @@ func Test_logFollower_completeBeforeSubscribe(t *testing.T) {
|
|||
Time: now.Add(-time.Millisecond),
|
||||
Valid: true,
|
||||
},
|
||||
JobStatus: database.ProvisionerJobStatusSucceeded,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
@ -293,6 +304,7 @@ func Test_logFollower_EndOfLogs(t *testing.T) {
|
|||
CanceledAt: sql.NullTime{},
|
||||
CompletedAt: sql.NullTime{},
|
||||
Error: sql.NullString{},
|
||||
JobStatus: database.ProvisionerJobStatusRunning,
|
||||
}
|
||||
|
||||
// we need an HTTP server to get a websocket
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/database/pubsub"
|
||||
|
@ -240,7 +239,7 @@ func unhangJob(ctx context.Context, log slog.Logger, db database.Store, pub pubs
|
|||
}
|
||||
if job.CompletedAt.Valid {
|
||||
return jobInelligibleError{
|
||||
Err: xerrors.Errorf("job is completed (status %s)", db2sdk.ProvisionerJobStatus(job)),
|
||||
Err: xerrors.Errorf("job is completed (status %s)", job.JobStatus),
|
||||
}
|
||||
}
|
||||
if job.UpdatedAt.After(time.Now().Add(-HungJobDuration)) {
|
||||
|
|
|
@ -718,7 +718,7 @@ func (b *Builder) checkTemplateJobStatus() error {
|
|||
}
|
||||
}
|
||||
|
||||
templateVersionJobStatus := db2sdk.ProvisionerJobStatus(*templateVersionJob)
|
||||
templateVersionJobStatus := codersdk.ProvisionerJobStatus(templateVersionJob.JobStatus)
|
||||
switch templateVersionJobStatus {
|
||||
case codersdk.ProvisionerJobPending, codersdk.ProvisionerJobRunning:
|
||||
msg := fmt.Sprintf("The provided template version is %s. Wait for it to complete importing!", templateVersionJobStatus)
|
||||
|
@ -755,7 +755,7 @@ func (b *Builder) checkRunningBuild() error {
|
|||
if err != nil {
|
||||
return BuildError{http.StatusInternalServerError, "failed to fetch prior build", err}
|
||||
}
|
||||
if db2sdk.ProvisionerJobStatus(*job).Active() {
|
||||
if codersdk.ProvisionerJobStatus(job.JobStatus).Active() {
|
||||
msg := "A workspace build is already active."
|
||||
return BuildError{
|
||||
http.StatusConflict,
|
||||
|
|
|
@ -64,6 +64,7 @@ const (
|
|||
ProvisionerJobCanceling ProvisionerJobStatus = "canceling"
|
||||
ProvisionerJobCanceled ProvisionerJobStatus = "canceled"
|
||||
ProvisionerJobFailed ProvisionerJobStatus = "failed"
|
||||
ProvisionerJobUnknown ProvisionerJobStatus = "unknown"
|
||||
)
|
||||
|
||||
// JobErrorCode defines the error code returned by job runner.
|
||||
|
|
|
@ -3758,6 +3758,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
| `canceling` |
|
||||
| `canceled` |
|
||||
| `failed` |
|
||||
| `unknown` |
|
||||
|
||||
## codersdk.ProvisionerLogLevel
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
agpl "github.com/coder/coder/v2/coderd/schedule"
|
||||
|
@ -242,7 +241,7 @@ func (s *EnterpriseTemplateScheduleStore) updateWorkspaceBuild(ctx context.Conte
|
|||
if err != nil {
|
||||
return xerrors.Errorf("get provisioner job %q: %w", build.JobID, err)
|
||||
}
|
||||
if db2sdk.ProvisionerJobStatus(job) != codersdk.ProvisionerJobSucceeded {
|
||||
if codersdk.ProvisionerJobStatus(job.JobStatus) != codersdk.ProvisionerJobSucceeded {
|
||||
// Only touch builds that are completed.
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1755,7 +1755,8 @@ export type ProvisionerJobStatus =
|
|||
| "failed"
|
||||
| "pending"
|
||||
| "running"
|
||||
| "succeeded";
|
||||
| "succeeded"
|
||||
| "unknown";
|
||||
export const ProvisionerJobStatuses: ProvisionerJobStatus[] = [
|
||||
"canceled",
|
||||
"canceling",
|
||||
|
@ -1763,6 +1764,7 @@ export const ProvisionerJobStatuses: ProvisionerJobStatus[] = [
|
|||
"pending",
|
||||
"running",
|
||||
"succeeded",
|
||||
"unknown",
|
||||
];
|
||||
|
||||
// From codersdk/workspaces.go
|
||||
|
|
|
@ -55,6 +55,7 @@ export const getStatus = (
|
|||
text: "Canceled",
|
||||
icon: <ErrorIcon />,
|
||||
};
|
||||
case "unknown":
|
||||
case "failed":
|
||||
return {
|
||||
type: "error",
|
||||
|
|
|
@ -51,6 +51,8 @@ export const getDisplayWorkspaceBuildStatus = (
|
|||
color: theme.palette.primary.main,
|
||||
status: DisplayWorkspaceBuildStatusLanguage.running,
|
||||
} as const;
|
||||
// Just handle unknown as failed
|
||||
case "unknown":
|
||||
case "failed":
|
||||
return {
|
||||
type: "error",
|
||||
|
|
Loading…
Reference in New Issue