@Summary Extend workspace deadline by ID @ID extend-workspace-deadline-by-id @Security CoderSessionToken @Accept json @Produce json @Tags Workspaces @Param workspace path string true "Workspace ID" format(uuid) @Param request body codersdk.PutExtendWorkspaceRequest true "Extend deadline update reque
(rw http.ResponseWriter, r *http.Request)
| 1614 | // @Success 200 {object} codersdk.Response |
| 1615 | // @Router /api/v2/workspaces/{workspace}/extend [put] |
| 1616 | func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) { |
| 1617 | ctx := r.Context() |
| 1618 | workspace := httpmw.WorkspaceParam(r) |
| 1619 | |
| 1620 | var req codersdk.PutExtendWorkspaceRequest |
| 1621 | if !httpapi.Read(ctx, rw, r, &req) { |
| 1622 | return |
| 1623 | } |
| 1624 | |
| 1625 | // Deadline extensions are not supported for prebuilt workspaces. |
| 1626 | // Prebuilds are managed by the reconciliation loop and must always have |
| 1627 | // Deadline and MaxDeadline unset. |
| 1628 | if workspace.IsPrebuild() { |
| 1629 | httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ |
| 1630 | Message: "Deadline extension is not supported for prebuilt workspaces", |
| 1631 | Detail: "Prebuilt workspaces do not support user deadline modifications. Deadline extension is only applicable to regular workspaces", |
| 1632 | }) |
| 1633 | return |
| 1634 | } |
| 1635 | |
| 1636 | code := http.StatusOK |
| 1637 | resp := codersdk.Response{} |
| 1638 | |
| 1639 | err := api.Database.InTx(func(s database.Store) error { |
| 1640 | build, err := s.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) |
| 1641 | if err != nil { |
| 1642 | code = http.StatusInternalServerError |
| 1643 | resp.Message = "Error fetching workspace build." |
| 1644 | return xerrors.Errorf("get latest workspace build: %w", err) |
| 1645 | } |
| 1646 | |
| 1647 | job, err := s.GetProvisionerJobByID(ctx, build.JobID) |
| 1648 | if err != nil { |
| 1649 | code = http.StatusInternalServerError |
| 1650 | resp.Message = "Error fetching workspace provisioner job." |
| 1651 | return xerrors.Errorf("get provisioner job: %w", err) |
| 1652 | } |
| 1653 | |
| 1654 | if build.Transition != database.WorkspaceTransitionStart { |
| 1655 | code = http.StatusConflict |
| 1656 | resp.Message = "Workspace must be started, current status: " + string(build.Transition) |
| 1657 | return xerrors.Errorf("workspace must be started, current status: %s", build.Transition) |
| 1658 | } |
| 1659 | |
| 1660 | if !job.CompletedAt.Valid { |
| 1661 | code = http.StatusConflict |
| 1662 | resp.Message = "Workspace is still building!" |
| 1663 | return xerrors.Errorf("workspace is still building") |
| 1664 | } |
| 1665 | |
| 1666 | if build.Deadline.IsZero() { |
| 1667 | code = http.StatusConflict |
| 1668 | resp.Message = "Workspace shutdown is manual." |
| 1669 | return xerrors.Errorf("workspace shutdown is manual") |
| 1670 | } |
| 1671 | |
| 1672 | // Use injected Clock to allow time mocking in tests |
| 1673 | now := api.Clock.Now() |
nothing calls this directly
no test coverage detected