mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: add API/SDK support for autostop extension (#1778)
* Adds deadline column to workspace_builds, associated DB/API plumbing * database: Upon inserting a row into workspace_builds, deadline will initially be zero. * autobuild: Executor now checks the Deadline field of the workspace_build for the purpose of autostop logic. * coderd: Adds a new route /api/v2/workspaces/:workspace/extend which allows updating the deadline of the currently active workspace build. The new deadline must be after the existing deadline, and not the zero time. * provisionerd: updates workspace_build.deadline upon successful workspace build completion (equal to now plus workspace TTL, if it exists).
This commit is contained in:
@ -477,7 +477,8 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
||||
InitiatorID: apiKey.UserID,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
JobID: provisionerJob.ID,
|
||||
BuildNumber: 1, // First build!
|
||||
BuildNumber: 1, // First build!
|
||||
Deadline: time.Time{}, // provisionerd will set this upon success
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert workspace build: %w", err)
|
||||
@ -570,6 +571,69 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
|
||||
workspace := httpmw.WorkspaceParam(r)
|
||||
|
||||
if !api.Authorize(rw, r, rbac.ActionUpdate, workspace) {
|
||||
return
|
||||
}
|
||||
|
||||
var req codersdk.PutExtendWorkspaceRequest
|
||||
if !httpapi.Read(rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
var code = http.StatusOK
|
||||
|
||||
err := api.Database.InTx(func(s database.Store) error {
|
||||
build, err := s.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)
|
||||
if err != nil {
|
||||
code = http.StatusInternalServerError
|
||||
return xerrors.Errorf("get latest workspace build: %w", err)
|
||||
}
|
||||
|
||||
if build.Transition != database.WorkspaceTransitionStart {
|
||||
code = http.StatusConflict
|
||||
return xerrors.Errorf("workspace must be started, current status: %s", build.Transition)
|
||||
}
|
||||
|
||||
newDeadline := req.Deadline.Truncate(time.Minute).UTC()
|
||||
if newDeadline.IsZero() {
|
||||
// This should not be possible because the struct validation field enforces a non-zero value.
|
||||
code = http.StatusBadRequest
|
||||
return xerrors.New("new deadline cannot be zero")
|
||||
}
|
||||
|
||||
if newDeadline.Before(build.Deadline) || newDeadline.Before(time.Now()) {
|
||||
code = http.StatusBadRequest
|
||||
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 {
|
||||
code = http.StatusNotModified
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.UpdateWorkspaceBuildByID(r.Context(), database.UpdateWorkspaceBuildByIDParams{
|
||||
ID: build.ID,
|
||||
UpdatedAt: build.UpdatedAt,
|
||||
ProvisionerState: build.ProvisionerState,
|
||||
Deadline: newDeadline,
|
||||
}); err != nil {
|
||||
return xerrors.Errorf("update workspace build: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
var resp = httpapi.Response{}
|
||||
if err != nil {
|
||||
resp.Message = err.Error()
|
||||
}
|
||||
httpapi.Write(rw, code, resp)
|
||||
}
|
||||
|
||||
func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
|
||||
workspace := httpmw.WorkspaceParam(r)
|
||||
|
||||
|
Reference in New Issue
Block a user