diff --git a/coderd/workspaces.go b/coderd/workspaces.go index aa032a947d..0cb72cdcec 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -996,9 +996,44 @@ func convertWorkspace( AutostartSchedule: autostartSchedule, TTLMillis: ttlMillis, LastUsedAt: workspace.LastUsedAt, + Status: convertStatus(workspaceBuild), } } +func convertStatus(build codersdk.WorkspaceBuild) codersdk.WorkspaceStatus { + switch build.Job.Status { + case codersdk.ProvisionerJobPending: + return codersdk.WorkspaceStatusPending + case codersdk.ProvisionerJobRunning: + switch build.Transition { + case codersdk.WorkspaceTransitionStart: + return codersdk.WorkspaceStatusStarting + case codersdk.WorkspaceTransitionStop: + return codersdk.WorkspaceStatusStopping + case codersdk.WorkspaceTransitionDelete: + return codersdk.WorkspaceStatusDeleting + } + case codersdk.ProvisionerJobSucceeded: + switch build.Transition { + case codersdk.WorkspaceTransitionStart: + return codersdk.WorkspaceStatusRunning + case codersdk.WorkspaceTransitionStop: + return codersdk.WorkspaceStatusStopped + case codersdk.WorkspaceTransitionDelete: + return codersdk.WorkspaceStatusDeleted + } + case codersdk.ProvisionerJobCanceling: + return codersdk.WorkspaceStatusCanceling + case codersdk.ProvisionerJobCanceled: + return codersdk.WorkspaceStatusCanceled + case codersdk.ProvisionerJobFailed: + return codersdk.WorkspaceStatusFailed + } + + // return error status since we should never get here + return codersdk.WorkspaceStatusFailed +} + func convertWorkspaceTTLMillis(i sql.NullInt64) *int64 { if !i.Valid { return nil diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index c94c4dd277..d295a48875 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -1256,6 +1256,52 @@ func TestWorkspaceWatcher(t *testing.T) { require.EqualValues(t, codersdk.Workspace{}, <-wc) } +func TestWorkspaceStatus(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + var ( + client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user = coderdtest.CreateFirstUser(t, client) + version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + _ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + ) + + // initial returned state is "pending" + require.EqualValues(t, codersdk.WorkspaceStatusPending, workspace.Status) + + // after successful build is "running" + _ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + workspace, err := client.Workspace(ctx, workspace.ID) + require.NoError(t, err) + require.EqualValues(t, codersdk.WorkspaceStatusRunning, workspace.Status) + + // after successful stop is "stopped" + build := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop) + _ = coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID) + workspace, err = client.Workspace(ctx, workspace.ID) + require.NoError(t, err) + require.EqualValues(t, codersdk.WorkspaceStatusStopped, workspace.Status) + + // after successful cancel is "canceled" + build = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStart) + err = client.CancelWorkspaceBuild(ctx, build.ID) + require.NoError(t, err) + _ = coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID) + workspace, err = client.Workspace(ctx, workspace.ID) + require.NoError(t, err) + require.EqualValues(t, codersdk.WorkspaceStatusCanceled, workspace.Status) + + // after successful delete is "deleted" + build = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionDelete) + _ = coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID) + workspace, err = client.DeletedWorkspace(ctx, workspace.ID) + require.NoError(t, err) + require.EqualValues(t, codersdk.WorkspaceStatusDeleted, workspace.Status) +} + func mustLocation(t *testing.T, location string) *time.Location { t.Helper() loc, err := time.LoadLocation(location) diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index a9a2c68e9c..2757a9a085 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -15,22 +15,38 @@ import ( // Workspace is a deployment of a template. It references a specific // version and can be updated. type Workspace struct { - ID uuid.UUID `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - OwnerID uuid.UUID `json:"owner_id"` - OwnerName string `json:"owner_name"` - TemplateID uuid.UUID `json:"template_id"` - TemplateName string `json:"template_name"` - TemplateIcon string `json:"template_icon"` - LatestBuild WorkspaceBuild `json:"latest_build"` - Outdated bool `json:"outdated"` - Name string `json:"name"` - AutostartSchedule *string `json:"autostart_schedule,omitempty"` - TTLMillis *int64 `json:"ttl_ms,omitempty"` - LastUsedAt time.Time `json:"last_used_at"` + ID uuid.UUID `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + OwnerID uuid.UUID `json:"owner_id"` + OwnerName string `json:"owner_name"` + TemplateID uuid.UUID `json:"template_id"` + TemplateName string `json:"template_name"` + TemplateIcon string `json:"template_icon"` + LatestBuild WorkspaceBuild `json:"latest_build"` + Outdated bool `json:"outdated"` + Name string `json:"name"` + AutostartSchedule *string `json:"autostart_schedule,omitempty"` + TTLMillis *int64 `json:"ttl_ms,omitempty"` + LastUsedAt time.Time `json:"last_used_at"` + Status WorkspaceStatus `json:"status"` } +type WorkspaceStatus string + +const ( + WorkspaceStatusPending WorkspaceStatus = "pending" + WorkspaceStatusStarting WorkspaceStatus = "starting" + WorkspaceStatusRunning WorkspaceStatus = "running" + WorkspaceStatusStopping WorkspaceStatus = "stopping" + WorkspaceStatusStopped WorkspaceStatus = "stopped" + WorkspaceStatusFailed WorkspaceStatus = "failed" + WorkspaceStatusCanceling WorkspaceStatus = "canceling" + WorkspaceStatusCanceled WorkspaceStatus = "canceled" + WorkspaceStatusDeleting WorkspaceStatus = "deleting" + WorkspaceStatusDeleted WorkspaceStatus = "deleted" +) + // CreateWorkspaceBuildRequest provides options to update the latest workspace build. type CreateWorkspaceBuildRequest struct { TemplateVersionID uuid.UUID `json:"template_version_id,omitempty"` diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index caf536b8bc..f8f34b4eaa 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -550,6 +550,7 @@ export interface Workspace { readonly autostart_schedule?: string readonly ttl_ms?: number readonly last_used_at: string + readonly status: WorkspaceStatus } // From codersdk/workspaceresources.go @@ -735,5 +736,18 @@ export type WorkspaceAgentStatus = "connected" | "connecting" | "disconnected" // From codersdk/workspaceapps.go export type WorkspaceAppHealth = "disabled" | "healthy" | "initializing" | "unhealthy" +// From codersdk/workspaces.go +export type WorkspaceStatus = + | "canceled" + | "canceling" + | "deleted" + | "deleting" + | "failed" + | "pending" + | "running" + | "starting" + | "stopped" + | "stopping" + // From codersdk/workspacebuilds.go export type WorkspaceTransition = "delete" | "start" | "stop" diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 87c918baaa..3f5f9ffd08 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -268,6 +268,7 @@ export const MockWorkspace: TypesGen.Workspace = { ttl_ms: 2 * 60 * 60 * 1000, // 2 hours as milliseconds latest_build: MockWorkspaceBuild, last_used_at: "", + status: "running", } export const MockStoppedWorkspace: TypesGen.Workspace = {