@Summary Cancel workspace build @ID cancel-workspace-build @Security CoderSessionToken @Produce json @Tags Builds @Param workspacebuild path string true "Workspace build ID" @Param expect_status query string false "Expected status of the job. If expect_status is supplied, the request will be rejecte
(rw http.ResponseWriter, r *http.Request)
| 663 | // @Success 200 {object} codersdk.Response |
| 664 | // @Router /api/v2/workspacebuilds/{workspacebuild}/cancel [patch] |
| 665 | func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Request) { |
| 666 | ctx := r.Context() |
| 667 | |
| 668 | var expectStatus database.ProvisionerJobStatus |
| 669 | expectStatusParam := r.URL.Query().Get("expect_status") |
| 670 | if expectStatusParam != "" { |
| 671 | if expectStatusParam != "running" && expectStatusParam != "pending" { |
| 672 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 673 | Message: fmt.Sprintf("Invalid expect_status %q. Only 'running' or 'pending' are allowed.", expectStatusParam), |
| 674 | }) |
| 675 | return |
| 676 | } |
| 677 | expectStatus = database.ProvisionerJobStatus(expectStatusParam) |
| 678 | } |
| 679 | |
| 680 | workspaceBuild := httpmw.WorkspaceBuildParam(r) |
| 681 | workspace, err := api.Database.GetWorkspaceByID(ctx, workspaceBuild.WorkspaceID) |
| 682 | if err != nil { |
| 683 | httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ |
| 684 | Message: "No workspace exists for this job.", |
| 685 | }) |
| 686 | return |
| 687 | } |
| 688 | |
| 689 | code := http.StatusInternalServerError |
| 690 | resp := codersdk.Response{ |
| 691 | Message: "Internal error canceling workspace build.", |
| 692 | } |
| 693 | err = api.Database.InTx(func(db database.Store) error { |
| 694 | valid, err := verifyUserCanCancelWorkspaceBuilds(ctx, db, httpmw.APIKey(r).UserID, workspace.TemplateID, expectStatus) |
| 695 | if err != nil { |
| 696 | code = http.StatusInternalServerError |
| 697 | resp.Message = "Internal error verifying permission to cancel workspace build." |
| 698 | resp.Detail = err.Error() |
| 699 | |
| 700 | return xerrors.Errorf("verify user can cancel workspace builds: %w", err) |
| 701 | } |
| 702 | if !valid { |
| 703 | code = http.StatusForbidden |
| 704 | resp.Message = "User is not allowed to cancel workspace builds. Owner role is required." |
| 705 | |
| 706 | return xerrors.New("user is not allowed to cancel workspace builds") |
| 707 | } |
| 708 | |
| 709 | job, err := db.GetProvisionerJobByIDWithLock(ctx, workspaceBuild.JobID) |
| 710 | if err != nil { |
| 711 | code = http.StatusInternalServerError |
| 712 | resp.Message = "Internal error fetching provisioner job." |
| 713 | resp.Detail = err.Error() |
| 714 | |
| 715 | return xerrors.Errorf("get provisioner job: %w", err) |
| 716 | } |
| 717 | if job.CompletedAt.Valid { |
| 718 | code = http.StatusBadRequest |
| 719 | resp.Message = "Job has already completed!" |
| 720 | |
| 721 | return xerrors.New("job has already completed") |
| 722 | } |
nothing calls this directly
no test coverage detected