diff --git a/internal/api/deployment.go b/internal/api/deployment.go index c88df92..e44aba0 100644 --- a/internal/api/deployment.go +++ b/internal/api/deployment.go @@ -699,13 +699,16 @@ func (s *Server) cancelDeploymentRun(c *gin.Context) { return } completedAt := time.Now() - run.Status = models.DeploymentRunStatusCancelled - run.CompletedAt = &completedAt - if err := s.dbStore.UpdateDeploymentRun(run); err != nil { + if err := s.dbStore.CancelDeploymentRun(run.ID, completedAt); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, gin.H{"data": deploymentRunHistoryView(run)}) + cancelledRun, err := s.dbStore.GetDeploymentRun(run.ID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": deploymentRunHistoryView(cancelledRun)}) } func (s *Server) importDeploymentGeneratedNode(c *gin.Context) { diff --git a/internal/api/deployment_test.go b/internal/api/deployment_test.go index 1a080c8..2921631 100644 --- a/internal/api/deployment_test.go +++ b/internal/api/deployment_test.go @@ -23,6 +23,7 @@ import ( "slices" "strconv" "strings" + "sync" "sync/atomic" "testing" "time" @@ -1746,7 +1747,8 @@ SBM_RESULT {"status":"failed","message":"curl or wget is required"} func TestCancelRunningDeploymentRunStopsBackgroundExecution(t *testing.T) { store := newDeploymentTestStore(t) taskManager := service.NewTaskManager(store) - executor := &fakeDeploymentScriptExecutor{waitForCancel: true} + executorStarted := make(chan struct{}) + executor := &fakeDeploymentScriptExecutor{waitForCancel: true, started: executorStarted} server := &Server{ dbStore: store, taskManager: taskManager, @@ -1776,6 +1778,11 @@ func TestCancelRunningDeploymentRunStopsBackgroundExecution(t *testing.T) { if err := json.Unmarshal(createRecorder.Body.Bytes(), &createResp); err != nil { t.Fatalf("decode create response: %v", err) } + select { + case <-executorStarted: + case <-time.After(2 * time.Second): + t.Fatal("deployment executor did not start") + } cancelRecorder := httptest.NewRecorder() cancelRequest := httptest.NewRequest(http.MethodPost, "/api/deployments/runs/"+createResp.Data.ID+"/cancel", nil) @@ -3806,6 +3813,8 @@ type fakeDeploymentScriptExecutor struct { envs []map[string]string waitForCancel bool waitForCancelCall int + started chan struct{} + startedOnce sync.Once } type fakeDeploymentReachabilityChecker struct { @@ -3857,6 +3866,9 @@ func (f *fakeDeploymentScriptExecutor) Execute(ctx context.Context, req deployme f.env[key] = value } f.envs = append(f.envs, f.env) + if f.started != nil { + f.startedOnce.Do(func() { close(f.started) }) + } if f.waitForCancel && (f.waitForCancelCall == 0 || f.waitForCancelCall == f.calls) { <-ctx.Done() return f.output, ctx.Err() diff --git a/internal/database/store.go b/internal/database/store.go index 37f19ab..7a0af42 100644 --- a/internal/database/store.go +++ b/internal/database/store.go @@ -158,6 +158,15 @@ func (s *Store) UpdateDeploymentRun(run *models.DeploymentRun) error { return s.db.Save(run).Error } +func (s *Store) CancelDeploymentRun(id string, completedAt time.Time) error { + return s.db.Model(&models.DeploymentRun{}). + Where("id = ?", id). + Updates(map[string]interface{}{ + "status": models.DeploymentRunStatusCancelled, + "completed_at": completedAt, + }).Error +} + func (s *Store) GetDeploymentRun(id string) (*models.DeploymentRun, error) { var run models.DeploymentRun if err := s.db.First(&run, "id = ?", id).Error; err != nil {