mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
chore: rename locked to dormant (#9290)
* chore: rename locked to dormant - The following columns have been updated: - workspace.locked_at -> dormant_at - template.inactivity_ttl -> time_til_dormant - template.locked_ttl -> time_til_dormant_autodelete This change has also been reflected in the SDK. A route has also been updated from /workspaces/<id>/lock to /workspaces/<id>/dormant
This commit is contained in:
140
coderd/apidoc/docs.go
generated
140
coderd/apidoc/docs.go
generated
@ -6082,6 +6082,53 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces/{workspace}/dormant": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Workspaces"
|
||||
],
|
||||
"summary": "Update workspace dormancy status by id.",
|
||||
"operationId": "update-workspace-dormancy-status-by-id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Workspace ID",
|
||||
"name": "workspace",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Make a workspace dormant or active",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.UpdateWorkspaceDormancy"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.Workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces/{workspace}/extend": {
|
||||
"put": {
|
||||
"security": [
|
||||
@ -6129,53 +6176,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces/{workspace}/lock": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Workspaces"
|
||||
],
|
||||
"summary": "Update workspace lock by id.",
|
||||
"operationId": "update-workspace-lock-by-id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Workspace ID",
|
||||
"name": "workspace",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Lock or unlock a workspace",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.UpdateWorkspaceLock"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.Workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces/{workspace}/ttl": {
|
||||
"put": {
|
||||
"security": [
|
||||
@ -7394,6 +7394,10 @@ const docTemplate = `{
|
||||
"description": "DefaultTTLMillis allows optionally specifying the default TTL\nfor all workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"delete_ttl_ms": {
|
||||
"description": "TimeTilDormantAutoDeleteMillis allows optionally specifying the max lifetime before Coder\npermanently deletes dormant workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description is a description of what the template contains. It must be\nless than 128 bytes.",
|
||||
"type": "string"
|
||||
@ -7406,6 +7410,10 @@ const docTemplate = `{
|
||||
"description": "DisplayName is the displayed name of the template.",
|
||||
"type": "string"
|
||||
},
|
||||
"dormant_ttl_ms": {
|
||||
"description": "TimeTilDormantMillis allows optionally specifying the max lifetime before Coder\nlocks inactive workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"failure_ttl_ms": {
|
||||
"description": "FailureTTLMillis allows optionally specifying the max lifetime before Coder\nstops all resources for failed workspaces created from this template.",
|
||||
"type": "integer"
|
||||
@ -7414,14 +7422,6 @@ const docTemplate = `{
|
||||
"description": "Icon is a relative path or external URL that specifies\nan icon to be displayed in the dashboard.",
|
||||
"type": "string"
|
||||
},
|
||||
"inactivity_ttl_ms": {
|
||||
"description": "InactivityTTLMillis allows optionally specifying the max lifetime before Coder\nlocks inactive workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"locked_ttl_ms": {
|
||||
"description": "LockedTTLMillis allows optionally specifying the max lifetime before Coder\npermanently deletes locked workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "TODO(@dean): remove max_ttl once restart_requirement is matured",
|
||||
"type": "integer"
|
||||
@ -9540,7 +9540,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"failure_ttl_ms": {
|
||||
"description": "FailureTTLMillis, InactivityTTLMillis, and LockedTTLMillis are enterprise-only. Their\nvalues are used if your license is entitled to use the advanced\ntemplate scheduling feature.",
|
||||
"description": "FailureTTLMillis, TimeTilDormantMillis, and TimeTilDormantAutoDeleteMillis are enterprise-only. Their\nvalues are used if your license is entitled to use the advanced\ntemplate scheduling feature.",
|
||||
"type": "integer"
|
||||
},
|
||||
"icon": {
|
||||
@ -9550,12 +9550,6 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"inactivity_ttl_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"locked_ttl_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "TODO(@dean): remove max_ttl once restart_requirement is matured",
|
||||
"type": "integer"
|
||||
@ -9581,6 +9575,12 @@ const docTemplate = `{
|
||||
}
|
||||
]
|
||||
},
|
||||
"time_til_dormant_autodelete_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"time_til_dormant_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
@ -10254,10 +10254,10 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateWorkspaceLock": {
|
||||
"codersdk.UpdateWorkspaceDormancy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lock": {
|
||||
"dormant": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
@ -10510,7 +10510,12 @@ const docTemplate = `{
|
||||
"format": "date-time"
|
||||
},
|
||||
"deleting_at": {
|
||||
"description": "DeletingAt indicates the time of the upcoming workspace deletion, if applicable; otherwise it is nil.\nWorkspaces may have impending deletions if Template.InactivityTTL feature is turned on and the workspace is inactive.",
|
||||
"description": "DeletingAt indicates the time at which the workspace will be permanently deleted.\nA workspace is eligible for deletion if it is dormant (a non-nil dormant_at value)\nand a value has been specified for time_til_dormant_autodelete on its template.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"dormant_at": {
|
||||
"description": "DormantAt being non-nil indicates a workspace that is dormant.\nA dormant workspace is no longer accessible must be activated.\nIt is subject to deletion if it breaches\nthe duration of the time_til_ field on its template.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
@ -10533,11 +10538,6 @@ const docTemplate = `{
|
||||
"latest_build": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceBuild"
|
||||
},
|
||||
"locked_at": {
|
||||
"description": "LockedAt being non-nil indicates a workspace that has been locked.\nA locked workspace is no longer accessible by a user and must be\nunlocked by an admin. It is subject to deletion if it breaches\nthe duration of the locked_ttl field on its template.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
128
coderd/apidoc/swagger.json
generated
128
coderd/apidoc/swagger.json
generated
@ -5366,6 +5366,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces/{workspace}/dormant": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": ["application/json"],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Workspaces"],
|
||||
"summary": "Update workspace dormancy status by id.",
|
||||
"operationId": "update-workspace-dormancy-status-by-id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Workspace ID",
|
||||
"name": "workspace",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Make a workspace dormant or active",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.UpdateWorkspaceDormancy"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.Workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces/{workspace}/extend": {
|
||||
"put": {
|
||||
"security": [
|
||||
@ -5407,47 +5448,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces/{workspace}/lock": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": ["application/json"],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Workspaces"],
|
||||
"summary": "Update workspace lock by id.",
|
||||
"operationId": "update-workspace-lock-by-id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Workspace ID",
|
||||
"name": "workspace",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Lock or unlock a workspace",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.UpdateWorkspaceLock"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.Workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces/{workspace}/ttl": {
|
||||
"put": {
|
||||
"security": [
|
||||
@ -6587,6 +6587,10 @@
|
||||
"description": "DefaultTTLMillis allows optionally specifying the default TTL\nfor all workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"delete_ttl_ms": {
|
||||
"description": "TimeTilDormantAutoDeleteMillis allows optionally specifying the max lifetime before Coder\npermanently deletes dormant workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description is a description of what the template contains. It must be\nless than 128 bytes.",
|
||||
"type": "string"
|
||||
@ -6599,6 +6603,10 @@
|
||||
"description": "DisplayName is the displayed name of the template.",
|
||||
"type": "string"
|
||||
},
|
||||
"dormant_ttl_ms": {
|
||||
"description": "TimeTilDormantMillis allows optionally specifying the max lifetime before Coder\nlocks inactive workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"failure_ttl_ms": {
|
||||
"description": "FailureTTLMillis allows optionally specifying the max lifetime before Coder\nstops all resources for failed workspaces created from this template.",
|
||||
"type": "integer"
|
||||
@ -6607,14 +6615,6 @@
|
||||
"description": "Icon is a relative path or external URL that specifies\nan icon to be displayed in the dashboard.",
|
||||
"type": "string"
|
||||
},
|
||||
"inactivity_ttl_ms": {
|
||||
"description": "InactivityTTLMillis allows optionally specifying the max lifetime before Coder\nlocks inactive workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"locked_ttl_ms": {
|
||||
"description": "LockedTTLMillis allows optionally specifying the max lifetime before Coder\npermanently deletes locked workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "TODO(@dean): remove max_ttl once restart_requirement is matured",
|
||||
"type": "integer"
|
||||
@ -8609,7 +8609,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"failure_ttl_ms": {
|
||||
"description": "FailureTTLMillis, InactivityTTLMillis, and LockedTTLMillis are enterprise-only. Their\nvalues are used if your license is entitled to use the advanced\ntemplate scheduling feature.",
|
||||
"description": "FailureTTLMillis, TimeTilDormantMillis, and TimeTilDormantAutoDeleteMillis are enterprise-only. Their\nvalues are used if your license is entitled to use the advanced\ntemplate scheduling feature.",
|
||||
"type": "integer"
|
||||
},
|
||||
"icon": {
|
||||
@ -8619,12 +8619,6 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"inactivity_ttl_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"locked_ttl_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "TODO(@dean): remove max_ttl once restart_requirement is matured",
|
||||
"type": "integer"
|
||||
@ -8648,6 +8642,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"time_til_dormant_autodelete_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"time_til_dormant_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
@ -9274,10 +9274,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateWorkspaceLock": {
|
||||
"codersdk.UpdateWorkspaceDormancy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lock": {
|
||||
"dormant": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
@ -9512,7 +9512,12 @@
|
||||
"format": "date-time"
|
||||
},
|
||||
"deleting_at": {
|
||||
"description": "DeletingAt indicates the time of the upcoming workspace deletion, if applicable; otherwise it is nil.\nWorkspaces may have impending deletions if Template.InactivityTTL feature is turned on and the workspace is inactive.",
|
||||
"description": "DeletingAt indicates the time at which the workspace will be permanently deleted.\nA workspace is eligible for deletion if it is dormant (a non-nil dormant_at value)\nand a value has been specified for time_til_dormant_autodelete on its template.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"dormant_at": {
|
||||
"description": "DormantAt being non-nil indicates a workspace that is dormant.\nA dormant workspace is no longer accessible must be activated.\nIt is subject to deletion if it breaches\nthe duration of the time_til_ field on its template.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
@ -9535,11 +9540,6 @@
|
||||
"latest_build": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceBuild"
|
||||
},
|
||||
"locked_at": {
|
||||
"description": "LockedAt being non-nil indicates a workspace that has been locked.\nA locked workspace is no longer accessible by a user and must be\nunlocked by an admin. It is subject to deletion if it breaches\nthe duration of the locked_ttl field on its template.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -175,35 +175,35 @@ func (e *Executor) runOnce(t time.Time) Stats {
|
||||
}
|
||||
}
|
||||
|
||||
// Lock the workspace if it has breached the template's
|
||||
// Transition the workspace to dormant if it has breached the template's
|
||||
// threshold for inactivity.
|
||||
if reason == database.BuildReasonAutolock {
|
||||
ws, err = tx.UpdateWorkspaceLockedDeletingAt(e.ctx, database.UpdateWorkspaceLockedDeletingAtParams{
|
||||
ws, err = tx.UpdateWorkspaceDormantDeletingAt(e.ctx, database.UpdateWorkspaceDormantDeletingAtParams{
|
||||
ID: ws.ID,
|
||||
LockedAt: sql.NullTime{
|
||||
DormantAt: sql.NullTime{
|
||||
Time: database.Now(),
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(e.ctx, "unable to lock workspace",
|
||||
log.Error(e.ctx, "unable to transition workspace to dormant",
|
||||
slog.F("transition", nextTransition),
|
||||
slog.Error(err),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info(e.ctx, "locked workspace",
|
||||
log.Info(e.ctx, "dormant workspace",
|
||||
slog.F("last_used_at", ws.LastUsedAt),
|
||||
slog.F("inactivity_ttl", templateSchedule.InactivityTTL),
|
||||
slog.F("time_til_dormant", templateSchedule.TimeTilDormant),
|
||||
slog.F("since_last_used_at", time.Since(ws.LastUsedAt)),
|
||||
)
|
||||
}
|
||||
|
||||
if reason == database.BuildReasonAutodelete {
|
||||
log.Info(e.ctx, "deleted workspace",
|
||||
slog.F("locked_at", ws.LockedAt.Time),
|
||||
slog.F("locked_ttl", templateSchedule.LockedTTL),
|
||||
slog.F("dormant_at", ws.DormantAt.Time),
|
||||
slog.F("time_til_dormant_autodelete", templateSchedule.TimeTilDormantAutoDelete),
|
||||
)
|
||||
}
|
||||
|
||||
@ -246,7 +246,7 @@ func (e *Executor) runOnce(t time.Time) Stats {
|
||||
// for this function to return a nil error as well as an empty transition.
|
||||
// In such cases it means no provisioning should occur but the workspace
|
||||
// may be "transitioning" to a new state (such as an inactive, stopped
|
||||
// workspace transitioning to the locked state).
|
||||
// workspace transitioning to the dormant state).
|
||||
func getNextTransition(
|
||||
ws database.Workspace,
|
||||
latestBuild database.WorkspaceBuild,
|
||||
@ -265,13 +265,13 @@ func getNextTransition(
|
||||
return database.WorkspaceTransitionStart, database.BuildReasonAutostart, nil
|
||||
case isEligibleForFailedStop(latestBuild, latestJob, templateSchedule, currentTick):
|
||||
return database.WorkspaceTransitionStop, database.BuildReasonAutostop, nil
|
||||
case isEligibleForLockedStop(ws, templateSchedule, currentTick):
|
||||
case isEligibleForDormantStop(ws, templateSchedule, currentTick):
|
||||
// Only stop started workspaces.
|
||||
if latestBuild.Transition == database.WorkspaceTransitionStart {
|
||||
return database.WorkspaceTransitionStop, database.BuildReasonAutolock, nil
|
||||
}
|
||||
// We shouldn't transition the workspace but we should still
|
||||
// lock it.
|
||||
// make it dormant.
|
||||
return "", database.BuildReasonAutolock, nil
|
||||
|
||||
case isEligibleForDelete(ws, templateSchedule, currentTick):
|
||||
@ -288,8 +288,8 @@ func isEligibleForAutostart(ws database.Workspace, build database.WorkspaceBuild
|
||||
return false
|
||||
}
|
||||
|
||||
// If the workspace is locked we should not autostart it.
|
||||
if ws.LockedAt.Valid {
|
||||
// If the workspace is dormant we should not autostart it.
|
||||
if ws.DormantAt.Valid {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -322,8 +322,8 @@ func isEligibleForAutostop(ws database.Workspace, build database.WorkspaceBuild,
|
||||
return false
|
||||
}
|
||||
|
||||
// If the workspace is locked we should not autostop it.
|
||||
if ws.LockedAt.Valid {
|
||||
// If the workspace is dormant we should not autostop it.
|
||||
if ws.DormantAt.Valid {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -334,23 +334,23 @@ func isEligibleForAutostop(ws database.Workspace, build database.WorkspaceBuild,
|
||||
!currentTick.Before(build.Deadline)
|
||||
}
|
||||
|
||||
// isEligibleForLockedStop returns true if the workspace should be locked
|
||||
// isEligibleForDormantStop returns true if the workspace should be dormant
|
||||
// for breaching the inactivity threshold of the template.
|
||||
func isEligibleForLockedStop(ws database.Workspace, templateSchedule schedule.TemplateScheduleOptions, currentTick time.Time) bool {
|
||||
// Only attempt to lock workspaces not already locked.
|
||||
return !ws.LockedAt.Valid &&
|
||||
// The template must specify an inactivity TTL.
|
||||
templateSchedule.InactivityTTL > 0 &&
|
||||
// The workspace must breach the inactivity TTL.
|
||||
currentTick.Sub(ws.LastUsedAt) > templateSchedule.InactivityTTL
|
||||
func isEligibleForDormantStop(ws database.Workspace, templateSchedule schedule.TemplateScheduleOptions, currentTick time.Time) bool {
|
||||
// Only attempt against workspaces not already dormant.
|
||||
return !ws.DormantAt.Valid &&
|
||||
// The template must specify an time_til_dormant value.
|
||||
templateSchedule.TimeTilDormant > 0 &&
|
||||
// The workspace must breach the time_til_dormant value.
|
||||
currentTick.Sub(ws.LastUsedAt) > templateSchedule.TimeTilDormant
|
||||
}
|
||||
|
||||
func isEligibleForDelete(ws database.Workspace, templateSchedule schedule.TemplateScheduleOptions, currentTick time.Time) bool {
|
||||
// Only attempt to delete locked workspaces.
|
||||
return ws.LockedAt.Valid && ws.DeletingAt.Valid &&
|
||||
// Locked workspaces should only be deleted if a locked_ttl is specified.
|
||||
templateSchedule.LockedTTL > 0 &&
|
||||
// The workspace must breach the locked_ttl.
|
||||
// Only attempt to delete dormant workspaces.
|
||||
return ws.DormantAt.Valid && ws.DeletingAt.Valid &&
|
||||
// Dormant workspaces should only be deleted if a time_til_dormant_autodelete value is specified.
|
||||
templateSchedule.TimeTilDormantAutoDelete > 0 &&
|
||||
// The workspace must breach the time_til_dormant_autodelete value.
|
||||
currentTick.After(ws.DeletingAt.Time)
|
||||
}
|
||||
|
||||
|
@ -737,7 +737,7 @@ func TestExecutorInactiveWorkspace(t *testing.T) {
|
||||
ProvisionApply: echo.ProvisionComplete,
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.InactivityTTLMillis = ptr.Ref[int64](inactiveTTL.Milliseconds())
|
||||
ctr.TimeTilDormantMillis = ptr.Ref[int64](inactiveTTL.Milliseconds())
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
|
@ -855,7 +855,7 @@ func New(options *Options) *API {
|
||||
})
|
||||
r.Get("/watch", api.watchWorkspace)
|
||||
r.Put("/extend", api.putExtendWorkspace)
|
||||
r.Put("/lock", api.putWorkspaceLock)
|
||||
r.Put("/dormant", api.putWorkspaceDormant)
|
||||
})
|
||||
})
|
||||
r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) {
|
||||
|
@ -2636,6 +2636,13 @@ func (q *querier) UpdateWorkspaceDeletedByID(ctx context.Context, arg database.U
|
||||
return deleteQ(q.log, q.auth, fetch, q.db.UpdateWorkspaceDeletedByID)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) {
|
||||
fetch := func(ctx context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) {
|
||||
return q.db.GetWorkspaceByID(ctx, arg.ID)
|
||||
}
|
||||
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateWorkspaceDormantDeletingAt)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceLastUsedAt(ctx context.Context, arg database.UpdateWorkspaceLastUsedAtParams) error {
|
||||
fetch := func(ctx context.Context, arg database.UpdateWorkspaceLastUsedAtParams) (database.Workspace, error) {
|
||||
return q.db.GetWorkspaceByID(ctx, arg.ID)
|
||||
@ -2643,13 +2650,6 @@ func (q *querier) UpdateWorkspaceLastUsedAt(ctx context.Context, arg database.Up
|
||||
return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceLastUsedAt)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceLockedDeletingAt(ctx context.Context, arg database.UpdateWorkspaceLockedDeletingAtParams) (database.Workspace, error) {
|
||||
fetch := func(ctx context.Context, arg database.UpdateWorkspaceLockedDeletingAtParams) (database.Workspace, error) {
|
||||
return q.db.GetWorkspaceByID(ctx, arg.ID)
|
||||
}
|
||||
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateWorkspaceLockedDeletingAt)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceProxy(ctx context.Context, arg database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
fetch := func(ctx context.Context, arg database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
return q.db.GetWorkspaceProxyByID(ctx, arg.ID)
|
||||
@ -2671,12 +2671,12 @@ func (q *querier) UpdateWorkspaceTTL(ctx context.Context, arg database.UpdateWor
|
||||
return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceTTL)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspacesLockedDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
|
||||
fetch := func(ctx context.Context, arg database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) (database.Template, error) {
|
||||
func (q *querier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) error {
|
||||
fetch := func(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) (database.Template, error) {
|
||||
return q.db.GetTemplateByID(ctx, arg.TemplateID)
|
||||
}
|
||||
|
||||
return fetchAndExec(q.log, q.auth, rbac.ActionUpdate, fetch, q.db.UpdateWorkspacesLockedDeletingAtByTemplateID)(ctx, arg)
|
||||
return fetchAndExec(q.log, q.auth, rbac.ActionUpdate, fetch, q.db.UpdateWorkspacesDormantDeletingAtByTemplateID)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertAppSecurityKey(ctx context.Context, data string) error {
|
||||
|
@ -341,7 +341,7 @@ func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspac
|
||||
AutostartSchedule: w.AutostartSchedule,
|
||||
Ttl: w.Ttl,
|
||||
LastUsedAt: w.LastUsedAt,
|
||||
LockedAt: w.LockedAt,
|
||||
DormantAt: w.DormantAt,
|
||||
DeletingAt: w.DeletingAt,
|
||||
Count: count,
|
||||
}
|
||||
@ -3737,14 +3737,14 @@ func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, no
|
||||
if build.Transition == database.WorkspaceTransitionStart &&
|
||||
!build.Deadline.IsZero() &&
|
||||
build.Deadline.Before(now) &&
|
||||
!workspace.LockedAt.Valid {
|
||||
!workspace.DormantAt.Valid {
|
||||
workspaces = append(workspaces, workspace)
|
||||
continue
|
||||
}
|
||||
|
||||
if build.Transition == database.WorkspaceTransitionStop &&
|
||||
workspace.AutostartSchedule.Valid &&
|
||||
!workspace.LockedAt.Valid {
|
||||
!workspace.DormantAt.Valid {
|
||||
workspaces = append(workspaces, workspace)
|
||||
continue
|
||||
}
|
||||
@ -3762,11 +3762,11 @@ func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, no
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get template by ID: %w", err)
|
||||
}
|
||||
if !workspace.LockedAt.Valid && template.InactivityTTL > 0 {
|
||||
if !workspace.DormantAt.Valid && template.TimeTilDormant > 0 {
|
||||
workspaces = append(workspaces, workspace)
|
||||
continue
|
||||
}
|
||||
if workspace.LockedAt.Valid && template.LockedTTL > 0 {
|
||||
if workspace.DormantAt.Valid && template.TimeTilDormantAutoDelete > 0 {
|
||||
workspaces = append(workspaces, workspace)
|
||||
continue
|
||||
}
|
||||
@ -5130,8 +5130,8 @@ func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database
|
||||
tpl.RestartRequirementDaysOfWeek = arg.RestartRequirementDaysOfWeek
|
||||
tpl.RestartRequirementWeeks = arg.RestartRequirementWeeks
|
||||
tpl.FailureTTL = arg.FailureTTL
|
||||
tpl.InactivityTTL = arg.InactivityTTL
|
||||
tpl.LockedTTL = arg.LockedTTL
|
||||
tpl.TimeTilDormant = arg.TimeTilDormant
|
||||
tpl.TimeTilDormantAutoDelete = arg.TimeTilDormantAutoDelete
|
||||
q.templates[idx] = tpl
|
||||
return nil
|
||||
}
|
||||
@ -5699,6 +5699,45 @@ func (q *FakeQuerier) UpdateWorkspaceDeletedByID(_ context.Context, arg database
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateWorkspaceDormantDeletingAt(_ context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return database.Workspace{}, err
|
||||
}
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
for index, workspace := range q.workspaces {
|
||||
if workspace.ID != arg.ID {
|
||||
continue
|
||||
}
|
||||
workspace.DormantAt = arg.DormantAt
|
||||
if workspace.DormantAt.Time.IsZero() {
|
||||
workspace.LastUsedAt = database.Now()
|
||||
workspace.DeletingAt = sql.NullTime{}
|
||||
}
|
||||
if !workspace.DormantAt.Time.IsZero() {
|
||||
var template database.TemplateTable
|
||||
for _, t := range q.templates {
|
||||
if t.ID == workspace.TemplateID {
|
||||
template = t
|
||||
break
|
||||
}
|
||||
}
|
||||
if template.ID == uuid.Nil {
|
||||
return database.Workspace{}, xerrors.Errorf("unable to find workspace template")
|
||||
}
|
||||
if template.TimeTilDormantAutoDelete > 0 {
|
||||
workspace.DeletingAt = sql.NullTime{
|
||||
Valid: true,
|
||||
Time: workspace.DormantAt.Time.Add(time.Duration(template.TimeTilDormantAutoDelete)),
|
||||
}
|
||||
}
|
||||
}
|
||||
q.workspaces[index] = workspace
|
||||
return workspace, nil
|
||||
}
|
||||
return database.Workspace{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateWorkspaceLastUsedAt(_ context.Context, arg database.UpdateWorkspaceLastUsedAtParams) error {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return err
|
||||
@ -5719,45 +5758,6 @@ func (q *FakeQuerier) UpdateWorkspaceLastUsedAt(_ context.Context, arg database.
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateWorkspaceLockedDeletingAt(_ context.Context, arg database.UpdateWorkspaceLockedDeletingAtParams) (database.Workspace, error) {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return database.Workspace{}, err
|
||||
}
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
for index, workspace := range q.workspaces {
|
||||
if workspace.ID != arg.ID {
|
||||
continue
|
||||
}
|
||||
workspace.LockedAt = arg.LockedAt
|
||||
if workspace.LockedAt.Time.IsZero() {
|
||||
workspace.LastUsedAt = database.Now()
|
||||
workspace.DeletingAt = sql.NullTime{}
|
||||
}
|
||||
if !workspace.LockedAt.Time.IsZero() {
|
||||
var template database.TemplateTable
|
||||
for _, t := range q.templates {
|
||||
if t.ID == workspace.TemplateID {
|
||||
template = t
|
||||
break
|
||||
}
|
||||
}
|
||||
if template.ID == uuid.Nil {
|
||||
return database.Workspace{}, xerrors.Errorf("unable to find workspace template")
|
||||
}
|
||||
if template.LockedTTL > 0 {
|
||||
workspace.DeletingAt = sql.NullTime{
|
||||
Valid: true,
|
||||
Time: workspace.LockedAt.Time.Add(time.Duration(template.LockedTTL)),
|
||||
}
|
||||
}
|
||||
}
|
||||
q.workspaces[index] = workspace
|
||||
return workspace, nil
|
||||
}
|
||||
return database.Workspace{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateWorkspaceProxy(_ context.Context, arg database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
@ -5818,7 +5818,7 @@ func (q *FakeQuerier) UpdateWorkspaceTTL(_ context.Context, arg database.UpdateW
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateWorkspacesLockedDeletingAtByTemplateID(_ context.Context, arg database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
|
||||
func (q *FakeQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(_ context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
@ -5832,22 +5832,22 @@ func (q *FakeQuerier) UpdateWorkspacesLockedDeletingAtByTemplateID(_ context.Con
|
||||
continue
|
||||
}
|
||||
|
||||
if ws.LockedAt.Time.IsZero() {
|
||||
if ws.DormantAt.Time.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
if !arg.LockedAt.IsZero() {
|
||||
ws.LockedAt = sql.NullTime{
|
||||
if !arg.DormantAt.IsZero() {
|
||||
ws.DormantAt = sql.NullTime{
|
||||
Valid: true,
|
||||
Time: arg.LockedAt,
|
||||
Time: arg.DormantAt,
|
||||
}
|
||||
}
|
||||
|
||||
deletingAt := sql.NullTime{
|
||||
Valid: arg.LockedTtlMs > 0,
|
||||
Valid: arg.TimeTilDormantAutodeleteMs > 0,
|
||||
}
|
||||
if arg.LockedTtlMs > 0 {
|
||||
deletingAt.Time = ws.LockedAt.Time.Add(time.Duration(arg.LockedTtlMs) * time.Millisecond)
|
||||
if arg.TimeTilDormantAutodeleteMs > 0 {
|
||||
deletingAt.Time = ws.DormantAt.Time.Add(time.Duration(arg.TimeTilDormantAutodeleteMs) * time.Millisecond)
|
||||
}
|
||||
ws.DeletingAt = deletingAt
|
||||
q.workspaces[i] = ws
|
||||
@ -6224,12 +6224,12 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
|
||||
}
|
||||
|
||||
// We omit locked workspaces by default.
|
||||
if arg.LockedAt.IsZero() && workspace.LockedAt.Valid {
|
||||
if arg.DormantAt.IsZero() && workspace.DormantAt.Valid {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter out workspaces that are locked after the timestamp.
|
||||
if !arg.LockedAt.IsZero() && workspace.LockedAt.Time.Before(arg.LockedAt) {
|
||||
if !arg.DormantAt.IsZero() && workspace.DormantAt.Time.Before(arg.DormantAt) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1607,6 +1607,13 @@ func (m metricsStore) UpdateWorkspaceDeletedByID(ctx context.Context, arg databa
|
||||
return err
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) {
|
||||
start := time.Now()
|
||||
ws, r0 := m.s.UpdateWorkspaceDormantDeletingAt(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateWorkspaceDormantDeletingAt").Observe(time.Since(start).Seconds())
|
||||
return ws, r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateWorkspaceLastUsedAt(ctx context.Context, arg database.UpdateWorkspaceLastUsedAtParams) error {
|
||||
start := time.Now()
|
||||
err := m.s.UpdateWorkspaceLastUsedAt(ctx, arg)
|
||||
@ -1614,13 +1621,6 @@ func (m metricsStore) UpdateWorkspaceLastUsedAt(ctx context.Context, arg databas
|
||||
return err
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateWorkspaceLockedDeletingAt(ctx context.Context, arg database.UpdateWorkspaceLockedDeletingAtParams) (database.Workspace, error) {
|
||||
start := time.Now()
|
||||
ws, r0 := m.s.UpdateWorkspaceLockedDeletingAt(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateWorkspaceLockedDeletingAt").Observe(time.Since(start).Seconds())
|
||||
return ws, r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateWorkspaceProxy(ctx context.Context, arg database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
start := time.Now()
|
||||
proxy, err := m.s.UpdateWorkspaceProxy(ctx, arg)
|
||||
@ -1642,10 +1642,10 @@ func (m metricsStore) UpdateWorkspaceTTL(ctx context.Context, arg database.Updat
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateWorkspacesLockedDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
|
||||
func (m metricsStore) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpdateWorkspacesLockedDeletingAtByTemplateID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateWorkspacesLockedDeletingAtByTemplateID").Observe(time.Since(start).Seconds())
|
||||
r0 := m.s.UpdateWorkspacesDormantDeletingAtByTemplateID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateWorkspacesDormantDeletingAtByTemplateID").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
|
@ -3379,6 +3379,21 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceDeletedByID(arg0, arg1 interface
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceDeletedByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceDeletedByID), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceDormantDeletingAt mocks base method.
|
||||
func (m *MockStore) UpdateWorkspaceDormantDeletingAt(arg0 context.Context, arg1 database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateWorkspaceDormantDeletingAt", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.Workspace)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateWorkspaceDormantDeletingAt indicates an expected call of UpdateWorkspaceDormantDeletingAt.
|
||||
func (mr *MockStoreMockRecorder) UpdateWorkspaceDormantDeletingAt(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceDormantDeletingAt", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceDormantDeletingAt), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceLastUsedAt mocks base method.
|
||||
func (m *MockStore) UpdateWorkspaceLastUsedAt(arg0 context.Context, arg1 database.UpdateWorkspaceLastUsedAtParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
@ -3393,21 +3408,6 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceLastUsedAt(arg0, arg1 interface{
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceLastUsedAt", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceLastUsedAt), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceLockedDeletingAt mocks base method.
|
||||
func (m *MockStore) UpdateWorkspaceLockedDeletingAt(arg0 context.Context, arg1 database.UpdateWorkspaceLockedDeletingAtParams) (database.Workspace, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateWorkspaceLockedDeletingAt", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.Workspace)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateWorkspaceLockedDeletingAt indicates an expected call of UpdateWorkspaceLockedDeletingAt.
|
||||
func (mr *MockStoreMockRecorder) UpdateWorkspaceLockedDeletingAt(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceLockedDeletingAt", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceLockedDeletingAt), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceProxy mocks base method.
|
||||
func (m *MockStore) UpdateWorkspaceProxy(arg0 context.Context, arg1 database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -3451,18 +3451,18 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceTTL(arg0, arg1 interface{}) *gom
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceTTL", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceTTL), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateWorkspacesLockedDeletingAtByTemplateID mocks base method.
|
||||
func (m *MockStore) UpdateWorkspacesLockedDeletingAtByTemplateID(arg0 context.Context, arg1 database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
|
||||
// UpdateWorkspacesDormantDeletingAtByTemplateID mocks base method.
|
||||
func (m *MockStore) UpdateWorkspacesDormantDeletingAtByTemplateID(arg0 context.Context, arg1 database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateWorkspacesLockedDeletingAtByTemplateID", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "UpdateWorkspacesDormantDeletingAtByTemplateID", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateWorkspacesLockedDeletingAtByTemplateID indicates an expected call of UpdateWorkspacesLockedDeletingAtByTemplateID.
|
||||
func (mr *MockStoreMockRecorder) UpdateWorkspacesLockedDeletingAtByTemplateID(arg0, arg1 interface{}) *gomock.Call {
|
||||
// UpdateWorkspacesDormantDeletingAtByTemplateID indicates an expected call of UpdateWorkspacesDormantDeletingAtByTemplateID.
|
||||
func (mr *MockStoreMockRecorder) UpdateWorkspacesDormantDeletingAtByTemplateID(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspacesLockedDeletingAtByTemplateID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspacesLockedDeletingAtByTemplateID), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspacesDormantDeletingAtByTemplateID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspacesDormantDeletingAtByTemplateID), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertAppSecurityKey mocks base method.
|
||||
|
10
coderd/database/dump.sql
generated
10
coderd/database/dump.sql
generated
@ -635,8 +635,8 @@ CREATE TABLE templates (
|
||||
allow_user_autostart boolean DEFAULT true NOT NULL,
|
||||
allow_user_autostop boolean DEFAULT true NOT NULL,
|
||||
failure_ttl bigint DEFAULT 0 NOT NULL,
|
||||
inactivity_ttl bigint DEFAULT 0 NOT NULL,
|
||||
locked_ttl bigint DEFAULT 0 NOT NULL,
|
||||
time_til_dormant bigint DEFAULT 0 NOT NULL,
|
||||
time_til_dormant_autodelete bigint DEFAULT 0 NOT NULL,
|
||||
restart_requirement_days_of_week smallint DEFAULT 0 NOT NULL,
|
||||
restart_requirement_weeks bigint DEFAULT 0 NOT NULL
|
||||
);
|
||||
@ -676,8 +676,8 @@ CREATE VIEW template_with_users AS
|
||||
templates.allow_user_autostart,
|
||||
templates.allow_user_autostop,
|
||||
templates.failure_ttl,
|
||||
templates.inactivity_ttl,
|
||||
templates.locked_ttl,
|
||||
templates.time_til_dormant,
|
||||
templates.time_til_dormant_autodelete,
|
||||
templates.restart_requirement_days_of_week,
|
||||
templates.restart_requirement_weeks,
|
||||
COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url,
|
||||
@ -1003,7 +1003,7 @@ CREATE TABLE workspaces (
|
||||
autostart_schedule text,
|
||||
ttl bigint,
|
||||
last_used_at timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL,
|
||||
locked_at timestamp with time zone,
|
||||
dormant_at timestamp with time zone,
|
||||
deleting_at timestamp with time zone
|
||||
);
|
||||
|
||||
|
26
coderd/database/migrations/000151_rename_locked.down.sql
Normal file
26
coderd/database/migrations/000151_rename_locked.down.sql
Normal file
@ -0,0 +1,26 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE templates RENAME COLUMN time_til_dormant TO inactivity_ttl;
|
||||
ALTER TABLE templates RENAME COLUMN time_til_dormant_autodelete TO locked_ttl;
|
||||
ALTER TABLE workspaces RENAME COLUMN dormant_at TO locked_at;
|
||||
|
||||
-- Update the template_with_users view;
|
||||
DROP VIEW template_with_users;
|
||||
-- If you need to update this view, put 'DROP VIEW template_with_users;' before this.
|
||||
CREATE VIEW
|
||||
template_with_users
|
||||
AS
|
||||
SELECT
|
||||
templates.*,
|
||||
coalesce(visible_users.avatar_url, '') AS created_by_avatar_url,
|
||||
coalesce(visible_users.username, '') AS created_by_username
|
||||
FROM
|
||||
templates
|
||||
LEFT JOIN
|
||||
visible_users
|
||||
ON
|
||||
templates.created_by = visible_users.id;
|
||||
|
||||
COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.';
|
||||
|
||||
COMMIT;
|
25
coderd/database/migrations/000151_rename_locked.up.sql
Normal file
25
coderd/database/migrations/000151_rename_locked.up.sql
Normal file
@ -0,0 +1,25 @@
|
||||
BEGIN;
|
||||
ALTER TABLE templates RENAME COLUMN inactivity_ttl TO time_til_dormant;
|
||||
ALTER TABLE templates RENAME COLUMN locked_ttl TO time_til_dormant_autodelete;
|
||||
ALTER TABLE workspaces RENAME COLUMN locked_at TO dormant_at;
|
||||
|
||||
-- Update the template_with_users view;a
|
||||
DROP VIEW template_with_users;
|
||||
-- If you need to update this view, put 'DROP VIEW template_with_users;' before this.
|
||||
CREATE VIEW
|
||||
template_with_users
|
||||
AS
|
||||
SELECT
|
||||
templates.*,
|
||||
coalesce(visible_users.avatar_url, '') AS created_by_avatar_url,
|
||||
coalesce(visible_users.username, '') AS created_by_username
|
||||
FROM
|
||||
templates
|
||||
LEFT JOIN
|
||||
visible_users
|
||||
ON
|
||||
templates.created_by = visible_users.id;
|
||||
|
||||
COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.';
|
||||
|
||||
COMMIT;
|
@ -146,8 +146,8 @@ func (w Workspace) RBACObject() rbac.Object {
|
||||
|
||||
func (w Workspace) ExecutionRBAC() rbac.Object {
|
||||
// If a workspace is locked it cannot be accessed.
|
||||
if w.LockedAt.Valid {
|
||||
return w.LockedRBAC()
|
||||
if w.DormantAt.Valid {
|
||||
return w.DormantRBAC()
|
||||
}
|
||||
|
||||
return rbac.ResourceWorkspaceExecution.
|
||||
@ -158,8 +158,8 @@ func (w Workspace) ExecutionRBAC() rbac.Object {
|
||||
|
||||
func (w Workspace) ApplicationConnectRBAC() rbac.Object {
|
||||
// If a workspace is locked it cannot be accessed.
|
||||
if w.LockedAt.Valid {
|
||||
return w.LockedRBAC()
|
||||
if w.DormantAt.Valid {
|
||||
return w.DormantRBAC()
|
||||
}
|
||||
|
||||
return rbac.ResourceWorkspaceApplicationConnect.
|
||||
@ -173,9 +173,9 @@ func (w Workspace) WorkspaceBuildRBAC(transition WorkspaceTransition) rbac.Objec
|
||||
// However we need to allow stopping a workspace by a caller once a workspace
|
||||
// is locked (e.g. for autobuild). Additionally, if a user wants to delete
|
||||
// a locked workspace, they shouldn't have to have it unlocked first.
|
||||
if w.LockedAt.Valid && transition != WorkspaceTransitionStop &&
|
||||
if w.DormantAt.Valid && transition != WorkspaceTransitionStop &&
|
||||
transition != WorkspaceTransitionDelete {
|
||||
return w.LockedRBAC()
|
||||
return w.DormantRBAC()
|
||||
}
|
||||
|
||||
return rbac.ResourceWorkspaceBuild.
|
||||
@ -184,8 +184,8 @@ func (w Workspace) WorkspaceBuildRBAC(transition WorkspaceTransition) rbac.Objec
|
||||
WithOwner(w.OwnerID.String())
|
||||
}
|
||||
|
||||
func (w Workspace) LockedRBAC() rbac.Object {
|
||||
return rbac.ResourceWorkspaceLocked.
|
||||
func (w Workspace) DormantRBAC() rbac.Object {
|
||||
return rbac.ResourceWorkspaceDormant.
|
||||
WithID(w.ID).
|
||||
InOrg(w.OrganizationID).
|
||||
WithOwner(w.OwnerID.String())
|
||||
@ -355,7 +355,7 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
|
||||
AutostartSchedule: r.AutostartSchedule,
|
||||
Ttl: r.Ttl,
|
||||
LastUsedAt: r.LastUsedAt,
|
||||
LockedAt: r.LockedAt,
|
||||
DormantAt: r.DormantAt,
|
||||
DeletingAt: r.DeletingAt,
|
||||
}
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
&i.LockedTTL,
|
||||
&i.TimeTilDormant,
|
||||
&i.TimeTilDormantAutoDelete,
|
||||
&i.RestartRequirementDaysOfWeek,
|
||||
&i.RestartRequirementWeeks,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -217,7 +217,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
|
||||
arg.Name,
|
||||
arg.HasAgent,
|
||||
arg.AgentInactiveDisconnectTimeoutSeconds,
|
||||
arg.LockedAt,
|
||||
arg.DormantAt,
|
||||
arg.LastUsedBefore,
|
||||
arg.LastUsedAfter,
|
||||
arg.Offset,
|
||||
@ -242,7 +242,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
&i.TemplateName,
|
||||
&i.TemplateVersionID,
|
||||
|
@ -1729,8 +1729,8 @@ type Template struct {
|
||||
AllowUserAutostart bool `db:"allow_user_autostart" json:"allow_user_autostart"`
|
||||
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
|
||||
FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"`
|
||||
InactivityTTL int64 `db:"inactivity_ttl" json:"inactivity_ttl"`
|
||||
LockedTTL int64 `db:"locked_ttl" json:"locked_ttl"`
|
||||
TimeTilDormant int64 `db:"time_til_dormant" json:"time_til_dormant"`
|
||||
TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"`
|
||||
RestartRequirementDaysOfWeek int16 `db:"restart_requirement_days_of_week" json:"restart_requirement_days_of_week"`
|
||||
RestartRequirementWeeks int64 `db:"restart_requirement_weeks" json:"restart_requirement_weeks"`
|
||||
CreatedByAvatarURL sql.NullString `db:"created_by_avatar_url" json:"created_by_avatar_url"`
|
||||
@ -1761,10 +1761,10 @@ type TemplateTable struct {
|
||||
// Allow users to specify an autostart schedule for workspaces (enterprise).
|
||||
AllowUserAutostart bool `db:"allow_user_autostart" json:"allow_user_autostart"`
|
||||
// Allow users to specify custom autostop values for workspaces (enterprise).
|
||||
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
|
||||
FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"`
|
||||
InactivityTTL int64 `db:"inactivity_ttl" json:"inactivity_ttl"`
|
||||
LockedTTL int64 `db:"locked_ttl" json:"locked_ttl"`
|
||||
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
|
||||
FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"`
|
||||
TimeTilDormant int64 `db:"time_til_dormant" json:"time_til_dormant"`
|
||||
TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"`
|
||||
// A bitmap of days of week to restart the workspace on, starting with Monday as the 0th bit, and Sunday as the 6th bit. The 7th bit is unused.
|
||||
RestartRequirementDaysOfWeek int16 `db:"restart_requirement_days_of_week" json:"restart_requirement_days_of_week"`
|
||||
// The number of weeks between restarts. 0 or 1 weeks means "every week", 2 week means "every second week", etc. Weeks are counted from January 2, 2023, which is the first Monday of 2023. This is to ensure workspaces are started consistently for all customers on the same n-week cycles.
|
||||
@ -1903,7 +1903,7 @@ type Workspace struct {
|
||||
AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"`
|
||||
Ttl sql.NullInt64 `db:"ttl" json:"ttl"`
|
||||
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
|
||||
LockedAt sql.NullTime `db:"locked_at" json:"locked_at"`
|
||||
DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
|
||||
DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"`
|
||||
}
|
||||
|
||||
|
@ -292,13 +292,13 @@ type sqlcQuerier interface {
|
||||
UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) error
|
||||
UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) error
|
||||
UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error
|
||||
UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (Workspace, error)
|
||||
UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error
|
||||
UpdateWorkspaceLockedDeletingAt(ctx context.Context, arg UpdateWorkspaceLockedDeletingAtParams) (Workspace, error)
|
||||
// This allows editing the properties of a workspace proxy.
|
||||
UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspaceProxyParams) (WorkspaceProxy, error)
|
||||
UpdateWorkspaceProxyDeleted(ctx context.Context, arg UpdateWorkspaceProxyDeletedParams) error
|
||||
UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error
|
||||
UpdateWorkspacesLockedDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error
|
||||
UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDormantDeletingAtByTemplateIDParams) error
|
||||
UpsertAppSecurityKey(ctx context.Context, value string) error
|
||||
// The default proxy is implied and not actually stored in the database.
|
||||
// So we need to store it's configuration here for display purposes.
|
||||
|
@ -4317,7 +4317,7 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg GetTem
|
||||
|
||||
const getTemplateByID = `-- name: GetTemplateByID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl, restart_requirement_days_of_week, restart_requirement_weeks, created_by_avatar_url, created_by_username
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, restart_requirement_days_of_week, restart_requirement_weeks, created_by_avatar_url, created_by_username
|
||||
FROM
|
||||
template_with_users
|
||||
WHERE
|
||||
@ -4350,8 +4350,8 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
&i.LockedTTL,
|
||||
&i.TimeTilDormant,
|
||||
&i.TimeTilDormantAutoDelete,
|
||||
&i.RestartRequirementDaysOfWeek,
|
||||
&i.RestartRequirementWeeks,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -4362,7 +4362,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
||||
|
||||
const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one
|
||||
SELECT
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl, restart_requirement_days_of_week, restart_requirement_weeks, created_by_avatar_url, created_by_username
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, restart_requirement_days_of_week, restart_requirement_weeks, created_by_avatar_url, created_by_username
|
||||
FROM
|
||||
template_with_users AS templates
|
||||
WHERE
|
||||
@ -4403,8 +4403,8 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
&i.LockedTTL,
|
||||
&i.TimeTilDormant,
|
||||
&i.TimeTilDormantAutoDelete,
|
||||
&i.RestartRequirementDaysOfWeek,
|
||||
&i.RestartRequirementWeeks,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -4414,7 +4414,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
||||
}
|
||||
|
||||
const getTemplates = `-- name: GetTemplates :many
|
||||
SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl, restart_requirement_days_of_week, restart_requirement_weeks, created_by_avatar_url, created_by_username FROM template_with_users AS templates
|
||||
SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, restart_requirement_days_of_week, restart_requirement_weeks, created_by_avatar_url, created_by_username FROM template_with_users AS templates
|
||||
ORDER BY (name, id) ASC
|
||||
`
|
||||
|
||||
@ -4448,8 +4448,8 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
&i.LockedTTL,
|
||||
&i.TimeTilDormant,
|
||||
&i.TimeTilDormantAutoDelete,
|
||||
&i.RestartRequirementDaysOfWeek,
|
||||
&i.RestartRequirementWeeks,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -4470,7 +4470,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
|
||||
|
||||
const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many
|
||||
SELECT
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl, restart_requirement_days_of_week, restart_requirement_weeks, created_by_avatar_url, created_by_username
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, restart_requirement_days_of_week, restart_requirement_weeks, created_by_avatar_url, created_by_username
|
||||
FROM
|
||||
template_with_users AS templates
|
||||
WHERE
|
||||
@ -4541,8 +4541,8 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
&i.LockedTTL,
|
||||
&i.TimeTilDormant,
|
||||
&i.TimeTilDormantAutoDelete,
|
||||
&i.RestartRequirementDaysOfWeek,
|
||||
&i.RestartRequirementWeeks,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -4732,8 +4732,8 @@ SET
|
||||
restart_requirement_days_of_week = $7,
|
||||
restart_requirement_weeks = $8,
|
||||
failure_ttl = $9,
|
||||
inactivity_ttl = $10,
|
||||
locked_ttl = $11
|
||||
time_til_dormant = $10,
|
||||
time_til_dormant_autodelete = $11
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
@ -4748,8 +4748,8 @@ type UpdateTemplateScheduleByIDParams struct {
|
||||
RestartRequirementDaysOfWeek int16 `db:"restart_requirement_days_of_week" json:"restart_requirement_days_of_week"`
|
||||
RestartRequirementWeeks int64 `db:"restart_requirement_weeks" json:"restart_requirement_weeks"`
|
||||
FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"`
|
||||
InactivityTTL int64 `db:"inactivity_ttl" json:"inactivity_ttl"`
|
||||
LockedTTL int64 `db:"locked_ttl" json:"locked_ttl"`
|
||||
TimeTilDormant int64 `db:"time_til_dormant" json:"time_til_dormant"`
|
||||
TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error {
|
||||
@ -4763,8 +4763,8 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT
|
||||
arg.RestartRequirementDaysOfWeek,
|
||||
arg.RestartRequirementWeeks,
|
||||
arg.FailureTTL,
|
||||
arg.InactivityTTL,
|
||||
arg.LockedTTL,
|
||||
arg.TimeTilDormant,
|
||||
arg.TimeTilDormantAutoDelete,
|
||||
)
|
||||
return err
|
||||
}
|
||||
@ -9104,7 +9104,7 @@ func (q *sqlQuerier) GetDeploymentWorkspaceStats(ctx context.Context) (GetDeploy
|
||||
|
||||
const getWorkspaceByAgentID = `-- name: GetWorkspaceByAgentID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, locked_at, deleting_at
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at
|
||||
FROM
|
||||
workspaces
|
||||
WHERE
|
||||
@ -9147,7 +9147,7 @@ func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUI
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
)
|
||||
return i, err
|
||||
@ -9155,7 +9155,7 @@ func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUI
|
||||
|
||||
const getWorkspaceByID = `-- name: GetWorkspaceByID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, locked_at, deleting_at
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at
|
||||
FROM
|
||||
workspaces
|
||||
WHERE
|
||||
@ -9179,7 +9179,7 @@ func (q *sqlQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Worksp
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
)
|
||||
return i, err
|
||||
@ -9187,7 +9187,7 @@ func (q *sqlQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Worksp
|
||||
|
||||
const getWorkspaceByOwnerIDAndName = `-- name: GetWorkspaceByOwnerIDAndName :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, locked_at, deleting_at
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at
|
||||
FROM
|
||||
workspaces
|
||||
WHERE
|
||||
@ -9218,7 +9218,7 @@ func (q *sqlQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWo
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
)
|
||||
return i, err
|
||||
@ -9226,7 +9226,7 @@ func (q *sqlQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWo
|
||||
|
||||
const getWorkspaceByWorkspaceAppID = `-- name: GetWorkspaceByWorkspaceAppID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, locked_at, deleting_at
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at
|
||||
FROM
|
||||
workspaces
|
||||
WHERE
|
||||
@ -9276,7 +9276,7 @@ func (q *sqlQuerier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspace
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
)
|
||||
return i, err
|
||||
@ -9284,7 +9284,7 @@ func (q *sqlQuerier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspace
|
||||
|
||||
const getWorkspaces = `-- name: GetWorkspaces :many
|
||||
SELECT
|
||||
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.locked_at, workspaces.deleting_at,
|
||||
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at,
|
||||
COALESCE(template_name.template_name, 'unknown') as template_name,
|
||||
latest_build.template_version_id,
|
||||
latest_build.template_version_name,
|
||||
@ -9468,13 +9468,13 @@ WHERE
|
||||
) > 0
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by locked workspaces. By default we do not return locked
|
||||
-- Filter by dormant workspaces. By default we do not return dormant
|
||||
-- workspaces since they are considered soft-deleted.
|
||||
AND CASE
|
||||
WHEN $10 :: timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN
|
||||
locked_at IS NOT NULL AND locked_at >= $10
|
||||
dormant_at IS NOT NULL AND dormant_at >= $10
|
||||
ELSE
|
||||
locked_at IS NULL
|
||||
dormant_at IS NULL
|
||||
END
|
||||
-- Filter by last_used
|
||||
AND CASE
|
||||
@ -9515,7 +9515,7 @@ type GetWorkspacesParams struct {
|
||||
Name string `db:"name" json:"name"`
|
||||
HasAgent string `db:"has_agent" json:"has_agent"`
|
||||
AgentInactiveDisconnectTimeoutSeconds int64 `db:"agent_inactive_disconnect_timeout_seconds" json:"agent_inactive_disconnect_timeout_seconds"`
|
||||
LockedAt time.Time `db:"locked_at" json:"locked_at"`
|
||||
DormantAt time.Time `db:"dormant_at" json:"dormant_at"`
|
||||
LastUsedBefore time.Time `db:"last_used_before" json:"last_used_before"`
|
||||
LastUsedAfter time.Time `db:"last_used_after" json:"last_used_after"`
|
||||
Offset int32 `db:"offset_" json:"offset_"`
|
||||
@ -9534,7 +9534,7 @@ type GetWorkspacesRow struct {
|
||||
AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"`
|
||||
Ttl sql.NullInt64 `db:"ttl" json:"ttl"`
|
||||
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
|
||||
LockedAt sql.NullTime `db:"locked_at" json:"locked_at"`
|
||||
DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
|
||||
DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"`
|
||||
TemplateName string `db:"template_name" json:"template_name"`
|
||||
TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
|
||||
@ -9553,7 +9553,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams)
|
||||
arg.Name,
|
||||
arg.HasAgent,
|
||||
arg.AgentInactiveDisconnectTimeoutSeconds,
|
||||
arg.LockedAt,
|
||||
arg.DormantAt,
|
||||
arg.LastUsedBefore,
|
||||
arg.LastUsedAfter,
|
||||
arg.Offset,
|
||||
@ -9578,7 +9578,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams)
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
&i.TemplateName,
|
||||
&i.TemplateVersionID,
|
||||
@ -9600,7 +9600,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams)
|
||||
|
||||
const getWorkspacesEligibleForTransition = `-- name: GetWorkspacesEligibleForTransition :many
|
||||
SELECT
|
||||
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.locked_at, workspaces.deleting_at
|
||||
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at
|
||||
FROM
|
||||
workspaces
|
||||
LEFT JOIN
|
||||
@ -9649,17 +9649,17 @@ WHERE
|
||||
) OR
|
||||
|
||||
-- If the workspace's template has an inactivity_ttl set
|
||||
-- it may be eligible for locking.
|
||||
-- it may be eligible for dormancy.
|
||||
(
|
||||
templates.inactivity_ttl > 0 AND
|
||||
workspaces.locked_at IS NULL
|
||||
templates.time_til_dormant > 0 AND
|
||||
workspaces.dormant_at IS NULL
|
||||
) OR
|
||||
|
||||
-- If the workspace's template has a locked_ttl set
|
||||
-- and the workspace is already locked
|
||||
-- If the workspace's template has a time_til_dormant_autodelete set
|
||||
-- and the workspace is already dormant.
|
||||
(
|
||||
templates.locked_ttl > 0 AND
|
||||
workspaces.locked_at IS NOT NULL
|
||||
templates.time_til_dormant_autodelete > 0 AND
|
||||
workspaces.dormant_at IS NOT NULL
|
||||
)
|
||||
) AND workspaces.deleted = 'false'
|
||||
`
|
||||
@ -9685,7 +9685,7 @@ func (q *sqlQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@ -9716,7 +9716,7 @@ INSERT INTO
|
||||
last_used_at
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, locked_at, deleting_at
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at
|
||||
`
|
||||
|
||||
type InsertWorkspaceParams struct {
|
||||
@ -9758,7 +9758,7 @@ func (q *sqlQuerier) InsertWorkspace(ctx context.Context, arg InsertWorkspacePar
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
)
|
||||
return i, err
|
||||
@ -9790,7 +9790,7 @@ SET
|
||||
WHERE
|
||||
id = $1
|
||||
AND deleted = false
|
||||
RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, locked_at, deleting_at
|
||||
RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at
|
||||
`
|
||||
|
||||
type UpdateWorkspaceParams struct {
|
||||
@ -9813,7 +9813,7 @@ func (q *sqlQuerier) UpdateWorkspace(ctx context.Context, arg UpdateWorkspacePar
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
)
|
||||
return i, err
|
||||
@ -9857,6 +9857,52 @@ func (q *sqlQuerier) UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateW
|
||||
return err
|
||||
}
|
||||
|
||||
const updateWorkspaceDormantDeletingAt = `-- name: UpdateWorkspaceDormantDeletingAt :one
|
||||
UPDATE
|
||||
workspaces
|
||||
SET
|
||||
dormant_at = $2,
|
||||
-- When a workspace is active we want to update the last_used_at to avoid the workspace going
|
||||
-- immediately dormant. If we're transition the workspace to dormant then we leave it alone.
|
||||
last_used_at = CASE WHEN $2::timestamptz IS NULL THEN now() at time zone 'utc' ELSE last_used_at END,
|
||||
-- If dormant_at is null (meaning active) or the template-defined time_til_dormant_autodelete is 0 we should set
|
||||
-- deleting_at to NULL else set it to the dormant_at + time_til_dormant_autodelete duration.
|
||||
deleting_at = CASE WHEN $2::timestamptz IS NULL OR templates.time_til_dormant_autodelete = 0 THEN NULL ELSE $2::timestamptz + INTERVAL '1 milliseconds' * templates.time_til_dormant_autodelete / 1000000 END
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
workspaces.template_id = templates.id
|
||||
AND
|
||||
workspaces.id = $1
|
||||
RETURNING workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at
|
||||
`
|
||||
|
||||
type UpdateWorkspaceDormantDeletingAtParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (Workspace, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateWorkspaceDormantDeletingAt, arg.ID, arg.DormantAt)
|
||||
var i Workspace
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
&i.OrganizationID,
|
||||
&i.TemplateID,
|
||||
&i.Deleted,
|
||||
&i.Name,
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateWorkspaceLastUsedAt = `-- name: UpdateWorkspaceLastUsedAt :exec
|
||||
UPDATE
|
||||
workspaces
|
||||
@ -9876,52 +9922,6 @@ func (q *sqlQuerier) UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWo
|
||||
return err
|
||||
}
|
||||
|
||||
const updateWorkspaceLockedDeletingAt = `-- name: UpdateWorkspaceLockedDeletingAt :one
|
||||
UPDATE
|
||||
workspaces
|
||||
SET
|
||||
locked_at = $2,
|
||||
-- When a workspace is unlocked we want to update the last_used_at to avoid the workspace getting re-locked.
|
||||
-- if we're locking the workspace then we leave it alone.
|
||||
last_used_at = CASE WHEN $2::timestamptz IS NULL THEN now() at time zone 'utc' ELSE last_used_at END,
|
||||
-- If locked_at is null (meaning unlocked) or the template-defined locked_ttl is 0 we should set
|
||||
-- deleting_at to NULL else set it to the locked_at + locked_ttl duration.
|
||||
deleting_at = CASE WHEN $2::timestamptz IS NULL OR templates.locked_ttl = 0 THEN NULL ELSE $2::timestamptz + INTERVAL '1 milliseconds' * templates.locked_ttl / 1000000 END
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
workspaces.template_id = templates.id
|
||||
AND
|
||||
workspaces.id = $1
|
||||
RETURNING workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.locked_at, workspaces.deleting_at
|
||||
`
|
||||
|
||||
type UpdateWorkspaceLockedDeletingAtParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
LockedAt sql.NullTime `db:"locked_at" json:"locked_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateWorkspaceLockedDeletingAt(ctx context.Context, arg UpdateWorkspaceLockedDeletingAtParams) (Workspace, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateWorkspaceLockedDeletingAt, arg.ID, arg.LockedAt)
|
||||
var i Workspace
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
&i.OrganizationID,
|
||||
&i.TemplateID,
|
||||
&i.Deleted,
|
||||
&i.Name,
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.LockedAt,
|
||||
&i.DeletingAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateWorkspaceTTL = `-- name: UpdateWorkspaceTTL :exec
|
||||
UPDATE
|
||||
workspaces
|
||||
@ -9941,28 +9941,28 @@ func (q *sqlQuerier) UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspace
|
||||
return err
|
||||
}
|
||||
|
||||
const updateWorkspacesLockedDeletingAtByTemplateID = `-- name: UpdateWorkspacesLockedDeletingAtByTemplateID :exec
|
||||
const updateWorkspacesDormantDeletingAtByTemplateID = `-- name: UpdateWorkspacesDormantDeletingAtByTemplateID :exec
|
||||
UPDATE workspaces
|
||||
SET
|
||||
deleting_at = CASE
|
||||
WHEN $1::bigint = 0 THEN NULL
|
||||
WHEN $2::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN ($2::timestamptz) + interval '1 milliseconds' * $1::bigint
|
||||
ELSE locked_at + interval '1 milliseconds' * $1::bigint
|
||||
ELSE dormant_at + interval '1 milliseconds' * $1::bigint
|
||||
END,
|
||||
locked_at = CASE WHEN $2::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN $2::timestamptz ELSE locked_at END
|
||||
dormant_at = CASE WHEN $2::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN $2::timestamptz ELSE dormant_at END
|
||||
WHERE
|
||||
template_id = $3
|
||||
AND
|
||||
locked_at IS NOT NULL
|
||||
dormant_at IS NOT NULL
|
||||
`
|
||||
|
||||
type UpdateWorkspacesLockedDeletingAtByTemplateIDParams struct {
|
||||
LockedTtlMs int64 `db:"locked_ttl_ms" json:"locked_ttl_ms"`
|
||||
LockedAt time.Time `db:"locked_at" json:"locked_at"`
|
||||
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
|
||||
type UpdateWorkspacesDormantDeletingAtByTemplateIDParams struct {
|
||||
TimeTilDormantAutodeleteMs int64 `db:"time_til_dormant_autodelete_ms" json:"time_til_dormant_autodelete_ms"`
|
||||
DormantAt time.Time `db:"dormant_at" json:"dormant_at"`
|
||||
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateWorkspacesLockedDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateWorkspacesLockedDeletingAtByTemplateID, arg.LockedTtlMs, arg.LockedAt, arg.TemplateID)
|
||||
func (q *sqlQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDormantDeletingAtByTemplateIDParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateWorkspacesDormantDeletingAtByTemplateID, arg.TimeTilDormantAutodeleteMs, arg.DormantAt, arg.TemplateID)
|
||||
return err
|
||||
}
|
||||
|
@ -121,8 +121,8 @@ SET
|
||||
restart_requirement_days_of_week = $7,
|
||||
restart_requirement_weeks = $8,
|
||||
failure_ttl = $9,
|
||||
inactivity_ttl = $10,
|
||||
locked_ttl = $11
|
||||
time_til_dormant = $10,
|
||||
time_til_dormant_autodelete = $11
|
||||
WHERE
|
||||
id = $1
|
||||
;
|
||||
|
@ -259,13 +259,13 @@ WHERE
|
||||
) > 0
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by locked workspaces. By default we do not return locked
|
||||
-- Filter by dormant workspaces. By default we do not return dormant
|
||||
-- workspaces since they are considered soft-deleted.
|
||||
AND CASE
|
||||
WHEN @locked_at :: timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN
|
||||
locked_at IS NOT NULL AND locked_at >= @locked_at
|
||||
WHEN @dormant_at :: timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN
|
||||
dormant_at IS NOT NULL AND dormant_at >= @dormant_at
|
||||
ELSE
|
||||
locked_at IS NULL
|
||||
dormant_at IS NULL
|
||||
END
|
||||
-- Filter by last_used
|
||||
AND CASE
|
||||
@ -479,31 +479,31 @@ WHERE
|
||||
) OR
|
||||
|
||||
-- If the workspace's template has an inactivity_ttl set
|
||||
-- it may be eligible for locking.
|
||||
-- it may be eligible for dormancy.
|
||||
(
|
||||
templates.inactivity_ttl > 0 AND
|
||||
workspaces.locked_at IS NULL
|
||||
templates.time_til_dormant > 0 AND
|
||||
workspaces.dormant_at IS NULL
|
||||
) OR
|
||||
|
||||
-- If the workspace's template has a locked_ttl set
|
||||
-- and the workspace is already locked
|
||||
-- If the workspace's template has a time_til_dormant_autodelete set
|
||||
-- and the workspace is already dormant.
|
||||
(
|
||||
templates.locked_ttl > 0 AND
|
||||
workspaces.locked_at IS NOT NULL
|
||||
templates.time_til_dormant_autodelete > 0 AND
|
||||
workspaces.dormant_at IS NOT NULL
|
||||
)
|
||||
) AND workspaces.deleted = 'false';
|
||||
|
||||
-- name: UpdateWorkspaceLockedDeletingAt :one
|
||||
-- name: UpdateWorkspaceDormantDeletingAt :one
|
||||
UPDATE
|
||||
workspaces
|
||||
SET
|
||||
locked_at = $2,
|
||||
-- When a workspace is unlocked we want to update the last_used_at to avoid the workspace getting re-locked.
|
||||
-- if we're locking the workspace then we leave it alone.
|
||||
dormant_at = $2,
|
||||
-- When a workspace is active we want to update the last_used_at to avoid the workspace going
|
||||
-- immediately dormant. If we're transition the workspace to dormant then we leave it alone.
|
||||
last_used_at = CASE WHEN $2::timestamptz IS NULL THEN now() at time zone 'utc' ELSE last_used_at END,
|
||||
-- If locked_at is null (meaning unlocked) or the template-defined locked_ttl is 0 we should set
|
||||
-- deleting_at to NULL else set it to the locked_at + locked_ttl duration.
|
||||
deleting_at = CASE WHEN $2::timestamptz IS NULL OR templates.locked_ttl = 0 THEN NULL ELSE $2::timestamptz + INTERVAL '1 milliseconds' * templates.locked_ttl / 1000000 END
|
||||
-- If dormant_at is null (meaning active) or the template-defined time_til_dormant_autodelete is 0 we should set
|
||||
-- deleting_at to NULL else set it to the dormant_at + time_til_dormant_autodelete duration.
|
||||
deleting_at = CASE WHEN $2::timestamptz IS NULL OR templates.time_til_dormant_autodelete = 0 THEN NULL ELSE $2::timestamptz + INTERVAL '1 milliseconds' * templates.time_til_dormant_autodelete / 1000000 END
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
@ -512,19 +512,19 @@ AND
|
||||
workspaces.id = $1
|
||||
RETURNING workspaces.*;
|
||||
|
||||
-- name: UpdateWorkspacesLockedDeletingAtByTemplateID :exec
|
||||
-- name: UpdateWorkspacesDormantDeletingAtByTemplateID :exec
|
||||
UPDATE workspaces
|
||||
SET
|
||||
deleting_at = CASE
|
||||
WHEN @locked_ttl_ms::bigint = 0 THEN NULL
|
||||
WHEN @locked_at::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN (@locked_at::timestamptz) + interval '1 milliseconds' * @locked_ttl_ms::bigint
|
||||
ELSE locked_at + interval '1 milliseconds' * @locked_ttl_ms::bigint
|
||||
WHEN @time_til_dormant_autodelete_ms::bigint = 0 THEN NULL
|
||||
WHEN @dormant_at::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN (@dormant_at::timestamptz) + interval '1 milliseconds' * @time_til_dormant_autodelete_ms::bigint
|
||||
ELSE dormant_at + interval '1 milliseconds' * @time_til_dormant_autodelete_ms::bigint
|
||||
END,
|
||||
locked_at = CASE WHEN @locked_at::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN @locked_at::timestamptz ELSE locked_at END
|
||||
dormant_at = CASE WHEN @dormant_at::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN @dormant_at::timestamptz ELSE dormant_at END
|
||||
WHERE
|
||||
template_id = @template_id
|
||||
AND
|
||||
locked_at IS NOT NULL;
|
||||
dormant_at IS NOT NULL;
|
||||
|
||||
-- name: UpdateTemplateWorkspacesLastUsedAt :exec
|
||||
UPDATE workspaces
|
||||
|
@ -67,9 +67,8 @@ overrides:
|
||||
motd_file: MOTDFile
|
||||
uuid: UUID
|
||||
failure_ttl: FailureTTL
|
||||
inactivity_ttl: InactivityTTL
|
||||
time_til_dormant_autodelete: TimeTilDormantAutoDelete
|
||||
eof: EOF
|
||||
locked_ttl: LockedTTL
|
||||
template_ids: TemplateIDs
|
||||
active_user_ids: ActiveUserIDs
|
||||
|
||||
|
@ -37,10 +37,10 @@ var (
|
||||
Type: "workspace_build",
|
||||
}
|
||||
|
||||
// ResourceWorkspaceLocked is returned if a workspace is locked.
|
||||
// ResourceWorkspaceDormant is returned if a workspace is dormant.
|
||||
// It grants restricted permissions on workspace builds.
|
||||
ResourceWorkspaceLocked = Object{
|
||||
Type: "workspace_locked",
|
||||
ResourceWorkspaceDormant = Object{
|
||||
Type: "workspace_dormant",
|
||||
}
|
||||
|
||||
// ResourceWorkspaceProxy CRUD. Org
|
||||
|
@ -26,8 +26,8 @@ func AllResources() []Object {
|
||||
ResourceWorkspace,
|
||||
ResourceWorkspaceApplicationConnect,
|
||||
ResourceWorkspaceBuild,
|
||||
ResourceWorkspaceDormant,
|
||||
ResourceWorkspaceExecution,
|
||||
ResourceWorkspaceLocked,
|
||||
ResourceWorkspaceProxy,
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
opts = &RoleOptions{}
|
||||
}
|
||||
|
||||
ownerAndAdminExceptions := []Object{ResourceWorkspaceLocked}
|
||||
ownerAndAdminExceptions := []Object{ResourceWorkspaceDormant}
|
||||
if opts.NoOwnerWorkspaceExec {
|
||||
ownerAndAdminExceptions = append(ownerAndAdminExceptions,
|
||||
ResourceWorkspaceExecution,
|
||||
@ -150,7 +150,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
ResourceProvisionerDaemon.Type: {ActionRead},
|
||||
}),
|
||||
Org: map[string][]Permission{},
|
||||
User: append(allPermsExcept(ResourceWorkspaceLocked, ResourceUser, ResourceOrganizationMember),
|
||||
User: append(allPermsExcept(ResourceWorkspaceDormant, ResourceUser, ResourceOrganizationMember),
|
||||
Permissions(map[string][]Action{
|
||||
// Users cannot do create/update/delete on themselves, but they
|
||||
// can read their own details.
|
||||
@ -246,7 +246,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
Site: []Permission{},
|
||||
Org: map[string][]Permission{
|
||||
// Org admins should not have workspace exec perms.
|
||||
organizationID: allPermsExcept(ResourceWorkspaceExecution, ResourceWorkspaceLocked),
|
||||
organizationID: allPermsExcept(ResourceWorkspaceExecution, ResourceWorkspaceDormant),
|
||||
},
|
||||
User: []Permission{},
|
||||
}
|
||||
|
@ -319,9 +319,9 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "WorkspaceLocked",
|
||||
Name: "WorkspaceDormant",
|
||||
Actions: rbac.AllActions(),
|
||||
Resource: rbac.ResourceWorkspaceLocked.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
||||
Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {},
|
||||
false: {memberMe, orgAdmin, userAdmin, otherOrgAdmin, otherOrgMember, orgMemberMe, owner, templateAdmin},
|
||||
|
@ -99,24 +99,24 @@ type TemplateScheduleOptions struct {
|
||||
// FailureTTL dictates the duration after which failed workspaces will be
|
||||
// stopped automatically.
|
||||
FailureTTL time.Duration `json:"failure_ttl"`
|
||||
// InactivityTTL dictates the duration after which inactive workspaces will
|
||||
// be locked.
|
||||
InactivityTTL time.Duration `json:"inactivity_ttl"`
|
||||
// LockedTTL dictates the duration after which locked workspaces will be
|
||||
// TimeTilDormant dictates the duration after which inactive workspaces will
|
||||
// go dormant.
|
||||
TimeTilDormant time.Duration `json:"time_til_dormant"`
|
||||
// TimeTilDormantAutoDelete dictates the duration after which dormant workspaces will be
|
||||
// permanently deleted.
|
||||
LockedTTL time.Duration `json:"locked_ttl"`
|
||||
TimeTilDormantAutoDelete time.Duration `json:"time_til_dormant_autodelete"`
|
||||
// UpdateWorkspaceLastUsedAt updates the template's workspaces'
|
||||
// last_used_at field. This is useful for preventing updates to the
|
||||
// templates inactivity_ttl immediately triggering a lock action against
|
||||
// templates inactivity_ttl immediately triggering a dormant action against
|
||||
// workspaces whose last_used_at field violates the new template
|
||||
// inactivity_ttl threshold.
|
||||
UpdateWorkspaceLastUsedAt bool `json:"update_workspace_last_used_at"`
|
||||
// UpdateWorkspaceLockedAt updates the template's workspaces'
|
||||
// locked_at field. This is useful for preventing updates to the
|
||||
// UpdateWorkspaceDormantAt updates the template's workspaces'
|
||||
// dormant_at field. This is useful for preventing updates to the
|
||||
// templates locked_ttl immediately triggering a delete action against
|
||||
// workspaces whose locked_at field violates the new template locked_ttl
|
||||
// workspaces whose dormant_at field violates the new template time_til_dormant_autodelete
|
||||
// threshold.
|
||||
UpdateWorkspaceLockedAt bool `json:"update_workspace_locked_at"`
|
||||
UpdateWorkspaceDormantAt bool `json:"update_workspace_dormant_at"`
|
||||
}
|
||||
|
||||
// TemplateScheduleStore provides an interface for retrieving template
|
||||
@ -150,16 +150,16 @@ func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, te
|
||||
UserAutostopEnabled: true,
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
// Disregard the values in the database, since RestartRequirement,
|
||||
// FailureTTL, InactivityTTL, and LockedTTL are enterprise features.
|
||||
// FailureTTL, TimeTilDormant, and TimeTilDormantAutoDelete are enterprise features.
|
||||
UseRestartRequirement: false,
|
||||
MaxTTL: 0,
|
||||
RestartRequirement: TemplateRestartRequirement{
|
||||
DaysOfWeek: 0,
|
||||
Weeks: 0,
|
||||
},
|
||||
FailureTTL: 0,
|
||||
InactivityTTL: 0,
|
||||
LockedTTL: 0,
|
||||
FailureTTL: 0,
|
||||
TimeTilDormant: 0,
|
||||
TimeTilDormantAutoDelete: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -186,8 +186,8 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp
|
||||
AllowUserAutostart: tpl.AllowUserAutostart,
|
||||
AllowUserAutostop: tpl.AllowUserAutostop,
|
||||
FailureTTL: tpl.FailureTTL,
|
||||
InactivityTTL: tpl.InactivityTTL,
|
||||
LockedTTL: tpl.LockedTTL,
|
||||
TimeTilDormant: tpl.TimeTilDormant,
|
||||
TimeTilDormantAutoDelete: tpl.TimeTilDormantAutoDelete,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update template schedule: %w", err)
|
||||
|
@ -114,16 +114,16 @@ func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectT
|
||||
filter.Name = parser.String(values, "", "name")
|
||||
filter.Status = string(httpapi.ParseCustom(parser, values, "", "status", httpapi.ParseEnum[database.WorkspaceStatus]))
|
||||
filter.HasAgent = parser.String(values, "", "has-agent")
|
||||
filter.LockedAt = parser.Time(values, time.Time{}, "locked_at", "2006-01-02")
|
||||
filter.DormantAt = parser.Time(values, time.Time{}, "dormant_at", "2006-01-02")
|
||||
filter.LastUsedAfter = parser.Time3339Nano(values, time.Time{}, "last_used_after")
|
||||
filter.LastUsedBefore = parser.Time3339Nano(values, time.Time{}, "last_used_before")
|
||||
|
||||
if _, ok := values["deleting_by"]; ok {
|
||||
postFilter.DeletingBy = ptr.Ref(parser.Time(values, time.Time{}, "deleting_by", "2006-01-02"))
|
||||
// We want to make sure to grab locked workspaces since they
|
||||
// We want to make sure to grab dormant workspaces since they
|
||||
// are omitted by default.
|
||||
if filter.LockedAt.IsZero() {
|
||||
filter.LockedAt = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
if filter.DormantAt.IsZero() {
|
||||
filter.DormantAt = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,8 +219,8 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
restartRequirementDaysOfWeek []string
|
||||
restartRequirementWeeks int64
|
||||
failureTTL time.Duration
|
||||
inactivityTTL time.Duration
|
||||
lockedTTL time.Duration
|
||||
dormantTTL time.Duration
|
||||
dormantAutoDeletionTTL time.Duration
|
||||
)
|
||||
if createTemplate.DefaultTTLMillis != nil {
|
||||
defaultTTL = time.Duration(*createTemplate.DefaultTTLMillis) * time.Millisecond
|
||||
@ -232,11 +232,11 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
if createTemplate.FailureTTLMillis != nil {
|
||||
failureTTL = time.Duration(*createTemplate.FailureTTLMillis) * time.Millisecond
|
||||
}
|
||||
if createTemplate.InactivityTTLMillis != nil {
|
||||
inactivityTTL = time.Duration(*createTemplate.InactivityTTLMillis) * time.Millisecond
|
||||
if createTemplate.TimeTilDormantMillis != nil {
|
||||
dormantTTL = time.Duration(*createTemplate.TimeTilDormantMillis) * time.Millisecond
|
||||
}
|
||||
if createTemplate.LockedTTLMillis != nil {
|
||||
lockedTTL = time.Duration(*createTemplate.LockedTTLMillis) * time.Millisecond
|
||||
if createTemplate.TimeTilDormantAutoDeleteMillis != nil {
|
||||
dormantAutoDeletionTTL = time.Duration(*createTemplate.TimeTilDormantAutoDeleteMillis) * time.Millisecond
|
||||
}
|
||||
|
||||
var (
|
||||
@ -270,11 +270,11 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
if failureTTL < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "failure_ttl_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
if inactivityTTL < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "inactivity_ttl_ms", Detail: "Must be a positive integer."})
|
||||
if dormantTTL < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "time_til_dormant_autodeletion_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
if lockedTTL < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "locked_ttl_ms", Detail: "Must be a positive integer."})
|
||||
if dormantAutoDeletionTTL < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "time_til_dormant_autodeletion_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
|
||||
if len(validErrs) > 0 {
|
||||
@ -340,9 +340,9 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
DaysOfWeek: restartRequirementDaysOfWeekParsed,
|
||||
Weeks: restartRequirementWeeks,
|
||||
},
|
||||
FailureTTL: failureTTL,
|
||||
InactivityTTL: inactivityTTL,
|
||||
LockedTTL: lockedTTL,
|
||||
FailureTTL: failureTTL,
|
||||
TimeTilDormant: dormantTTL,
|
||||
TimeTilDormantAutoDelete: dormantAutoDeletionTTL,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("set template schedule options: %s", err)
|
||||
@ -533,13 +533,13 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
if req.FailureTTLMillis < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "failure_ttl_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
if req.InactivityTTLMillis < 0 {
|
||||
if req.TimeTilDormantMillis < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "inactivity_ttl_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
if req.InactivityTTLMillis < 0 {
|
||||
if req.TimeTilDormantMillis < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "inactivity_ttl_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
if req.LockedTTLMillis < 0 {
|
||||
if req.TimeTilDormantAutoDeleteMillis < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "locked_ttl_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
|
||||
@ -565,8 +565,8 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
restartRequirementDaysOfWeekParsed == scheduleOpts.RestartRequirement.DaysOfWeek &&
|
||||
req.RestartRequirement.Weeks == scheduleOpts.RestartRequirement.Weeks &&
|
||||
req.FailureTTLMillis == time.Duration(template.FailureTTL).Milliseconds() &&
|
||||
req.InactivityTTLMillis == time.Duration(template.InactivityTTL).Milliseconds() &&
|
||||
req.LockedTTLMillis == time.Duration(template.LockedTTL).Milliseconds() {
|
||||
req.TimeTilDormantMillis == time.Duration(template.TimeTilDormant).Milliseconds() &&
|
||||
req.TimeTilDormantAutoDeleteMillis == time.Duration(template.TimeTilDormantAutoDelete).Milliseconds() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -598,16 +598,16 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond
|
||||
maxTTL := time.Duration(req.MaxTTLMillis) * time.Millisecond
|
||||
failureTTL := time.Duration(req.FailureTTLMillis) * time.Millisecond
|
||||
inactivityTTL := time.Duration(req.InactivityTTLMillis) * time.Millisecond
|
||||
lockedTTL := time.Duration(req.LockedTTLMillis) * time.Millisecond
|
||||
inactivityTTL := time.Duration(req.TimeTilDormantMillis) * time.Millisecond
|
||||
timeTilDormantAutoDelete := time.Duration(req.TimeTilDormantAutoDeleteMillis) * time.Millisecond
|
||||
|
||||
if defaultTTL != time.Duration(template.DefaultTTL) ||
|
||||
maxTTL != time.Duration(template.MaxTTL) ||
|
||||
restartRequirementDaysOfWeekParsed != scheduleOpts.RestartRequirement.DaysOfWeek ||
|
||||
req.RestartRequirement.Weeks != scheduleOpts.RestartRequirement.Weeks ||
|
||||
failureTTL != time.Duration(template.FailureTTL) ||
|
||||
inactivityTTL != time.Duration(template.InactivityTTL) ||
|
||||
lockedTTL != time.Duration(template.LockedTTL) ||
|
||||
inactivityTTL != time.Duration(template.TimeTilDormant) ||
|
||||
timeTilDormantAutoDelete != time.Duration(template.TimeTilDormantAutoDelete) ||
|
||||
req.AllowUserAutostart != template.AllowUserAutostart ||
|
||||
req.AllowUserAutostop != template.AllowUserAutostop {
|
||||
updated, err = (*api.TemplateScheduleStore.Load()).Set(ctx, tx, updated, schedule.TemplateScheduleOptions{
|
||||
@ -623,10 +623,10 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
Weeks: req.RestartRequirement.Weeks,
|
||||
},
|
||||
FailureTTL: failureTTL,
|
||||
InactivityTTL: inactivityTTL,
|
||||
LockedTTL: lockedTTL,
|
||||
TimeTilDormant: inactivityTTL,
|
||||
TimeTilDormantAutoDelete: timeTilDormantAutoDelete,
|
||||
UpdateWorkspaceLastUsedAt: req.UpdateWorkspaceLastUsedAt,
|
||||
UpdateWorkspaceLockedAt: req.UpdateWorkspaceLockedAt,
|
||||
UpdateWorkspaceDormantAt: req.UpdateWorkspaceDormantAt,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("set template schedule options: %w", err)
|
||||
@ -738,28 +738,28 @@ func (api *API) convertTemplate(
|
||||
buildTimeStats := api.metricsCache.TemplateBuildTimeStats(template.ID)
|
||||
|
||||
return codersdk.Template{
|
||||
ID: template.ID,
|
||||
CreatedAt: template.CreatedAt,
|
||||
UpdatedAt: template.UpdatedAt,
|
||||
OrganizationID: template.OrganizationID,
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Provisioner: codersdk.ProvisionerType(template.Provisioner),
|
||||
ActiveVersionID: template.ActiveVersionID,
|
||||
ActiveUserCount: activeCount,
|
||||
BuildTimeStats: buildTimeStats,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: time.Duration(template.DefaultTTL).Milliseconds(),
|
||||
MaxTTLMillis: time.Duration(template.MaxTTL).Milliseconds(),
|
||||
CreatedByID: template.CreatedBy,
|
||||
CreatedByName: template.CreatedByUsername,
|
||||
AllowUserAutostart: template.AllowUserAutostart,
|
||||
AllowUserAutostop: template.AllowUserAutostop,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
FailureTTLMillis: time.Duration(template.FailureTTL).Milliseconds(),
|
||||
InactivityTTLMillis: time.Duration(template.InactivityTTL).Milliseconds(),
|
||||
LockedTTLMillis: time.Duration(template.LockedTTL).Milliseconds(),
|
||||
ID: template.ID,
|
||||
CreatedAt: template.CreatedAt,
|
||||
UpdatedAt: template.UpdatedAt,
|
||||
OrganizationID: template.OrganizationID,
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Provisioner: codersdk.ProvisionerType(template.Provisioner),
|
||||
ActiveVersionID: template.ActiveVersionID,
|
||||
ActiveUserCount: activeCount,
|
||||
BuildTimeStats: buildTimeStats,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: time.Duration(template.DefaultTTL).Milliseconds(),
|
||||
MaxTTLMillis: time.Duration(template.MaxTTL).Milliseconds(),
|
||||
CreatedByID: template.CreatedBy,
|
||||
CreatedByName: template.CreatedByUsername,
|
||||
AllowUserAutostart: template.AllowUserAutostart,
|
||||
AllowUserAutostop: template.AllowUserAutostop,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
FailureTTLMillis: time.Duration(template.FailureTTL).Milliseconds(),
|
||||
TimeTilDormantMillis: time.Duration(template.TimeTilDormant).Milliseconds(),
|
||||
TimeTilDormantAutoDeleteMillis: time.Duration(template.TimeTilDormantAutoDelete).Milliseconds(),
|
||||
RestartRequirement: codersdk.TemplateRestartRequirement{
|
||||
DaysOfWeek: codersdk.BitmapToWeekdays(uint8(template.RestartRequirementDaysOfWeek)),
|
||||
Weeks: template.RestartRequirementWeeks,
|
||||
|
@ -270,8 +270,8 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
RestartRequirementDaysOfWeek: int16(options.RestartRequirement.DaysOfWeek),
|
||||
RestartRequirementWeeks: options.RestartRequirement.Weeks,
|
||||
FailureTTL: int64(options.FailureTTL),
|
||||
InactivityTTL: int64(options.InactivityTTL),
|
||||
LockedTTL: int64(options.LockedTTL),
|
||||
TimeTilDormant: int64(options.TimeTilDormant),
|
||||
TimeTilDormantAutoDelete: int64(options.TimeTilDormantAutoDelete),
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
return database.Template{}, err
|
||||
@ -320,8 +320,8 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
RestartRequirementDaysOfWeek: int16(options.RestartRequirement.DaysOfWeek),
|
||||
RestartRequirementWeeks: options.RestartRequirement.Weeks,
|
||||
FailureTTL: int64(options.FailureTTL),
|
||||
InactivityTTL: int64(options.InactivityTTL),
|
||||
LockedTTL: int64(options.LockedTTL),
|
||||
TimeTilDormant: int64(options.TimeTilDormant),
|
||||
TimeTilDormantAutoDelete: int64(options.TimeTilDormantAutoDelete),
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
return database.Template{}, err
|
||||
@ -598,8 +598,8 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
RestartRequirementDaysOfWeek: int16(options.RestartRequirement.DaysOfWeek),
|
||||
RestartRequirementWeeks: options.RestartRequirement.Weeks,
|
||||
FailureTTL: int64(options.FailureTTL),
|
||||
InactivityTTL: int64(options.InactivityTTL),
|
||||
LockedTTL: int64(options.LockedTTL),
|
||||
TimeTilDormant: int64(options.TimeTilDormant),
|
||||
TimeTilDormantAutoDelete: int64(options.TimeTilDormantAutoDelete),
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
return database.Template{}, err
|
||||
@ -697,9 +697,9 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
failureTTL = 7 * 24 * time.Hour
|
||||
inactivityTTL = 180 * 24 * time.Hour
|
||||
lockedTTL = 360 * 24 * time.Hour
|
||||
failureTTL = 7 * 24 * time.Hour
|
||||
inactivityTTL = 180 * 24 * time.Hour
|
||||
timeTilDormantAutoDelete = 360 * 24 * time.Hour
|
||||
)
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
@ -711,12 +711,12 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
SetFn: func(ctx context.Context, db database.Store, template database.Template, options schedule.TemplateScheduleOptions) (database.Template, error) {
|
||||
if atomic.AddInt64(&setCalled, 1) == 2 {
|
||||
require.Equal(t, failureTTL, options.FailureTTL)
|
||||
require.Equal(t, inactivityTTL, options.InactivityTTL)
|
||||
require.Equal(t, lockedTTL, options.LockedTTL)
|
||||
require.Equal(t, inactivityTTL, options.TimeTilDormant)
|
||||
require.Equal(t, timeTilDormantAutoDelete, options.TimeTilDormantAutoDelete)
|
||||
}
|
||||
template.FailureTTL = int64(options.FailureTTL)
|
||||
template.InactivityTTL = int64(options.InactivityTTL)
|
||||
template.LockedTTL = int64(options.LockedTTL)
|
||||
template.TimeTilDormant = int64(options.TimeTilDormant)
|
||||
template.TimeTilDormantAutoDelete = int64(options.TimeTilDormantAutoDelete)
|
||||
return template, nil
|
||||
},
|
||||
},
|
||||
@ -725,31 +725,31 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.FailureTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.InactivityTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.LockedTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.TimeTilDormantMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
got, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: 0,
|
||||
RestartRequirement: &template.RestartRequirement,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
FailureTTLMillis: failureTTL.Milliseconds(),
|
||||
InactivityTTLMillis: inactivityTTL.Milliseconds(),
|
||||
LockedTTLMillis: lockedTTL.Milliseconds(),
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: 0,
|
||||
RestartRequirement: &template.RestartRequirement,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
FailureTTLMillis: failureTTL.Milliseconds(),
|
||||
TimeTilDormantMillis: inactivityTTL.Milliseconds(),
|
||||
TimeTilDormantAutoDeleteMillis: timeTilDormantAutoDelete.Milliseconds(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, 2, atomic.LoadInt64(&setCalled))
|
||||
require.Equal(t, failureTTL.Milliseconds(), got.FailureTTLMillis)
|
||||
require.Equal(t, inactivityTTL.Milliseconds(), got.InactivityTTLMillis)
|
||||
require.Equal(t, lockedTTL.Milliseconds(), got.LockedTTLMillis)
|
||||
require.Equal(t, inactivityTTL.Milliseconds(), got.TimeTilDormantMillis)
|
||||
require.Equal(t, timeTilDormantAutoDelete.Milliseconds(), got.TimeTilDormantAutoDeleteMillis)
|
||||
})
|
||||
|
||||
t.Run("IgnoredUnlicensed", func(t *testing.T) {
|
||||
@ -760,29 +760,29 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.FailureTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.InactivityTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.LockedTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.TimeTilDormantMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
got, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: template.DefaultTTLMillis,
|
||||
RestartRequirement: &template.RestartRequirement,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
FailureTTLMillis: failureTTL.Milliseconds(),
|
||||
InactivityTTLMillis: inactivityTTL.Milliseconds(),
|
||||
LockedTTLMillis: lockedTTL.Milliseconds(),
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: template.DefaultTTLMillis,
|
||||
RestartRequirement: &template.RestartRequirement,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
FailureTTLMillis: failureTTL.Milliseconds(),
|
||||
TimeTilDormantMillis: inactivityTTL.Milliseconds(),
|
||||
TimeTilDormantAutoDeleteMillis: timeTilDormantAutoDelete.Milliseconds(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, got.FailureTTLMillis)
|
||||
require.Zero(t, got.InactivityTTLMillis)
|
||||
require.Zero(t, got.LockedTTLMillis)
|
||||
require.Zero(t, got.TimeTilDormantMillis)
|
||||
require.Zero(t, got.TimeTilDormantAutoDeleteMillis)
|
||||
})
|
||||
})
|
||||
|
||||
@ -989,8 +989,8 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
RestartRequirementDaysOfWeek: int16(options.RestartRequirement.DaysOfWeek),
|
||||
RestartRequirementWeeks: options.RestartRequirement.Weeks,
|
||||
FailureTTL: int64(options.FailureTTL),
|
||||
InactivityTTL: int64(options.InactivityTTL),
|
||||
LockedTTL: int64(options.LockedTTL),
|
||||
TimeTilDormant: int64(options.TimeTilDormant),
|
||||
TimeTilDormantAutoDelete: int64(options.TimeTilDormantAutoDelete),
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
return database.Template{}, err
|
||||
@ -1058,8 +1058,8 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
RestartRequirementDaysOfWeek: int16(options.RestartRequirement.DaysOfWeek),
|
||||
RestartRequirementWeeks: options.RestartRequirement.Weeks,
|
||||
FailureTTL: int64(options.FailureTTL),
|
||||
InactivityTTL: int64(options.InactivityTTL),
|
||||
LockedTTL: int64(options.LockedTTL),
|
||||
TimeTilDormant: int64(options.TimeTilDormant),
|
||||
TimeTilDormantAutoDelete: int64(options.TimeTilDormantAutoDelete),
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
return database.Template{}, err
|
||||
|
@ -765,43 +765,43 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// @Summary Update workspace lock by id.
|
||||
// @ID update-workspace-lock-by-id
|
||||
// @Summary Update workspace dormancy status by id.
|
||||
// @ID update-workspace-dormancy-status-by-id
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Workspaces
|
||||
// @Param workspace path string true "Workspace ID" format(uuid)
|
||||
// @Param request body codersdk.UpdateWorkspaceLock true "Lock or unlock a workspace"
|
||||
// @Param request body codersdk.UpdateWorkspaceDormancy true "Make a workspace dormant or active"
|
||||
// @Success 200 {object} codersdk.Workspace
|
||||
// @Router /workspaces/{workspace}/lock [put]
|
||||
func (api *API) putWorkspaceLock(rw http.ResponseWriter, r *http.Request) {
|
||||
// @Router /workspaces/{workspace}/dormant [put]
|
||||
func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
workspace := httpmw.WorkspaceParam(r)
|
||||
|
||||
var req codersdk.UpdateWorkspaceLock
|
||||
var req codersdk.UpdateWorkspaceDormancy
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
// If the workspace is already in the desired state do nothing!
|
||||
if workspace.LockedAt.Valid == req.Lock {
|
||||
if workspace.DormantAt.Valid == req.Dormant {
|
||||
httpapi.Write(ctx, rw, http.StatusNotModified, codersdk.Response{
|
||||
Message: "Nothing to do!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
lockedAt := sql.NullTime{
|
||||
Valid: req.Lock,
|
||||
dormantAt := sql.NullTime{
|
||||
Valid: req.Dormant,
|
||||
}
|
||||
if req.Lock {
|
||||
lockedAt.Time = database.Now()
|
||||
if req.Dormant {
|
||||
dormantAt.Time = database.Now()
|
||||
}
|
||||
|
||||
workspace, err := api.Database.UpdateWorkspaceLockedDeletingAt(ctx, database.UpdateWorkspaceLockedDeletingAtParams{
|
||||
ID: workspace.ID,
|
||||
LockedAt: lockedAt,
|
||||
workspace, err := api.Database.UpdateWorkspaceDormantDeletingAt(ctx, database.UpdateWorkspaceDormantDeletingAtParams{
|
||||
ID: workspace.ID,
|
||||
DormantAt: dormantAt,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
@ -1153,14 +1153,14 @@ func convertWorkspace(
|
||||
autostartSchedule = &workspace.AutostartSchedule.String
|
||||
}
|
||||
|
||||
var lockedAt *time.Time
|
||||
if workspace.LockedAt.Valid {
|
||||
lockedAt = &workspace.LockedAt.Time
|
||||
var dormantAt *time.Time
|
||||
if workspace.DormantAt.Valid {
|
||||
dormantAt = &workspace.DormantAt.Time
|
||||
}
|
||||
|
||||
var deletedAt *time.Time
|
||||
var deletingAt *time.Time
|
||||
if workspace.DeletingAt.Valid {
|
||||
deletedAt = &workspace.DeletingAt.Time
|
||||
deletingAt = &workspace.DeletingAt.Time
|
||||
}
|
||||
|
||||
failingAgents := []uuid.UUID{}
|
||||
@ -1192,8 +1192,8 @@ func convertWorkspace(
|
||||
AutostartSchedule: autostartSchedule,
|
||||
TTLMillis: ttlMillis,
|
||||
LastUsedAt: workspace.LastUsedAt,
|
||||
DeletingAt: deletedAt,
|
||||
LockedAt: lockedAt,
|
||||
DeletingAt: deletingAt,
|
||||
DormantAt: dormantAt,
|
||||
Health: codersdk.WorkspaceHealth{
|
||||
Healthy: len(failingAgents) == 0,
|
||||
FailingAgents: failingAgents,
|
||||
|
@ -1363,9 +1363,9 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
||||
TemplateScheduleStore: schedule.MockTemplateScheduleStore{
|
||||
SetFn: func(ctx context.Context, db database.Store, template database.Template, options schedule.TemplateScheduleOptions) (database.Template, error) {
|
||||
if atomic.AddInt64(&setCalled, 1) == 2 {
|
||||
assert.Equal(t, inactivityTTL, options.InactivityTTL)
|
||||
assert.Equal(t, inactivityTTL, options.TimeTilDormant)
|
||||
}
|
||||
template.InactivityTTL = int64(options.InactivityTTL)
|
||||
template.TimeTilDormant = int64(options.TimeTilDormant)
|
||||
return template, nil
|
||||
},
|
||||
},
|
||||
@ -1385,11 +1385,11 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
template, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
InactivityTTLMillis: inactivityTTL.Milliseconds(),
|
||||
TimeTilDormantMillis: inactivityTTL.Milliseconds(),
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, inactivityTTL.Milliseconds(), template.InactivityTTLMillis)
|
||||
assert.Equal(t, inactivityTTL.Milliseconds(), template.TimeTilDormantMillis)
|
||||
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
@ -1404,11 +1404,11 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
||||
|
||||
assert.NoError(t, err)
|
||||
// we are expecting that no workspaces are returned as user is unlicensed
|
||||
// and template.InactivityTTL should be 0
|
||||
// and template.TimeTilDormant should be 0
|
||||
assert.Len(t, res.Workspaces, 0)
|
||||
})
|
||||
|
||||
t.Run("LockedAt", func(t *testing.T) {
|
||||
t.Run("DormantAt", func(t *testing.T) {
|
||||
// this test has a licensed counterpart in enterprise/coderd/workspaces_test.go: FilterQueryHasDeletingByAndLicensed
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
@ -1428,24 +1428,24 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
lockedWorkspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, lockedWorkspace.LatestBuild.ID)
|
||||
dormantWorkspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, dormantWorkspace.LatestBuild.ID)
|
||||
|
||||
// Create another workspace to validate that we do not return unlocked workspaces.
|
||||
// Create another workspace to validate that we do not return active workspaces.
|
||||
_ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, lockedWorkspace.LatestBuild.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, dormantWorkspace.LatestBuild.ID)
|
||||
|
||||
err := client.UpdateWorkspaceLock(ctx, lockedWorkspace.ID, codersdk.UpdateWorkspaceLock{
|
||||
Lock: true,
|
||||
err := client.UpdateWorkspaceDormancy(ctx, dormantWorkspace.ID, codersdk.UpdateWorkspaceDormancy{
|
||||
Dormant: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
||||
FilterQuery: fmt.Sprintf("locked_at:%s", time.Now().Add(-time.Minute).Format("2006-01-02")),
|
||||
FilterQuery: fmt.Sprintf("dormant_at:%s", time.Now().Add(-time.Minute).Format("2006-01-02")),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res.Workspaces, 1)
|
||||
require.NotNil(t, res.Workspaces[0].LockedAt)
|
||||
require.NotNil(t, res.Workspaces[0].DormantAt)
|
||||
})
|
||||
|
||||
t.Run("LastUsed", func(t *testing.T) {
|
||||
@ -2782,21 +2782,21 @@ func TestWorkspaceWithEphemeralRichParameters(t *testing.T) {
|
||||
require.ElementsMatch(t, expectedBuildParameters, workspaceBuildParameters)
|
||||
}
|
||||
|
||||
func TestWorkspaceLock(t *testing.T) {
|
||||
func TestWorkspaceDormant(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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)
|
||||
lockedTTL = time.Minute
|
||||
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)
|
||||
timeTilDormantAutoDelete = time.Minute
|
||||
)
|
||||
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.LockedTTLMillis = ptr.Ref[int64](lockedTTL.Milliseconds())
|
||||
ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref[int64](timeTilDormantAutoDelete.Milliseconds())
|
||||
})
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
@ -2805,32 +2805,32 @@ func TestWorkspaceLock(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
lastUsedAt := workspace.LastUsedAt
|
||||
err := client.UpdateWorkspaceLock(ctx, workspace.ID, codersdk.UpdateWorkspaceLock{
|
||||
Lock: true,
|
||||
err := client.UpdateWorkspaceDormancy(ctx, workspace.ID, codersdk.UpdateWorkspaceDormancy{
|
||||
Dormant: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
require.NoError(t, err, "fetch provisioned workspace")
|
||||
// The template doesn't have a locked_ttl set so this should be nil.
|
||||
// The template doesn't have a time_til_dormant_autodelete set so this should be nil.
|
||||
require.Nil(t, workspace.DeletingAt)
|
||||
require.NotNil(t, workspace.LockedAt)
|
||||
require.WithinRange(t, *workspace.LockedAt, time.Now().Add(-time.Second*10), time.Now())
|
||||
require.NotNil(t, workspace.DormantAt)
|
||||
require.WithinRange(t, *workspace.DormantAt, time.Now().Add(-time.Second*10), time.Now())
|
||||
require.Equal(t, lastUsedAt, workspace.LastUsedAt)
|
||||
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
lastUsedAt = workspace.LastUsedAt
|
||||
err = client.UpdateWorkspaceLock(ctx, workspace.ID, codersdk.UpdateWorkspaceLock{
|
||||
Lock: false,
|
||||
err = client.UpdateWorkspaceDormancy(ctx, workspace.ID, codersdk.UpdateWorkspaceDormancy{
|
||||
Dormant: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
workspace, err = client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch provisioned workspace")
|
||||
require.Nil(t, workspace.LockedAt)
|
||||
// The template doesn't have a locked_ttl set so this should be nil.
|
||||
require.Nil(t, workspace.DormantAt)
|
||||
// The template doesn't have a time_til_dormant_autodelete set so this should be nil.
|
||||
require.Nil(t, workspace.DeletingAt)
|
||||
// The last_used_at should get updated when we unlock the workspace.
|
||||
// The last_used_at should get updated when we activate the workspace.
|
||||
require.True(t, workspace.LastUsedAt.After(lastUsedAt))
|
||||
})
|
||||
|
||||
@ -2849,23 +2849,23 @@ func TestWorkspaceLock(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
err := client.UpdateWorkspaceLock(ctx, workspace.ID, codersdk.UpdateWorkspaceLock{
|
||||
Lock: true,
|
||||
err := client.UpdateWorkspaceDormancy(ctx, workspace.ID, codersdk.UpdateWorkspaceDormancy{
|
||||
Dormant: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should be able to stop a workspace while it is locked.
|
||||
// Should be able to stop a workspace while it is dormant.
|
||||
coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
||||
|
||||
// Should not be able to start a workspace while it is locked.
|
||||
// Should not be able to start a workspace while it is dormant.
|
||||
_, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
TemplateVersionID: template.ActiveVersionID,
|
||||
Transition: codersdk.WorkspaceTransition(database.WorkspaceTransitionStart),
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
err = client.UpdateWorkspaceLock(ctx, workspace.ID, codersdk.UpdateWorkspaceLock{
|
||||
Lock: false,
|
||||
err = client.UpdateWorkspaceDormancy(ctx, workspace.ID, codersdk.UpdateWorkspaceDormancy{
|
||||
Dormant: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStop, database.WorkspaceTransitionStart)
|
||||
|
Reference in New Issue
Block a user