fix: allow setting workspace deadline as early as now plus 30 minutes (#2328)

This PR makes the following changes:

- coderd: /api/v2/workspaces/:workspace/extend now accepts any time at least 30 minutes in the future.
- coder bump command also allows the above. Some small copy changes to command.
- coder bump now actually enforces template-level maxima.
This commit is contained in:
Cian Johnston
2022-06-14 22:39:15 +01:00
committed by GitHub
parent 4734636b17
commit 02ad60fd75
4 changed files with 88 additions and 40 deletions

View File

@ -575,21 +575,47 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
resp := httpapi.Response{}
err := api.Database.InTx(func(s database.Store) error {
template, err := s.GetTemplateByID(r.Context(), workspace.TemplateID)
if err != nil {
code = http.StatusInternalServerError
resp.Message = "Error fetching workspace template!"
return xerrors.Errorf("get workspace template: %w", err)
}
build, err := s.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)
if err != nil {
code = http.StatusInternalServerError
resp.Message = "Workspace not found."
resp.Message = "Error fetching workspace build."
return xerrors.Errorf("get latest workspace build: %w", err)
}
job, err := s.GetProvisionerJobByID(r.Context(), build.JobID)
if err != nil {
code = http.StatusInternalServerError
resp.Message = "Error fetching workspace provisioner job."
return xerrors.Errorf("get provisioner job: %w", err)
}
if build.Transition != database.WorkspaceTransitionStart {
code = http.StatusConflict
resp.Message = "Workspace must be started, current status: " + string(build.Transition)
return xerrors.Errorf("workspace must be started, current status: %s", build.Transition)
}
if !job.CompletedAt.Valid {
code = http.StatusConflict
resp.Message = "Workspace is still building!"
return xerrors.Errorf("workspace is still building")
}
if build.Deadline.IsZero() {
code = http.StatusConflict
resp.Message = "Workspace shutdown is manual."
return xerrors.Errorf("workspace shutdown is manual")
}
newDeadline := req.Deadline.UTC()
if err := validWorkspaceDeadline(build.Deadline, newDeadline); err != nil {
if err := validWorkspaceDeadline(job.CompletedAt.Time, newDeadline, time.Duration(template.MaxTtl)); err != nil {
code = http.StatusBadRequest
resp.Message = "Bad extend workspace request."
resp.Validations = append(resp.Validations, httpapi.Error{Field: "deadline", Detail: err.Error()})
@ -878,23 +904,20 @@ func validWorkspaceTTLMillis(millis *int64, max time.Duration) (sql.NullInt64, e
}, nil
}
func validWorkspaceDeadline(old, new time.Time) error {
if old.IsZero() {
return xerrors.New("nothing to do: no existing deadline set")
func validWorkspaceDeadline(startedAt, newDeadline time.Time, max time.Duration) error {
soon := time.Now().Add(29 * time.Minute)
if newDeadline.Before(soon) {
return xerrors.New("new deadline must be at least 30 minutes in the future")
}
now := time.Now()
if new.Before(now) {
return xerrors.New("new deadline must be in the future")
// No idea how this could happen.
if newDeadline.Before(startedAt) {
return xerrors.Errorf("new deadline must be before workspace start time")
}
delta := new.Sub(old)
if delta < time.Minute {
return xerrors.New("minimum extension is one minute")
}
if delta > 24*time.Hour {
return xerrors.New("maximum extension is 24 hours")
delta := newDeadline.Sub(startedAt)
if delta > max {
return xerrors.New("new deadline is greater than template allows")
}
return nil