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",
|
"succeeded",
|
||||||
"canceling",
|
"canceling",
|
||||||
"canceled",
|
"canceled",
|
||||||
"failed"
|
"failed",
|
||||||
|
"unknown"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"ProvisionerJobPending",
|
"ProvisionerJobPending",
|
||||||
|
@ -9132,7 +9133,8 @@ const docTemplate = `{
|
||||||
"ProvisionerJobSucceeded",
|
"ProvisionerJobSucceeded",
|
||||||
"ProvisionerJobCanceling",
|
"ProvisionerJobCanceling",
|
||||||
"ProvisionerJobCanceled",
|
"ProvisionerJobCanceled",
|
||||||
"ProvisionerJobFailed"
|
"ProvisionerJobFailed",
|
||||||
|
"ProvisionerJobUnknown"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"codersdk.ProvisionerLogLevel": {
|
"codersdk.ProvisionerLogLevel": {
|
||||||
|
|
|
@ -8211,7 +8211,8 @@
|
||||||
"succeeded",
|
"succeeded",
|
||||||
"canceling",
|
"canceling",
|
||||||
"canceled",
|
"canceled",
|
||||||
"failed"
|
"failed",
|
||||||
|
"unknown"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"ProvisionerJobPending",
|
"ProvisionerJobPending",
|
||||||
|
@ -8219,7 +8220,8 @@
|
||||||
"ProvisionerJobSucceeded",
|
"ProvisionerJobSucceeded",
|
||||||
"ProvisionerJobCanceling",
|
"ProvisionerJobCanceling",
|
||||||
"ProvisionerJobCanceled",
|
"ProvisionerJobCanceled",
|
||||||
"ProvisionerJobFailed"
|
"ProvisionerJobFailed",
|
||||||
|
"ProvisionerJobUnknown"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"codersdk.ProvisionerLogLevel": {
|
"codersdk.ProvisionerLogLevel": {
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"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/dbauthz"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
|
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
|
||||||
|
@ -303,7 +302,7 @@ func getNextTransition(
|
||||||
// isEligibleForAutostart returns true if the workspace should be autostarted.
|
// 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 {
|
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.
|
// Don't attempt to autostart failed workspaces.
|
||||||
if db2sdk.ProvisionerJobStatus(job) == codersdk.ProvisionerJobFailed {
|
if codersdk.ProvisionerJobStatus(job.JobStatus) == codersdk.ProvisionerJobFailed {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +336,7 @@ func isEligibleForAutostart(ws database.Workspace, build database.WorkspaceBuild
|
||||||
|
|
||||||
// isEligibleForAutostart returns true if the workspace should be autostopped.
|
// isEligibleForAutostart returns true if the workspace should be autostopped.
|
||||||
func isEligibleForAutostop(ws database.Workspace, build database.WorkspaceBuild, job database.ProvisionerJob, currentTick time.Time) bool {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,7 +378,7 @@ func isEligibleForFailedStop(build database.WorkspaceBuild, job database.Provisi
|
||||||
// If the template has specified a failure TLL.
|
// If the template has specified a failure TLL.
|
||||||
return templateSchedule.FailureTTL > 0 &&
|
return templateSchedule.FailureTTL > 0 &&
|
||||||
// And the job resulted in failure.
|
// And the job resulted in failure.
|
||||||
db2sdk.ProvisionerJobStatus(job) == codersdk.ProvisionerJobFailed &&
|
codersdk.ProvisionerJobStatus(job.JobStatus) == codersdk.ProvisionerJobFailed &&
|
||||||
build.Transition == database.WorkspaceTransitionStart &&
|
build.Transition == database.WorkspaceTransitionStart &&
|
||||||
// And sufficient time has elapsed since the job has completed.
|
// And sufficient time has elapsed since the job has completed.
|
||||||
job.CompletedAt.Valid &&
|
job.CompletedAt.Valid &&
|
||||||
|
|
|
@ -71,31 +71,6 @@ func TemplateVersionParameter(param database.TemplateVersionParameter) (codersdk
|
||||||
}, nil
|
}, 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 {
|
func User(user database.User, organizationIDs []uuid.UUID) codersdk.User {
|
||||||
convertedUser := codersdk.User{
|
convertedUser := codersdk.User{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
|
|
|
@ -4,13 +4,17 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
"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/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
"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
|
tc := tc
|
||||||
|
i := i
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
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)
|
require.Equal(t, tc.status, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"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/database/dbtime"
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"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
|
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
|
// 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
|
// 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
|
// 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
|
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 {
|
func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error {
|
||||||
return xerrors.New("AcquireLock must only be called within a transaction")
|
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.StartedAt = arg.StartedAt
|
||||||
provisionerJob.UpdatedAt = arg.StartedAt.Time
|
provisionerJob.UpdatedAt = arg.StartedAt.Time
|
||||||
provisionerJob.WorkerID = arg.WorkerID
|
provisionerJob.WorkerID = arg.WorkerID
|
||||||
|
provisionerJob.JobStatus = provisonerJobStatus(provisionerJob)
|
||||||
q.provisionerJobs[index] = provisionerJob
|
q.provisionerJobs[index] = provisionerJob
|
||||||
return provisionerJob, nil
|
return provisionerJob, nil
|
||||||
}
|
}
|
||||||
|
@ -4077,7 +4097,7 @@ func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, no
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("get provisioner job by ID: %w", err)
|
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)
|
workspaces = append(workspaces, workspace)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -4464,6 +4484,7 @@ func (q *FakeQuerier) InsertProvisionerJob(_ context.Context, arg database.Inser
|
||||||
Input: arg.Input,
|
Input: arg.Input,
|
||||||
Tags: arg.Tags,
|
Tags: arg.Tags,
|
||||||
}
|
}
|
||||||
|
job.JobStatus = provisonerJobStatus(job)
|
||||||
q.provisionerJobs = append(q.provisionerJobs, job)
|
q.provisionerJobs = append(q.provisionerJobs, job)
|
||||||
return job, nil
|
return job, nil
|
||||||
}
|
}
|
||||||
|
@ -5393,6 +5414,7 @@ func (q *FakeQuerier) UpdateProvisionerJobByID(_ context.Context, arg database.U
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
job.UpdatedAt = arg.UpdatedAt
|
job.UpdatedAt = arg.UpdatedAt
|
||||||
|
job.JobStatus = provisonerJobStatus(job)
|
||||||
q.provisionerJobs[index] = job
|
q.provisionerJobs[index] = job
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5413,6 +5435,7 @@ func (q *FakeQuerier) UpdateProvisionerJobWithCancelByID(_ context.Context, arg
|
||||||
}
|
}
|
||||||
job.CanceledAt = arg.CanceledAt
|
job.CanceledAt = arg.CanceledAt
|
||||||
job.CompletedAt = arg.CompletedAt
|
job.CompletedAt = arg.CompletedAt
|
||||||
|
job.JobStatus = provisonerJobStatus(job)
|
||||||
q.provisionerJobs[index] = job
|
q.provisionerJobs[index] = job
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5435,6 +5458,7 @@ func (q *FakeQuerier) UpdateProvisionerJobWithCompleteByID(_ context.Context, ar
|
||||||
job.CompletedAt = arg.CompletedAt
|
job.CompletedAt = arg.CompletedAt
|
||||||
job.Error = arg.Error
|
job.Error = arg.Error
|
||||||
job.ErrorCode = arg.ErrorCode
|
job.ErrorCode = arg.ErrorCode
|
||||||
|
job.JobStatus = provisonerJobStatus(job)
|
||||||
q.provisionerJobs[index] = job
|
q.provisionerJobs[index] = job
|
||||||
return nil
|
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.
|
// This logic should match the logic in the workspace.sql file.
|
||||||
var statusMatch bool
|
var statusMatch bool
|
||||||
switch database.WorkspaceStatus(arg.Status) {
|
switch database.WorkspaceStatus(arg.Status) {
|
||||||
case database.WorkspaceStatusPending:
|
|
||||||
statusMatch = isNull(job.StartedAt)
|
|
||||||
case database.WorkspaceStatusStarting:
|
case database.WorkspaceStatusStarting:
|
||||||
statusMatch = isNotNull(job.StartedAt) &&
|
statusMatch = job.JobStatus == database.ProvisionerJobStatusRunning &&
|
||||||
isNull(job.CanceledAt) &&
|
|
||||||
isNull(job.CompletedAt) &&
|
|
||||||
time.Since(job.UpdatedAt) < 30*time.Second &&
|
|
||||||
build.Transition == database.WorkspaceTransitionStart
|
build.Transition == database.WorkspaceTransitionStart
|
||||||
|
|
||||||
case database.WorkspaceStatusRunning:
|
|
||||||
statusMatch = isNotNull(job.CompletedAt) &&
|
|
||||||
isNull(job.CanceledAt) &&
|
|
||||||
isNull(job.Error) &&
|
|
||||||
build.Transition == database.WorkspaceTransitionStart
|
|
||||||
|
|
||||||
case database.WorkspaceStatusStopping:
|
case database.WorkspaceStatusStopping:
|
||||||
statusMatch = isNotNull(job.StartedAt) &&
|
statusMatch = job.JobStatus == database.ProvisionerJobStatusRunning &&
|
||||||
isNull(job.CanceledAt) &&
|
|
||||||
isNull(job.CompletedAt) &&
|
|
||||||
time.Since(job.UpdatedAt) < 30*time.Second &&
|
|
||||||
build.Transition == database.WorkspaceTransitionStop
|
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:
|
case database.WorkspaceStatusDeleting:
|
||||||
statusMatch = isNull(job.CompletedAt) &&
|
statusMatch = job.JobStatus == database.ProvisionerJobStatusRunning &&
|
||||||
isNull(job.CanceledAt) &&
|
|
||||||
isNull(job.Error) &&
|
|
||||||
build.Transition == database.WorkspaceTransitionDelete
|
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:
|
default:
|
||||||
return nil, xerrors.Errorf("unknown workspace status in filter: %q", arg.Status)
|
statusMatch = job.JobStatus == database.ProvisionerJobStatus(arg.Status)
|
||||||
}
|
}
|
||||||
if !statusMatch {
|
if !statusMatch {
|
||||||
continue
|
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
|
// 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.
|
// 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 {
|
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.
|
// Always set some tags to prevent Acquire from grabbing jobs it should not.
|
||||||
if !orig.StartedAt.Time.IsZero() {
|
if !orig.StartedAt.Time.IsZero() {
|
||||||
if orig.Tags == nil {
|
if orig.Tags == nil {
|
||||||
orig.Tags = make(database.StringMap)
|
orig.Tags = make(database.StringMap)
|
||||||
}
|
}
|
||||||
// Make sure when we acquire the job, we only get this one.
|
// 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{
|
job, err := db.InsertProvisionerJob(genCtx, database.InsertProvisionerJobParams{
|
||||||
ID: jobID,
|
ID: jobID,
|
||||||
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
|
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{},
|
WorkerID: uuid.NullUUID{},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
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 != "" {
|
if !orig.CompletedAt.Time.IsZero() || orig.Error.String != "" {
|
||||||
|
|
|
@ -89,6 +89,18 @@ CREATE TYPE parameter_type_system AS ENUM (
|
||||||
'hcl'
|
'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 (
|
CREATE TYPE provisioner_job_type AS ENUM (
|
||||||
'template_version_import',
|
'template_version_import',
|
||||||
'workspace_build',
|
'workspace_build',
|
||||||
|
@ -500,9 +512,27 @@ CREATE TABLE provisioner_jobs (
|
||||||
file_id uuid NOT NULL,
|
file_id uuid NOT NULL,
|
||||||
tags jsonb DEFAULT '{"scope": "organization"}'::jsonb NOT NULL,
|
tags jsonb DEFAULT '{"scope": "organization"}'::jsonb NOT NULL,
|
||||||
error_code text,
|
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 (
|
CREATE TABLE replicas (
|
||||||
id uuid NOT NULL,
|
id uuid NOT NULL,
|
||||||
created_at timestamp with time zone 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
|
type ProvisionerJobType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1671,6 +1745,8 @@ type ProvisionerJob struct {
|
||||||
Tags StringMap `db:"tags" json:"tags"`
|
Tags StringMap `db:"tags" json:"tags"`
|
||||||
ErrorCode sql.NullString `db:"error_code" json:"error_code"`
|
ErrorCode sql.NullString `db:"error_code" json:"error_code"`
|
||||||
TraceMetadata pqtype.NullRawMessage `db:"trace_metadata" json:"trace_metadata"`
|
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 {
|
type ProvisionerJobLog struct {
|
||||||
|
|
|
@ -2988,7 +2988,7 @@ WHERE
|
||||||
SKIP LOCKED
|
SKIP LOCKED
|
||||||
LIMIT
|
LIMIT
|
||||||
1
|
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 {
|
type AcquireProvisionerJobParams struct {
|
||||||
|
@ -3031,13 +3031,14 @@ func (q *sqlQuerier) AcquireProvisionerJob(ctx context.Context, arg AcquireProvi
|
||||||
&i.Tags,
|
&i.Tags,
|
||||||
&i.ErrorCode,
|
&i.ErrorCode,
|
||||||
&i.TraceMetadata,
|
&i.TraceMetadata,
|
||||||
|
&i.JobStatus,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getHungProvisionerJobs = `-- name: GetHungProvisionerJobs :many
|
const getHungProvisionerJobs = `-- name: GetHungProvisionerJobs :many
|
||||||
SELECT
|
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
|
FROM
|
||||||
provisioner_jobs
|
provisioner_jobs
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -3074,6 +3075,7 @@ func (q *sqlQuerier) GetHungProvisionerJobs(ctx context.Context, updatedAt time.
|
||||||
&i.Tags,
|
&i.Tags,
|
||||||
&i.ErrorCode,
|
&i.ErrorCode,
|
||||||
&i.TraceMetadata,
|
&i.TraceMetadata,
|
||||||
|
&i.JobStatus,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3090,7 +3092,7 @@ func (q *sqlQuerier) GetHungProvisionerJobs(ctx context.Context, updatedAt time.
|
||||||
|
|
||||||
const getProvisionerJobByID = `-- name: GetProvisionerJobByID :one
|
const getProvisionerJobByID = `-- name: GetProvisionerJobByID :one
|
||||||
SELECT
|
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
|
FROM
|
||||||
provisioner_jobs
|
provisioner_jobs
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -3119,13 +3121,14 @@ func (q *sqlQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (P
|
||||||
&i.Tags,
|
&i.Tags,
|
||||||
&i.ErrorCode,
|
&i.ErrorCode,
|
||||||
&i.TraceMetadata,
|
&i.TraceMetadata,
|
||||||
|
&i.JobStatus,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProvisionerJobsByIDs = `-- name: GetProvisionerJobsByIDs :many
|
const getProvisionerJobsByIDs = `-- name: GetProvisionerJobsByIDs :many
|
||||||
SELECT
|
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
|
FROM
|
||||||
provisioner_jobs
|
provisioner_jobs
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -3160,6 +3163,7 @@ func (q *sqlQuerier) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUI
|
||||||
&i.Tags,
|
&i.Tags,
|
||||||
&i.ErrorCode,
|
&i.ErrorCode,
|
||||||
&i.TraceMetadata,
|
&i.TraceMetadata,
|
||||||
|
&i.JobStatus,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3194,7 +3198,7 @@ queue_size AS (
|
||||||
SELECT COUNT(*) as count FROM unstarted_jobs
|
SELECT COUNT(*) as count FROM unstarted_jobs
|
||||||
)
|
)
|
||||||
SELECT
|
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(qp.queue_position, 0) AS queue_position,
|
||||||
COALESCE(qs.count, 0) AS queue_size
|
COALESCE(qs.count, 0) AS queue_size
|
||||||
FROM
|
FROM
|
||||||
|
@ -3241,6 +3245,7 @@ func (q *sqlQuerier) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Contex
|
||||||
&i.ProvisionerJob.Tags,
|
&i.ProvisionerJob.Tags,
|
||||||
&i.ProvisionerJob.ErrorCode,
|
&i.ProvisionerJob.ErrorCode,
|
||||||
&i.ProvisionerJob.TraceMetadata,
|
&i.ProvisionerJob.TraceMetadata,
|
||||||
|
&i.ProvisionerJob.JobStatus,
|
||||||
&i.QueuePosition,
|
&i.QueuePosition,
|
||||||
&i.QueueSize,
|
&i.QueueSize,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -3258,7 +3263,7 @@ func (q *sqlQuerier) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Contex
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProvisionerJobsCreatedAfter = `-- name: GetProvisionerJobsCreatedAfter :many
|
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) {
|
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.Tags,
|
||||||
&i.ErrorCode,
|
&i.ErrorCode,
|
||||||
&i.TraceMetadata,
|
&i.TraceMetadata,
|
||||||
|
&i.JobStatus,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3320,7 +3326,7 @@ INSERT INTO
|
||||||
trace_metadata
|
trace_metadata
|
||||||
)
|
)
|
||||||
VALUES
|
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 {
|
type InsertProvisionerJobParams struct {
|
||||||
|
@ -3373,6 +3379,7 @@ func (q *sqlQuerier) InsertProvisionerJob(ctx context.Context, arg InsertProvisi
|
||||||
&i.Tags,
|
&i.Tags,
|
||||||
&i.ErrorCode,
|
&i.ErrorCode,
|
||||||
&i.TraceMetadata,
|
&i.TraceMetadata,
|
||||||
|
&i.JobStatus,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
@ -9841,7 +9848,8 @@ LEFT JOIN LATERAL (
|
||||||
provisioner_jobs.updated_at,
|
provisioner_jobs.updated_at,
|
||||||
provisioner_jobs.canceled_at,
|
provisioner_jobs.canceled_at,
|
||||||
provisioner_jobs.completed_at,
|
provisioner_jobs.completed_at,
|
||||||
provisioner_jobs.error
|
provisioner_jobs.error,
|
||||||
|
provisioner_jobs.job_status
|
||||||
FROM
|
FROM
|
||||||
workspace_builds
|
workspace_builds
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
|
@ -9873,63 +9881,42 @@ WHERE
|
||||||
AND CASE
|
AND CASE
|
||||||
WHEN $2 :: text != '' THEN
|
WHEN $2 :: text != '' THEN
|
||||||
CASE
|
CASE
|
||||||
WHEN $2 = 'pending' THEN
|
-- Some workspace specific status refer to the transition
|
||||||
latest_build.started_at IS NULL
|
-- type. By default, the standard provisioner job status
|
||||||
|
-- search strings are supported.
|
||||||
|
-- 'running' states
|
||||||
WHEN $2 = 'starting' THEN
|
WHEN $2 = 'starting' THEN
|
||||||
latest_build.started_at IS NOT NULL AND
|
latest_build.job_status = 'running'::provisioner_job_status 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.transition = 'start'::workspace_transition
|
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
|
WHEN $2 = 'stopping' THEN
|
||||||
latest_build.started_at IS NOT NULL AND
|
latest_build.job_status = 'running'::provisioner_job_status 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.transition = 'stop'::workspace_transition
|
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
|
WHEN $2 = 'deleting' THEN
|
||||||
latest_build.completed_at IS NULL AND
|
latest_build.job_status = 'running' AND
|
||||||
latest_build.canceled_at IS NULL AND
|
|
||||||
latest_build.error IS NULL AND
|
|
||||||
latest_build.transition = 'delete'::workspace_transition
|
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
|
ELSE
|
||||||
true
|
true
|
||||||
END
|
END
|
||||||
|
|
|
@ -96,7 +96,8 @@ LEFT JOIN LATERAL (
|
||||||
provisioner_jobs.updated_at,
|
provisioner_jobs.updated_at,
|
||||||
provisioner_jobs.canceled_at,
|
provisioner_jobs.canceled_at,
|
||||||
provisioner_jobs.completed_at,
|
provisioner_jobs.completed_at,
|
||||||
provisioner_jobs.error
|
provisioner_jobs.error,
|
||||||
|
provisioner_jobs.job_status
|
||||||
FROM
|
FROM
|
||||||
workspace_builds
|
workspace_builds
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
|
@ -128,63 +129,42 @@ WHERE
|
||||||
AND CASE
|
AND CASE
|
||||||
WHEN @status :: text != '' THEN
|
WHEN @status :: text != '' THEN
|
||||||
CASE
|
CASE
|
||||||
WHEN @status = 'pending' THEN
|
-- Some workspace specific status refer to the transition
|
||||||
latest_build.started_at IS NULL
|
-- type. By default, the standard provisioner job status
|
||||||
|
-- search strings are supported.
|
||||||
|
-- 'running' states
|
||||||
WHEN @status = 'starting' THEN
|
WHEN @status = 'starting' THEN
|
||||||
latest_build.started_at IS NOT NULL AND
|
latest_build.job_status = 'running'::provisioner_job_status 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.transition = 'start'::workspace_transition
|
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
|
WHEN @status = 'stopping' THEN
|
||||||
latest_build.started_at IS NOT NULL AND
|
latest_build.job_status = 'running'::provisioner_job_status 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.transition = 'stop'::workspace_transition
|
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
|
WHEN @status = 'deleting' THEN
|
||||||
latest_build.completed_at IS NULL AND
|
latest_build.job_status = 'running' AND
|
||||||
latest_build.canceled_at IS NULL AND
|
|
||||||
latest_build.error IS NULL AND
|
|
||||||
latest_build.transition = 'delete'::workspace_transition
|
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
|
ELSE
|
||||||
true
|
true
|
||||||
END
|
END
|
||||||
|
|
|
@ -10,13 +10,14 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"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/dbauthz"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/tailnet"
|
"github.com/coder/coder/v2/tailnet"
|
||||||
|
@ -119,7 +120,7 @@ func Workspaces(ctx context.Context, registerer prometheus.Registerer, db databa
|
||||||
|
|
||||||
gauge.Reset()
|
gauge.Reset()
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
status := db2sdk.ProvisionerJobStatus(job)
|
status := codersdk.ProvisionerJobStatus(job.JobStatus)
|
||||||
gauge.WithLabelValues(string(status)).Add(1)
|
gauge.WithLabelValues(string(status)).Add(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"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/dbauthz"
|
||||||
"github.com/coder/coder/v2/coderd/database/pubsub"
|
"github.com/coder/coder/v2/coderd/database/pubsub"
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
|
@ -258,7 +257,7 @@ func convertProvisionerJob(pj database.GetProvisionerJobsByIDsWithQueuePositionR
|
||||||
if provisionerJob.WorkerID.Valid {
|
if provisionerJob.WorkerID.Valid {
|
||||||
job.WorkerID = &provisionerJob.WorkerID.UUID
|
job.WorkerID = &provisionerJob.WorkerID.UUID
|
||||||
}
|
}
|
||||||
job.Status = db2sdk.ProvisionerJobStatus(provisionerJob)
|
job.Status = codersdk.ProvisionerJobStatus(pj.ProvisionerJob.JobStatus)
|
||||||
|
|
||||||
return job
|
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 {
|
func jobIsComplete(logger slog.Logger, job database.ProvisionerJob) bool {
|
||||||
status := db2sdk.ProvisionerJobStatus(job)
|
status := codersdk.ProvisionerJobStatus(job.JobStatus)
|
||||||
switch status {
|
switch status {
|
||||||
case codersdk.ProvisionerJobCanceled:
|
case codersdk.ProvisionerJobCanceled:
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -44,8 +44,10 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
||||||
expected codersdk.ProvisionerJob
|
expected codersdk.ProvisionerJob
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty",
|
name: "empty",
|
||||||
input: database.ProvisionerJob{},
|
input: database.ProvisionerJob{
|
||||||
|
JobStatus: database.ProvisionerJobStatusPending,
|
||||||
|
},
|
||||||
expected: codersdk.ProvisionerJob{
|
expected: codersdk.ProvisionerJob{
|
||||||
Status: codersdk.ProvisionerJobPending,
|
Status: codersdk.ProvisionerJobPending,
|
||||||
},
|
},
|
||||||
|
@ -55,6 +57,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
||||||
input: database.ProvisionerJob{
|
input: database.ProvisionerJob{
|
||||||
CanceledAt: validNullTimeMock,
|
CanceledAt: validNullTimeMock,
|
||||||
CompletedAt: invalidNullTimeMock,
|
CompletedAt: invalidNullTimeMock,
|
||||||
|
JobStatus: database.ProvisionerJobStatusCanceling,
|
||||||
},
|
},
|
||||||
expected: codersdk.ProvisionerJob{
|
expected: codersdk.ProvisionerJob{
|
||||||
CanceledAt: &validNullTimeMock.Time,
|
CanceledAt: &validNullTimeMock.Time,
|
||||||
|
@ -67,6 +70,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
||||||
CanceledAt: validNullTimeMock,
|
CanceledAt: validNullTimeMock,
|
||||||
CompletedAt: validNullTimeMock,
|
CompletedAt: validNullTimeMock,
|
||||||
Error: errorMock,
|
Error: errorMock,
|
||||||
|
JobStatus: database.ProvisionerJobStatusFailed,
|
||||||
},
|
},
|
||||||
expected: codersdk.ProvisionerJob{
|
expected: codersdk.ProvisionerJob{
|
||||||
CanceledAt: &validNullTimeMock.Time,
|
CanceledAt: &validNullTimeMock.Time,
|
||||||
|
@ -80,6 +84,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
||||||
input: database.ProvisionerJob{
|
input: database.ProvisionerJob{
|
||||||
CanceledAt: validNullTimeMock,
|
CanceledAt: validNullTimeMock,
|
||||||
CompletedAt: validNullTimeMock,
|
CompletedAt: validNullTimeMock,
|
||||||
|
JobStatus: database.ProvisionerJobStatusCanceled,
|
||||||
},
|
},
|
||||||
expected: codersdk.ProvisionerJob{
|
expected: codersdk.ProvisionerJob{
|
||||||
CanceledAt: &validNullTimeMock.Time,
|
CanceledAt: &validNullTimeMock.Time,
|
||||||
|
@ -91,6 +96,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
||||||
name: "job pending",
|
name: "job pending",
|
||||||
input: database.ProvisionerJob{
|
input: database.ProvisionerJob{
|
||||||
StartedAt: invalidNullTimeMock,
|
StartedAt: invalidNullTimeMock,
|
||||||
|
JobStatus: database.ProvisionerJobStatusPending,
|
||||||
},
|
},
|
||||||
expected: codersdk.ProvisionerJob{
|
expected: codersdk.ProvisionerJob{
|
||||||
Status: codersdk.ProvisionerJobPending,
|
Status: codersdk.ProvisionerJobPending,
|
||||||
|
@ -102,6 +108,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
||||||
CompletedAt: validNullTimeMock,
|
CompletedAt: validNullTimeMock,
|
||||||
StartedAt: validNullTimeMock,
|
StartedAt: validNullTimeMock,
|
||||||
Error: errorMock,
|
Error: errorMock,
|
||||||
|
JobStatus: database.ProvisionerJobStatusFailed,
|
||||||
},
|
},
|
||||||
expected: codersdk.ProvisionerJob{
|
expected: codersdk.ProvisionerJob{
|
||||||
CompletedAt: &validNullTimeMock.Time,
|
CompletedAt: &validNullTimeMock.Time,
|
||||||
|
@ -115,6 +122,7 @@ func TestConvertProvisionerJob_Unit(t *testing.T) {
|
||||||
input: database.ProvisionerJob{
|
input: database.ProvisionerJob{
|
||||||
CompletedAt: validNullTimeMock,
|
CompletedAt: validNullTimeMock,
|
||||||
StartedAt: validNullTimeMock,
|
StartedAt: validNullTimeMock,
|
||||||
|
JobStatus: database.ProvisionerJobStatusSucceeded,
|
||||||
},
|
},
|
||||||
expected: codersdk.ProvisionerJob{
|
expected: codersdk.ProvisionerJob{
|
||||||
CompletedAt: &validNullTimeMock.Time,
|
CompletedAt: &validNullTimeMock.Time,
|
||||||
|
@ -156,7 +164,8 @@ func Test_logFollower_completeBeforeFollow(t *testing.T) {
|
||||||
Time: now.Add(-time.Second),
|
Time: now.Add(-time.Second),
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
Error: sql.NullString{},
|
Error: sql.NullString{},
|
||||||
|
JobStatus: database.ProvisionerJobStatusSucceeded,
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need an HTTP server to get a websocket
|
// we need an HTTP server to get a websocket
|
||||||
|
@ -217,6 +226,7 @@ func Test_logFollower_completeBeforeSubscribe(t *testing.T) {
|
||||||
CanceledAt: sql.NullTime{},
|
CanceledAt: sql.NullTime{},
|
||||||
CompletedAt: sql.NullTime{},
|
CompletedAt: sql.NullTime{},
|
||||||
Error: sql.NullString{},
|
Error: sql.NullString{},
|
||||||
|
JobStatus: database.ProvisionerJobStatusRunning,
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need an HTTP server to get a websocket
|
// 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),
|
Time: now.Add(-time.Millisecond),
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
|
JobStatus: database.ProvisionerJobStatusSucceeded,
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
@ -293,6 +304,7 @@ func Test_logFollower_EndOfLogs(t *testing.T) {
|
||||||
CanceledAt: sql.NullTime{},
|
CanceledAt: sql.NullTime{},
|
||||||
CompletedAt: sql.NullTime{},
|
CompletedAt: sql.NullTime{},
|
||||||
Error: sql.NullString{},
|
Error: sql.NullString{},
|
||||||
|
JobStatus: database.ProvisionerJobStatusRunning,
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need an HTTP server to get a websocket
|
// we need an HTTP server to get a websocket
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"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/dbauthz"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/coderd/database/pubsub"
|
"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 {
|
if job.CompletedAt.Valid {
|
||||||
return jobInelligibleError{
|
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)) {
|
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 {
|
switch templateVersionJobStatus {
|
||||||
case codersdk.ProvisionerJobPending, codersdk.ProvisionerJobRunning:
|
case codersdk.ProvisionerJobPending, codersdk.ProvisionerJobRunning:
|
||||||
msg := fmt.Sprintf("The provided template version is %s. Wait for it to complete importing!", templateVersionJobStatus)
|
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 {
|
if err != nil {
|
||||||
return BuildError{http.StatusInternalServerError, "failed to fetch prior build", err}
|
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."
|
msg := "A workspace build is already active."
|
||||||
return BuildError{
|
return BuildError{
|
||||||
http.StatusConflict,
|
http.StatusConflict,
|
||||||
|
|
|
@ -64,6 +64,7 @@ const (
|
||||||
ProvisionerJobCanceling ProvisionerJobStatus = "canceling"
|
ProvisionerJobCanceling ProvisionerJobStatus = "canceling"
|
||||||
ProvisionerJobCanceled ProvisionerJobStatus = "canceled"
|
ProvisionerJobCanceled ProvisionerJobStatus = "canceled"
|
||||||
ProvisionerJobFailed ProvisionerJobStatus = "failed"
|
ProvisionerJobFailed ProvisionerJobStatus = "failed"
|
||||||
|
ProvisionerJobUnknown ProvisionerJobStatus = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JobErrorCode defines the error code returned by job runner.
|
// 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` |
|
| `canceling` |
|
||||||
| `canceled` |
|
| `canceled` |
|
||||||
| `failed` |
|
| `failed` |
|
||||||
|
| `unknown` |
|
||||||
|
|
||||||
## codersdk.ProvisionerLogLevel
|
## codersdk.ProvisionerLogLevel
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"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/dbauthz"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
agpl "github.com/coder/coder/v2/coderd/schedule"
|
agpl "github.com/coder/coder/v2/coderd/schedule"
|
||||||
|
@ -242,7 +241,7 @@ func (s *EnterpriseTemplateScheduleStore) updateWorkspaceBuild(ctx context.Conte
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("get provisioner job %q: %w", build.JobID, err)
|
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.
|
// Only touch builds that are completed.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1755,7 +1755,8 @@ export type ProvisionerJobStatus =
|
||||||
| "failed"
|
| "failed"
|
||||||
| "pending"
|
| "pending"
|
||||||
| "running"
|
| "running"
|
||||||
| "succeeded";
|
| "succeeded"
|
||||||
|
| "unknown";
|
||||||
export const ProvisionerJobStatuses: ProvisionerJobStatus[] = [
|
export const ProvisionerJobStatuses: ProvisionerJobStatus[] = [
|
||||||
"canceled",
|
"canceled",
|
||||||
"canceling",
|
"canceling",
|
||||||
|
@ -1763,6 +1764,7 @@ export const ProvisionerJobStatuses: ProvisionerJobStatus[] = [
|
||||||
"pending",
|
"pending",
|
||||||
"running",
|
"running",
|
||||||
"succeeded",
|
"succeeded",
|
||||||
|
"unknown",
|
||||||
];
|
];
|
||||||
|
|
||||||
// From codersdk/workspaces.go
|
// From codersdk/workspaces.go
|
||||||
|
|
|
@ -55,6 +55,7 @@ export const getStatus = (
|
||||||
text: "Canceled",
|
text: "Canceled",
|
||||||
icon: <ErrorIcon />,
|
icon: <ErrorIcon />,
|
||||||
};
|
};
|
||||||
|
case "unknown":
|
||||||
case "failed":
|
case "failed":
|
||||||
return {
|
return {
|
||||||
type: "error",
|
type: "error",
|
||||||
|
|
|
@ -51,6 +51,8 @@ export const getDisplayWorkspaceBuildStatus = (
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
status: DisplayWorkspaceBuildStatusLanguage.running,
|
status: DisplayWorkspaceBuildStatusLanguage.running,
|
||||||
} as const;
|
} as const;
|
||||||
|
// Just handle unknown as failed
|
||||||
|
case "unknown":
|
||||||
case "failed":
|
case "failed":
|
||||||
return {
|
return {
|
||||||
type: "error",
|
type: "error",
|
||||||
|
|
Loading…
Reference in New Issue