@@ -581,12 +581,12 @@ func (api *API) notifyWorkspaceUpdated(
581
581
// @Produce json
582
582
// @Tags Builds
583
583
// @Param workspacebuild path string true "Workspace build ID"
584
- // @Param expect_state query string false "Expected state of the job"
584
+ // @Param expect_status query string false "Expected status of the job" Enums(running, pending)
585
585
// @Success 200 {object} codersdk.Response
586
586
// @Router /workspacebuilds/{workspacebuild}/cancel [patch]
587
587
func (api * API ) patchCancelWorkspaceBuild (rw http.ResponseWriter , r * http.Request ) {
588
588
ctx := r .Context ()
589
- expectState := r .URL .Query ().Get ("expect_state " )
589
+ expectStatus := r .URL .Query ().Get ("expect_status " )
590
590
workspaceBuild := httpmw .WorkspaceBuildParam (r )
591
591
workspace , err := api .Database .GetWorkspaceByID (ctx , workspaceBuild .WorkspaceID )
592
592
if err != nil {
@@ -596,90 +596,60 @@ func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Reques
596
596
return
597
597
}
598
598
599
- valid , err := api .verifyUserCanCancelWorkspaceBuilds (ctx , httpmw .APIKey (r ).UserID , workspace .TemplateID )
600
- if err != nil {
601
- httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
602
- Message : "Internal error verifying permission to cancel workspace build." ,
603
- Detail : err .Error (),
604
- })
605
- return
606
- }
607
- if ! valid {
608
- httpapi .Write (ctx , rw , http .StatusForbidden , codersdk.Response {
609
- Message : "User is not allowed to cancel workspace builds. Owner role is required." ,
610
- })
611
- return
612
- }
613
-
614
- if expectState != "" {
615
- jobStatus := database .ProvisionerJobStatus (expectState )
616
- if ! jobStatus .Valid () {
617
- httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
618
- Message : "Invalid expect_state. Only 'pending', 'running', 'succeeded', 'canceling', 'canceled', 'failed', or 'unknown' are allowed." ,
619
- })
620
- return
621
- }
622
-
623
- // use local error type to detect if the error is due to job state mismatch
624
- var errJobStateMismatch = xerrors .New ("job is not in the expected state" )
625
-
626
- err := api .Database .InTx (func (store database.Store ) error {
627
- job , err := store .GetProvisionerJobByIDForUpdate (ctx , workspaceBuild .JobID )
628
- if err != nil {
629
- return err
630
- }
631
-
632
- if job .JobStatus != jobStatus {
633
- return errJobStateMismatch
634
- }
635
-
636
- return store .UpdateProvisionerJobWithCancelByID (ctx , database.UpdateProvisionerJobWithCancelByIDParams {
637
- ID : job .ID ,
638
- CanceledAt : sql.NullTime {
639
- Time : dbtime .Now (),
640
- Valid : true ,
641
- },
642
- CompletedAt : sql.NullTime {
643
- Time : dbtime .Now (),
644
- Valid : ! job .WorkerID .Valid ,
645
- },
646
- })
647
- }, nil )
599
+ err = api .Database .InTx (func (store database.Store ) error {
600
+ valid , err := api .verifyUserCanCancelWorkspaceBuilds (ctx , store , httpmw .APIKey (r ).UserID , workspace .TemplateID )
648
601
if err != nil {
649
- if errors .Is (err , errJobStateMismatch ) {
650
- httpapi .Write (ctx , rw , http .StatusPreconditionFailed , codersdk.Response {
651
- Message : "Job is not in the expected state." ,
652
- })
653
- return
654
- }
655
602
httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
656
- Message : "Internal error fetching provisioner job ." ,
603
+ Message : "Internal error verifying permission to cancel workspace build ." ,
657
604
Detail : err .Error (),
658
605
})
659
- return
606
+ return nil
607
+ }
608
+ if ! valid {
609
+ httpapi .Write (ctx , rw , http .StatusForbidden , codersdk.Response {
610
+ Message : "User is not allowed to cancel workspace builds. Owner role is required." ,
611
+ })
612
+ return nil
660
613
}
661
- } else {
662
- job , err := api . Database .GetProvisionerJobByID (ctx , workspaceBuild .JobID )
614
+
615
+ job , err := store .GetProvisionerJobByID (ctx , workspaceBuild .JobID )
663
616
if err != nil {
664
617
httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
665
618
Message : "Internal error fetching provisioner job." ,
666
619
Detail : err .Error (),
667
620
})
668
- return
621
+ return nil
669
622
}
670
623
if job .CompletedAt .Valid {
671
624
httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
672
625
Message : "Job has already completed!" ,
673
626
})
674
- return
627
+ return nil
675
628
}
676
629
if job .CanceledAt .Valid {
677
630
httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
678
631
Message : "Job has already been marked as canceled!" ,
679
632
})
680
- return
633
+ return nil
634
+ }
635
+
636
+ if expectStatus != "" {
637
+ if expectStatus != "running" && expectStatus != "pending" {
638
+ httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
639
+ Message : "Invalid expect_status. Only 'running' or 'pending' are allowed." ,
640
+ })
641
+ return nil
642
+ }
643
+
644
+ if job .JobStatus != database .ProvisionerJobStatus (expectStatus ) {
645
+ httpapi .Write (ctx , rw , http .StatusPreconditionFailed , codersdk.Response {
646
+ Message : "Job is not in the expected state." ,
647
+ })
648
+ return nil
649
+ }
681
650
}
682
- err = api .Database .UpdateProvisionerJobWithCancelByID (ctx , database.UpdateProvisionerJobWithCancelByIDParams {
651
+
652
+ err = store .UpdateProvisionerJobWithCancelByID (ctx , database.UpdateProvisionerJobWithCancelByIDParams {
683
653
ID : job .ID ,
684
654
CanceledAt : sql.NullTime {
685
655
Time : dbtime .Now (),
@@ -696,9 +666,19 @@ func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Reques
696
666
Message : "Internal error updating provisioner job." ,
697
667
Detail : err .Error (),
698
668
})
699
- return
669
+ return nil
700
670
}
671
+
672
+ return nil
673
+ }, nil )
674
+ if err != nil {
675
+ httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
676
+ Message : "Internal error updating provisioner job." ,
677
+ Detail : err .Error (),
678
+ })
679
+ return
701
680
}
681
+
702
682
api .publishWorkspaceUpdate (ctx , workspace .OwnerID , wspubsub.WorkspaceEvent {
703
683
Kind : wspubsub .WorkspaceEventKindStateChange ,
704
684
WorkspaceID : workspace .ID ,
@@ -709,8 +689,8 @@ func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Reques
709
689
})
710
690
}
711
691
712
- func (api * API ) verifyUserCanCancelWorkspaceBuilds (ctx context.Context , userID uuid.UUID , templateID uuid.UUID ) (bool , error ) {
713
- template , err := api . Database .GetTemplateByID (ctx , templateID )
692
+ func (* API ) verifyUserCanCancelWorkspaceBuilds (ctx context.Context , store database. Store , userID uuid.UUID , templateID uuid.UUID ) (bool , error ) {
693
+ template , err := store .GetTemplateByID (ctx , templateID )
714
694
if err != nil {
715
695
return false , xerrors .New ("no template exists for this workspace" )
716
696
}
@@ -719,7 +699,7 @@ func (api *API) verifyUserCanCancelWorkspaceBuilds(ctx context.Context, userID u
719
699
return true , nil // all users can cancel workspace builds
720
700
}
721
701
722
- user , err := api . Database .GetUserByID (ctx , userID )
702
+ user , err := store .GetUserByID (ctx , userID )
723
703
if err != nil {
724
704
return false , xerrors .New ("user does not exist" )
725
705
}
0 commit comments