feat: allow bumping workspace deadline (#1828)

* Adds a `bump` command to extend workspace build deadline
 * Reduces WARN-level logging spam from autobuild executor
 * Modifies `cli/ssh` notifications to read from workspace build deadline and to notify relative time instead (sidestepping the problem of figuring out a user's timezone across multiple OSes)
 * Shows workspace extension time in `coder list` output e.g.
    ```
    WORKSPACE        TEMPLATE  STATUS   LAST BUILT  OUTDATED  AUTOSTART        TTL        
    developer/test1  docker    Running  4m          false     0 9 * * MON-FRI  15m (+5m)  
    ```
This commit is contained in:
Cian Johnston
2022-05-27 20:04:33 +01:00
committed by GitHub
parent bde3779fec
commit ff542afe87
8 changed files with 383 additions and 38 deletions

View File

@ -88,7 +88,7 @@ func (e *Executor) runOnce(t time.Time) error {
}
if !priorJob.CompletedAt.Valid || priorJob.Error.String != "" {
e.log.Warn(e.ctx, "last workspace build did not complete successfully, skipping",
e.log.Debug(e.ctx, "last workspace build did not complete successfully, skipping",
slog.F("workspace_id", ws.ID),
slog.F("error", priorJob.Error.String),
)
@ -107,13 +107,14 @@ func (e *Executor) runOnce(t time.Time) error {
)
continue
}
// Truncate to nearest minute for consistency with autostart behavior
nextTransition = priorHistory.Deadline.Truncate(time.Minute)
// For stopping, do not truncate. This is inconsistent with autostart, but
// it ensures we will not stop too early.
nextTransition = priorHistory.Deadline
case database.WorkspaceTransitionStop:
validTransition = database.WorkspaceTransitionStart
sched, err := schedule.Weekly(ws.AutostartSchedule.String)
if err != nil {
e.log.Warn(e.ctx, "workspace has invalid autostart schedule, skipping",
e.log.Debug(e.ctx, "workspace has invalid autostart schedule, skipping",
slog.F("workspace_id", ws.ID),
slog.F("autostart_schedule", ws.AutostartSchedule.String),
)

View File

@ -550,7 +550,7 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr
workspace, err := db.GetWorkspaceByID(ctx, workspaceBuild.WorkspaceID)
if err == nil {
if workspace.Ttl.Valid {
workspaceDeadline = now.Add(time.Duration(workspace.Ttl.Int64)).Truncate(time.Minute)
workspaceDeadline = now.Add(time.Duration(workspace.Ttl.Int64))
}
} else {
// Huh? Did the workspace get deleted?

View File

@ -604,7 +604,7 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
return xerrors.Errorf("workspace must be started, current status: %s", build.Transition)
}
newDeadline := req.Deadline.Truncate(time.Minute).UTC()
newDeadline := req.Deadline.UTC()
if newDeadline.IsZero() {
// This should not be possible because the struct validation field enforces a non-zero value.
code = http.StatusBadRequest
@ -616,8 +616,8 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
return xerrors.Errorf("new deadline %q must be after existing deadline %q", newDeadline.Format(time.RFC3339), build.Deadline.Format(time.RFC3339))
}
// both newDeadline and build.Deadline are truncated to time.Minute
if newDeadline == build.Deadline {
// Disallow updates within less than one minute
if withinDuration(newDeadline, build.Deadline, time.Minute) {
code = http.StatusNotModified
return nil
}
@ -786,6 +786,7 @@ func convertWorkspaces(ctx context.Context, db database.Store, workspaces []data
InitiatorID: workspaceBuild.InitiatorID,
ProvisionerState: workspaceBuild.ProvisionerState,
JobID: workspaceBuild.JobID,
Deadline: workspaceBuild.Deadline,
}
}
templateByID := map[uuid.UUID]database.Template{}
@ -848,3 +849,12 @@ func convertSQLNullInt64(i sql.NullInt64) *time.Duration {
return (*time.Duration)(&i.Int64)
}
func withinDuration(t1, t2 time.Time, d time.Duration) bool {
dt := t1.Sub(t2)
if dt < -d || dt > d {
return false
}
return true
}