diff --git a/.golangci.yml b/.golangci.yml index 1fb9e8879..5986dec2e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -35,4 +35,4 @@ issues: - dogsled linters-settings: interfacebloat: - max: 6 + max: 7 diff --git a/server/neptune/workflows/activities/github/revision_url_markdown.go b/server/neptune/workflows/activities/github/revision_url_markdown.go index b64bdffb8..4d7a0e487 100644 --- a/server/neptune/workflows/activities/github/revision_url_markdown.go +++ b/server/neptune/workflows/activities/github/revision_url_markdown.go @@ -6,3 +6,8 @@ func BuildRevisionURLMarkdown(repoFullName string, revision string) string { // uses Markdown formatting to generate the link on GH return fmt.Sprintf("[%s](https://github.com/%s/commit/%s)", revision, repoFullName, revision) } + +func BuildRunURLMarkdown(repoFullName string, revision string, runId int64) string { + // uses Markdown formatting to generate the link on GH + return fmt.Sprintf("[%s](https://github.com/%s/runs/%d)", revision, repoFullName, runId) +} diff --git a/server/neptune/workflows/internal/deploy/lock/lock.go b/server/neptune/workflows/internal/deploy/lock/lock.go new file mode 100644 index 000000000..ec40cb0ff --- /dev/null +++ b/server/neptune/workflows/internal/deploy/lock/lock.go @@ -0,0 +1,15 @@ +package lock + +type LockStatus int + +type LockState struct { + Revision string + Status LockStatus +} + +const ( + UnlockedStatus LockStatus = iota + LockedStatus + + QueueDepthStat = "queue.depth" +) diff --git a/server/neptune/workflows/internal/deploy/revision/notifier/slack.go b/server/neptune/workflows/internal/deploy/revision/notifier/slack.go index b4cfc57a6..6c199295b 100644 --- a/server/neptune/workflows/internal/deploy/revision/notifier/slack.go +++ b/server/neptune/workflows/internal/deploy/revision/notifier/slack.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/runatlantis/atlantis/server/neptune/workflows/activities" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision/queue" "github.com/slack-go/slack" "go.temporal.io/sdk/workflow" @@ -24,7 +25,7 @@ type Slack struct { func (s *Slack) Notify(ctx workflow.Context) error { state := s.DeployQueue.GetLockState() - if state.Status == queue.UnlockedStatus { + if state.Status == lock.UnlockedStatus { return nil } diff --git a/server/neptune/workflows/internal/deploy/revision/notifier/slack_test.go b/server/neptune/workflows/internal/deploy/revision/notifier/slack_test.go index 14da6d1a6..b95266e92 100644 --- a/server/neptune/workflows/internal/deploy/revision/notifier/slack_test.go +++ b/server/neptune/workflows/internal/deploy/revision/notifier/slack_test.go @@ -8,6 +8,7 @@ import ( "github.com/runatlantis/atlantis/server/neptune/workflows/activities" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" terraformActivities "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision/notifier" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision/queue" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/terraform" @@ -20,7 +21,7 @@ import ( ) type request struct { - LockState queue.LockState + LockState lock.LockState InitialItems []terraform.DeploymentInfo } @@ -51,7 +52,7 @@ func testWorkflow(ctx workflow.Context, request request) error { func TestNotifier(t *testing.T) { t.Run("empty queue", func(t *testing.T) { - state := queue.LockState{Status: queue.UnlockedStatus} + state := lock.LockState{Status: lock.UnlockedStatus} ts := testsuite.WorkflowTestSuite{} env := ts.NewTestWorkflowEnvironment() @@ -68,7 +69,7 @@ func TestNotifier(t *testing.T) { }) t.Run("locked state", func(t *testing.T) { - state := queue.LockState{Status: queue.LockedStatus} + state := lock.LockState{Status: lock.LockedStatus} ts := testsuite.WorkflowTestSuite{} env := ts.NewTestWorkflowEnvironment() @@ -85,7 +86,7 @@ func TestNotifier(t *testing.T) { }) t.Run("no slack config", func(t *testing.T) { - state := queue.LockState{Status: queue.LockedStatus} + state := lock.LockState{Status: lock.LockedStatus} ts := testsuite.WorkflowTestSuite{} env := ts.NewTestWorkflowEnvironment() @@ -121,7 +122,7 @@ func TestNotifier(t *testing.T) { }) t.Run("activity called", func(t *testing.T) { - state := queue.LockState{Status: queue.LockedStatus} + state := lock.LockState{Status: lock.LockedStatus} ts := testsuite.WorkflowTestSuite{} env := ts.NewTestWorkflowEnvironment() diff --git a/server/neptune/workflows/internal/deploy/revision/queue/queue.go b/server/neptune/workflows/internal/deploy/revision/queue/queue.go index 306ed6b46..051cecce2 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/queue.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/queue.go @@ -3,34 +3,23 @@ package queue import ( "container/list" "fmt" + "strings" + "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" activity "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/terraform" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/metrics" "go.temporal.io/sdk/workflow" ) -type LockStatus int - -type LockState struct { - Revision string - Status LockStatus -} - -const ( - UnlockedStatus LockStatus = iota - LockedStatus - - QueueDepthStat = "queue.depth" -) - type Deploy struct { queue *priority lockStatusCallback func(workflow.Context, *Deploy) scope metrics.Scope // mutable: default is unlocked - lock LockState + lock lock.LockState } func NewQueue(callback func(workflow.Context, *Deploy), scope metrics.Scope) *Deploy { @@ -41,12 +30,12 @@ func NewQueue(callback func(workflow.Context, *Deploy), scope metrics.Scope) *De } } -func (q *Deploy) GetLockState() LockState { +func (q *Deploy) GetLockState() lock.LockState { return q.lock } -func (q *Deploy) SetLockForMergedItems(ctx workflow.Context, state LockState) { - if state.Status == LockedStatus { +func (q *Deploy) SetLockForMergedItems(ctx workflow.Context, state lock.LockState) { + if state.Status == lock.LockedStatus { q.scope.Counter("locked").Inc(1) } else { q.scope.Counter("unlocked").Inc(1) @@ -56,11 +45,11 @@ func (q *Deploy) SetLockForMergedItems(ctx workflow.Context, state LockState) { } func (q *Deploy) CanPop() bool { - return q.queue.HasItemsOfPriority(High) || (q.lock.Status == UnlockedStatus && !q.queue.IsEmpty()) + return q.queue.HasItemsOfPriority(High) || (q.lock.Status == lock.UnlockedStatus && !q.queue.IsEmpty()) } func (q *Deploy) Pop() (terraform.DeploymentInfo, error) { - defer q.scope.Gauge(QueueDepthStat).Update(float64(q.queue.Size())) + defer q.scope.Gauge(lock.QueueDepthStat).Update(float64(q.queue.Size())) return q.queue.Pop() } @@ -77,7 +66,7 @@ func (q *Deploy) IsEmpty() bool { } func (q *Deploy) Push(msg terraform.DeploymentInfo) { - defer q.scope.Gauge(QueueDepthStat).Update(float64(q.queue.Size())) + defer q.scope.Gauge(lock.QueueDepthStat).Update(float64(q.queue.Size())) if msg.Root.TriggerInfo.Type == activity.ManualTrigger { q.queue.Push(msg, High) return @@ -85,6 +74,18 @@ func (q *Deploy) Push(msg terraform.DeploymentInfo) { q.queue.Push(msg, Low) } +func (q *Deploy) GetQueuedRevisionsSummary() string { + var revisions []string + if q.IsEmpty() { + return "No other runs ahead in queue." + } + for _, deploy := range q.Scan() { + runLink := github.BuildRunURLMarkdown(deploy.Repo.GetFullName(), deploy.Commit.Revision, deploy.CheckRunID) + revisions = append(revisions, runLink) + } + return fmt.Sprintf("Runs in queue: %s", strings.Join(revisions, ", ")) +} + // priority is a simple 2 priority queue implementation // priority is determined before an item enters a queue and does not change type priority struct { diff --git a/server/neptune/workflows/internal/deploy/revision/queue/queue_test.go b/server/neptune/workflows/internal/deploy/revision/queue/queue_test.go index d54178921..9cbac7bf9 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/queue_test.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/queue_test.go @@ -5,6 +5,7 @@ import ( "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" activity "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision/queue" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/terraform" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/metrics" @@ -38,8 +39,8 @@ func TestQueue(t *testing.T) { q := queue.NewQueue(func(ctx workflow.Context, d *queue.Deploy) { called = true }, metrics.NewNullableScope()) - q.SetLockForMergedItems(test.Background(), queue.LockState{ - Status: queue.LockedStatus, + q.SetLockForMergedItems(test.Background(), lock.LockState{ + Status: lock.LockedStatus, }) assert.True(t, called) @@ -52,8 +53,8 @@ func TestQueue(t *testing.T) { t.Run("can pop empty queue locked", func(t *testing.T) { q := queue.NewQueue(noopCallback, metrics.NewNullableScope()) - q.SetLockForMergedItems(test.Background(), queue.LockState{ - Status: queue.LockedStatus, + q.SetLockForMergedItems(test.Background(), lock.LockState{ + Status: lock.LockedStatus, }) assert.Equal(t, false, q.CanPop()) }) @@ -61,8 +62,8 @@ func TestQueue(t *testing.T) { q := queue.NewQueue(noopCallback, metrics.NewNullableScope()) msg1 := wrap("1", activity.ManualTrigger) q.Push(msg1) - q.SetLockForMergedItems(test.Background(), queue.LockState{ - Status: queue.LockedStatus, + q.SetLockForMergedItems(test.Background(), lock.LockState{ + Status: lock.LockedStatus, }) assert.Equal(t, true, q.CanPop()) }) @@ -76,8 +77,8 @@ func TestQueue(t *testing.T) { q := queue.NewQueue(noopCallback, metrics.NewNullableScope()) msg1 := wrap("1", activity.MergeTrigger) q.Push(msg1) - q.SetLockForMergedItems(test.Background(), queue.LockState{ - Status: queue.LockedStatus, + q.SetLockForMergedItems(test.Background(), lock.LockState{ + Status: lock.LockedStatus, }) assert.Equal(t, false, q.CanPop()) }) diff --git a/server/neptune/workflows/internal/deploy/revision/queue/updater.go b/server/neptune/workflows/internal/deploy/revision/queue/updater.go index 098c66d5c..dfd42bd30 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/updater.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/updater.go @@ -2,10 +2,12 @@ package queue import ( "fmt" + key "github.com/runatlantis/atlantis/server/neptune/context" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/notifier" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "go.temporal.io/sdk/workflow" ) @@ -19,17 +21,18 @@ type LockStateUpdater struct { } func (u *LockStateUpdater) UpdateQueuedRevisions(ctx workflow.Context, queue *Deploy, repoFullName string) { - lock := queue.GetLockState() + queueLock := queue.GetLockState() infos := queue.GetOrderedMergedItems() var actions []github.CheckRunAction var summary string + var revisionsSummary string = queue.GetQueuedRevisionsSummary() state := github.CheckRunQueued - if lock.Status == LockedStatus { + if queueLock.Status == lock.LockedStatus { actions = append(actions, github.CreateUnlockAction()) state = github.CheckRunActionRequired - revisionLink := github.BuildRevisionURLMarkdown(repoFullName, lock.Revision) - summary = fmt.Sprintf("This deploy is locked from a manual deployment for revision %s. Unlock to proceed.", revisionLink) + revisionLink := github.BuildRevisionURLMarkdown(repoFullName, queueLock.Revision) + summary = fmt.Sprintf("This deploy is locked from a manual deployment for revision %s. Unlock to proceed.\n%s", revisionLink, revisionsSummary) } for _, i := range infos { diff --git a/server/neptune/workflows/internal/deploy/revision/queue/updater_test.go b/server/neptune/workflows/internal/deploy/revision/queue/updater_test.go index dd8ade796..9fd6f85df 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/updater_test.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/updater_test.go @@ -1,14 +1,16 @@ package queue_test import ( - "github.com/runatlantis/atlantis/server/neptune/workflows/internal/notifier" "testing" "time" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/notifier" + "github.com/google/uuid" "github.com/runatlantis/atlantis/server/neptune/workflows/activities" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" tfActivity "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision/queue" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/terraform" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/metrics" @@ -25,9 +27,13 @@ type testCheckRunClient struct { } func (t *testCheckRunClient) CreateOrUpdate(ctx workflow.Context, deploymentID string, request notifier.GithubCheckRunRequest) (int64, error) { - assert.Equal(t.expectedT, t.expectedRequest, request) - assert.Equal(t.expectedT, t.expectedDeploymentID, deploymentID) + switch { + case assert.Equal(t.expectedT, t.expectedRequest, request): + case assert.Equal(t.expectedT, t.expectedDeploymentID, deploymentID): + default: + t.expectedT.FailNow() + } return 1, nil } @@ -121,8 +127,8 @@ func TestLockStateUpdater_locked_new_version(t *testing.T) { env.ExecuteWorkflow(testUpdaterWorkflow, updaterReq{ Queue: []terraform.DeploymentInfo{info}, - Lock: queue.LockState{ - Status: queue.LockedStatus, + Lock: lock.LockState{ + Status: lock.LockedStatus, Revision: "1234", }, ExpectedRequest: notifier.GithubCheckRunRequest{ @@ -147,7 +153,7 @@ func TestLockStateUpdater_locked_new_version(t *testing.T) { type updaterReq struct { Queue []terraform.DeploymentInfo - Lock queue.LockState + Lock lock.LockState ExpectedRequest notifier.GithubCheckRunRequest ExpectedDeploymentID string ExpectedT *testing.T diff --git a/server/neptune/workflows/internal/deploy/revision/queue/worker.go b/server/neptune/workflows/internal/deploy/revision/queue/worker.go index 9fc6c333e..f2acd0287 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/worker.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/worker.go @@ -13,6 +13,7 @@ import ( internalContext "github.com/runatlantis/atlantis/server/neptune/context" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/deployment" tfModel "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/terraform" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/metrics" "github.com/runatlantis/atlantis/server/neptune/workflows/plugins" @@ -24,7 +25,10 @@ type queue interface { IsEmpty() bool CanPop() bool Pop() (terraform.DeploymentInfo, error) - SetLockForMergedItems(ctx workflow.Context, state LockState) + SetLockForMergedItems(ctx workflow.Context, state lock.LockState) + GetOrderedMergedItems() []terraform.DeploymentInfo + GetQueuedRevisionsSummary() string + GetLockState() lock.LockState } type deployer interface { @@ -96,7 +100,7 @@ func NewWorker( }, } - tfWorkflowRunner := terraform.NewWorkflowRunner(tfWorkflow, notifiers, additionalNotifiers...) + tfWorkflowRunner := terraform.NewWorkflowRunner(q, tfWorkflow, githubCheckRunCache, notifiers, additionalNotifiers...) deployer := &Deployer{ Activities: a, TerraformWorkflowRunner: tfWorkflowRunner, @@ -112,8 +116,8 @@ func NewWorker( // we don't persist lock state anywhere so in the case of workflow completion we need to rebuild // the lock state if latestDeployment != nil && latestDeployment.Root.Trigger == string(tfModel.ManualTrigger) { - q.SetLockForMergedItems(ctx, LockState{ - Status: LockedStatus, + q.SetLockForMergedItems(ctx, lock.LockState{ + Status: lock.LockedStatus, Revision: latestDeployment.Revision, }) } @@ -179,8 +183,8 @@ func (w *Worker) Work(ctx workflow.Context) { workflow.GetMetricsHandler(ctx).WithTags(map[string]string{metricNames.SignalNameTag: UnlockSignalName}). Counter(metricNames.SignalReceive). Inc(1) - w.Queue.SetLockForMergedItems(ctx, LockState{ - Status: UnlockedStatus, + w.Queue.SetLockForMergedItems(ctx, lock.LockState{ + Status: lock.UnlockedStatus, }) continue default: diff --git a/server/neptune/workflows/internal/deploy/revision/queue/worker_state_test.go b/server/neptune/workflows/internal/deploy/revision/queue/worker_state_test.go index 26de4050a..55ec9f36c 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/worker_state_test.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/worker_state_test.go @@ -13,6 +13,7 @@ import ( "go.temporal.io/sdk/testsuite" "go.temporal.io/sdk/workflow" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision/queue" internalTerraform "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/terraform" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/metrics" @@ -47,7 +48,7 @@ func testStateWorkflow(ctx workflow.Context, r workerRequest) (workerResponse, e Queue: list.New(), } - q.SetLockForMergedItems(ctx, queue.LockState{Status: r.InitialLockStatus}) + q.SetLockForMergedItems(ctx, lock.LockState{Status: r.InitialLockStatus}) workflow.Go(ctx, func(ctx workflow.Context) { ch := workflow.GetSignalChannel(ctx, "add-to-queue") @@ -109,8 +110,8 @@ func TestWorker_StartsWithEmptyQueue(t *testing.T) { assert.NoError(t, err) assert.True(t, q.QueueIsEmpty) - assert.Equal(t, queue.LockState{ - Status: queue.UnlockedStatus, + assert.Equal(t, lock.LockState{ + Status: lock.UnlockedStatus, }, q.Lock) assert.Equal(t, queue.WaitingWorkerState, q.State) }, 2*time.Second) @@ -145,8 +146,8 @@ func TestWorker_StartsWithEmptyQueue(t *testing.T) { assert.NoError(t, err) assert.True(t, q.QueueIsEmpty) - assert.Equal(t, queue.LockState{ - Status: queue.UnlockedStatus, + assert.Equal(t, lock.LockState{ + Status: lock.UnlockedStatus, }, q.Lock) assert.Equal(t, queue.WorkingWorkerState, q.State) }, 6*time.Second) diff --git a/server/neptune/workflows/internal/deploy/revision/queue/worker_test.go b/server/neptune/workflows/internal/deploy/revision/queue/worker_test.go index 25a927610..8f0362e85 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/worker_test.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/worker_test.go @@ -12,6 +12,7 @@ import ( "github.com/runatlantis/atlantis/server/neptune/workflows/activities/deployment" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision/queue" internalTerraform "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/terraform" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/metrics" @@ -25,7 +26,7 @@ import ( type testQueue struct { Queue *list.List - Lock queue.LockState + Lock lock.LockState } func (q *testQueue) IsEmpty() bool { @@ -47,16 +48,32 @@ func (q *testQueue) Push(msg internalTerraform.DeploymentInfo) { q.Queue.PushBack(msg) } -func (q *testQueue) SetLockForMergedItems(ctx workflow.Context, state queue.LockState) { +func (q *testQueue) GetOrderedMergedItems() []internalTerraform.DeploymentInfo { + var result []internalTerraform.DeploymentInfo + for e := q.Queue.Front(); e != nil; e = e.Next() { + result = append(result, e.Value.(internalTerraform.DeploymentInfo)) + } + return result +} + +func (q *testQueue) SetLockForMergedItems(ctx workflow.Context, state lock.LockState) { q.Lock = state } +func (q *testQueue) GetLockState() lock.LockState { + return q.Lock +} + +func (q *testQueue) GetQueuedRevisionsSummary() string { + return "Revisions in queue" +} + type workerRequest struct { Queue []internalTerraform.DeploymentInfo ExpectedValidationErrors []*queue.ValidationError ExpectedPlanRejectionErrros []*internalTerraform.PlanRejectionError ExpectedTerraformClientErrors []*activities.TerraformClientError - InitialLockStatus queue.LockStatus + InitialLockStatus lock.LockStatus } type workerResponse struct { @@ -68,7 +85,7 @@ type workerResponse struct { type queueAndState struct { QueueIsEmpty bool State queue.WorkerState - Lock queue.LockState + Lock lock.LockState CurrentDeployment queue.CurrentDeployment LatestDeployment *deployment.Info } @@ -113,7 +130,7 @@ func testWorkerWorkflow(ctx workflow.Context, r workerRequest) (workerResponse, Queue: list.New(), } - q.SetLockForMergedItems(ctx, queue.LockState{Status: r.InitialLockStatus}) + q.SetLockForMergedItems(ctx, lock.LockState{Status: r.InitialLockStatus}) var infos []*deployment.Info for _, s := range r.Queue { @@ -179,8 +196,8 @@ func TestWorker_ReceivesUnlockSignal(t *testing.T) { assert.NoError(t, err) assert.True(t, q.QueueIsEmpty) - assert.Equal(t, queue.LockState{ - Status: queue.UnlockedStatus, + assert.Equal(t, lock.LockState{ + Status: lock.UnlockedStatus, }, q.Lock) assert.Equal(t, queue.WaitingWorkerState, q.State) @@ -189,7 +206,7 @@ func TestWorker_ReceivesUnlockSignal(t *testing.T) { env.ExecuteWorkflow(testWorkerWorkflow, workerRequest{ // start locked and ensure we can unlock it. - InitialLockStatus: queue.LockedStatus, + InitialLockStatus: lock.LockedStatus, }) env.AssertExpectations(t) @@ -388,7 +405,7 @@ func TestNewWorker(t *testing.T) { } type res struct { - Lock queue.LockState + Lock lock.LockState } testWorkflow := func(ctx workflow.Context) (res, error) { @@ -432,9 +449,9 @@ func TestNewWorker(t *testing.T) { err := env.GetWorkflowResult(&r) assert.NoError(t, err) - assert.Equal(t, queue.LockState{ + assert.Equal(t, lock.LockState{ Revision: "1234", - Status: queue.LockedStatus, + Status: lock.LockedStatus, }, r.Lock) }) @@ -463,7 +480,7 @@ func TestNewWorker(t *testing.T) { err := env.GetWorkflowResult(&r) assert.NoError(t, err) - assert.Equal(t, queue.LockState{}, r.Lock) + assert.Equal(t, lock.LockState{}, r.Lock) }) t.Run("first deploy", func(t *testing.T) { @@ -485,7 +502,7 @@ func TestNewWorker(t *testing.T) { err := env.GetWorkflowResult(&r) assert.NoError(t, err) - assert.Equal(t, queue.LockState{}, r.Lock) + assert.Equal(t, lock.LockState{}, r.Lock) }) } diff --git a/server/neptune/workflows/internal/deploy/revision/revision.go b/server/neptune/workflows/internal/deploy/revision/revision.go index 02f0d1424..9d2b6a924 100644 --- a/server/neptune/workflows/internal/deploy/revision/revision.go +++ b/server/neptune/workflows/internal/deploy/revision/revision.go @@ -13,6 +13,7 @@ import ( "github.com/runatlantis/atlantis/server/neptune/workflows/activities" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" activity "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/request" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/request/converter" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision/queue" @@ -43,9 +44,10 @@ type NewRevisionRequest struct { type Queue interface { Push(terraform.DeploymentInfo) - GetLockState() queue.LockState - SetLockForMergedItems(ctx workflow.Context, state queue.LockState) + GetLockState() lock.LockState + SetLockForMergedItems(ctx workflow.Context, state lock.LockState) Scan() []terraform.DeploymentInfo + GetQueuedRevisionsSummary() string } type DeploymentStore interface { @@ -114,8 +116,8 @@ func (n *Receiver) Receive(c workflow.ReceiveChannel, more bool) { // lock the queue on a manual deployment if root.TriggerInfo.Type == activity.ManualTrigger { // Lock the queue on a manual deployment - n.queue.SetLockForMergedItems(ctx, queue.LockState{ - Status: queue.LockedStatus, + n.queue.SetLockForMergedItems(ctx, lock.LockState{ + Status: lock.LockedStatus, Revision: request.Revision, }) } @@ -134,16 +136,17 @@ func (n *Receiver) Receive(c workflow.ReceiveChannel, more bool) { } func (n *Receiver) createCheckRun(ctx workflow.Context, id, revision string, root activity.Root, repo github.Repo) int64 { - lock := n.queue.GetLockState() + queueLock := n.queue.GetLockState() var actions []github.CheckRunAction - summary := "This deploy is queued and will be processed as soon as possible." + var revisionsSummary string = n.queue.GetQueuedRevisionsSummary() + summary := "This deploy is queued and will be processed as soon as possible.\n" + revisionsSummary state := github.CheckRunQueued - if lock.Status == queue.LockedStatus && (root.TriggerInfo.Type == activity.MergeTrigger) { + if queueLock.Status == lock.LockedStatus && (root.TriggerInfo.Type == activity.MergeTrigger) { actions = append(actions, github.CreateUnlockAction()) state = github.CheckRunActionRequired - revisionLink := github.BuildRevisionURLMarkdown(repo.GetFullName(), lock.Revision) - summary = fmt.Sprintf("This deploy is locked from a manual deployment for revision %s. Unlock to proceed.", revisionLink) + revisionLink := github.BuildRevisionURLMarkdown(repo.GetFullName(), queueLock.Revision) + summary = fmt.Sprintf("This deploy is locked from a manual deployment for revision %s. Unlock to proceed.\n%s", revisionLink, revisionsSummary) } cid, err := n.checkRunClient.CreateOrUpdate(ctx, id, notifier.GithubCheckRunRequest{ diff --git a/server/neptune/workflows/internal/deploy/revision/revision_test.go b/server/neptune/workflows/internal/deploy/revision/revision_test.go index d80d7073a..e43215176 100644 --- a/server/neptune/workflows/internal/deploy/revision/revision_test.go +++ b/server/neptune/workflows/internal/deploy/revision/revision_test.go @@ -1,13 +1,17 @@ package revision_test import ( - "github.com/runatlantis/atlantis/server/neptune/workflows/internal/notifier" + "fmt" + "strings" "testing" "time" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/notifier" + "github.com/google/uuid" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/request" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/revision/queue" @@ -23,14 +27,16 @@ type testCheckRunClient struct { } func (t *testCheckRunClient) CreateOrUpdate(ctx workflow.Context, deploymentID string, request notifier.GithubCheckRunRequest) (int64, error) { - assert.Equal(t.expectedT, t.expectedRequest, request) - + ok := assert.Equal(t.expectedT, t.expectedRequest, request) + if !ok { + t.expectedT.FailNow() + } return 1, nil } type testQueue struct { Queue []terraformWorkflow.DeploymentInfo - Lock queue.LockState + Lock lock.LockState } func (q *testQueue) Scan() []terraformWorkflow.DeploymentInfo { @@ -41,14 +47,29 @@ func (q *testQueue) Push(msg terraformWorkflow.DeploymentInfo) { q.Queue = append(q.Queue, msg) } -func (q *testQueue) GetLockState() queue.LockState { +func (q *testQueue) GetLockState() lock.LockState { return q.Lock } -func (q *testQueue) SetLockForMergedItems(ctx workflow.Context, state queue.LockState) { +func (q *testQueue) SetLockForMergedItems(ctx workflow.Context, state lock.LockState) { q.Lock = state } +func (q *testQueue) IsEmpty() bool { + return len(q.Queue) == 0 +} + +func (q *testQueue) GetQueuedRevisionsSummary() string { + var revisions []string + if q.IsEmpty() { + return "No other revisions ahead In queue." + } + for _, deploy := range q.Scan() { + revisions = append(revisions, deploy.Commit.Revision) + } + return fmt.Sprintf("Revisions in queue: %s", strings.Join(revisions, ", ")) +} + type testWorker struct { Current queue.CurrentDeployment } @@ -59,7 +80,7 @@ func (t testWorker) GetCurrentDeploymentState() queue.CurrentDeployment { type req struct { ID uuid.UUID - Lock queue.LockState + Lock lock.LockState Current queue.CurrentDeployment InitialElements []terraformWorkflow.DeploymentInfo ExpectedRequest notifier.GithubCheckRunRequest @@ -68,7 +89,7 @@ type req struct { type response struct { Queue []terraformWorkflow.DeploymentInfo - Lock queue.LockState + Lock lock.LockState Timeout bool } @@ -137,10 +158,11 @@ func TestEnqueue(t *testing.T) { env.ExecuteWorkflow(testWorkflow, req{ ID: id, ExpectedRequest: notifier.GithubCheckRunRequest{ - Title: "atlantis/deploy: root", - Sha: rev, - Repo: github.Repo{Name: "nish"}, - State: github.CheckRunQueued, + Title: "atlantis/deploy: root", + Sha: rev, + Repo: github.Repo{Name: "nish"}, + State: github.CheckRunQueued, + Summary: "This deploy is queued and will be processed as soon as possible.\nNo other revisions ahead In queue.", }, ExpectedT: t, }) @@ -165,8 +187,8 @@ func TestEnqueue(t *testing.T) { Repo: github.Repo{Name: "nish"}, }, }, resp.Queue) - assert.Equal(t, queue.LockState{ - Status: queue.UnlockedStatus, + assert.Equal(t, lock.LockState{ + Status: lock.UnlockedStatus, }, resp.Lock) assert.False(t, resp.Timeout) } @@ -197,10 +219,11 @@ func TestEnqueue_ManualTrigger(t *testing.T) { env.ExecuteWorkflow(testWorkflow, req{ ID: id, ExpectedRequest: notifier.GithubCheckRunRequest{ - Title: "atlantis/deploy: root", - Sha: rev, - Repo: github.Repo{Name: "nish"}, - State: github.CheckRunQueued, + Title: "atlantis/deploy: root", + Sha: rev, + Repo: github.Repo{Name: "nish"}, + State: github.CheckRunQueued, + Summary: "This deploy is queued and will be processed as soon as possible.\nNo other revisions ahead In queue.", }, ExpectedT: t, }) @@ -225,8 +248,8 @@ func TestEnqueue_ManualTrigger(t *testing.T) { Repo: github.Repo{Name: "nish"}, }, }, resp.Queue) - assert.Equal(t, queue.LockState{ - Status: queue.LockedStatus, + assert.Equal(t, lock.LockState{ + Status: lock.LockedStatus, Revision: "1234", }, resp.Lock) assert.False(t, resp.Timeout) @@ -257,16 +280,17 @@ func TestEnqueue_ManualTrigger_QueueAlreadyLocked(t *testing.T) { env.ExecuteWorkflow(testWorkflow, req{ ID: id, - Lock: queue.LockState{ + Lock: lock.LockState{ // ensure that the lock gets updated - Status: queue.LockedStatus, + Status: lock.LockedStatus, Revision: "123334444555", }, ExpectedRequest: notifier.GithubCheckRunRequest{ - Title: "atlantis/deploy: root", - Sha: rev, - Repo: github.Repo{Name: "nish"}, - State: github.CheckRunQueued, + Title: "atlantis/deploy: root", + Sha: rev, + Repo: github.Repo{Name: "nish"}, + State: github.CheckRunQueued, + Summary: "This deploy is queued and will be processed as soon as possible.\nNo other revisions ahead In queue.", }, ExpectedT: t, }) @@ -291,8 +315,8 @@ func TestEnqueue_ManualTrigger_QueueAlreadyLocked(t *testing.T) { Repo: github.Repo{Name: "nish"}, }, }, resp.Queue) - assert.Equal(t, queue.LockState{ - Status: queue.LockedStatus, + assert.Equal(t, lock.LockState{ + Status: lock.LockedStatus, Revision: "1234", }, resp.Lock) assert.False(t, resp.Timeout) @@ -321,18 +345,32 @@ func TestEnqueue_MergeTrigger_QueueAlreadyLocked(t *testing.T) { id := uuid.Must(uuid.NewUUID()) + deploymentInfo := terraformWorkflow.DeploymentInfo{ + Commit: github.Commit{ + Revision: "123334444555", + Branch: "locking-branch", + }, + CheckRunID: 0, + Root: terraform.Root{Name: "root", TriggerInfo: terraform.TriggerInfo{ + Type: terraform.MergeTrigger, + }, Trigger: terraform.MergeTrigger}, + ID: id, + Repo: github.Repo{Name: "nish"}, + } + env.ExecuteWorkflow(testWorkflow, req{ - ID: id, - Lock: queue.LockState{ + ID: id, + InitialElements: []terraformWorkflow.DeploymentInfo{deploymentInfo}, + Lock: lock.LockState{ // ensure that the lock gets updated - Status: queue.LockedStatus, + Status: lock.LockedStatus, Revision: "123334444555", }, ExpectedRequest: notifier.GithubCheckRunRequest{ Title: "atlantis/deploy: root", Sha: rev, Repo: github.Repo{Name: "nish"}, - Summary: "This deploy is locked from a manual deployment for revision [123334444555](https://github.com//nish/commit/123334444555). Unlock to proceed.", + Summary: "This deploy is locked from a manual deployment for revision [123334444555](https://github.com//nish/commit/123334444555). Unlock to proceed.\nRevisions in queue: 123334444555", Actions: []github.CheckRunAction{github.CreateUnlockAction()}, State: github.CheckRunActionRequired, }, @@ -346,6 +384,7 @@ func TestEnqueue_MergeTrigger_QueueAlreadyLocked(t *testing.T) { assert.NoError(t, err) assert.Equal(t, []terraformWorkflow.DeploymentInfo{ + deploymentInfo, { Commit: github.Commit{ Revision: rev, @@ -359,8 +398,8 @@ func TestEnqueue_MergeTrigger_QueueAlreadyLocked(t *testing.T) { Repo: github.Repo{Name: "nish"}, }, }, resp.Queue) - assert.Equal(t, queue.LockState{ - Status: queue.LockedStatus, + assert.Equal(t, lock.LockState{ + Status: lock.LockedStatus, Revision: "123334444555", }, resp.Lock) assert.False(t, resp.Timeout) diff --git a/server/neptune/workflows/internal/deploy/terraform/runner.go b/server/neptune/workflows/internal/deploy/terraform/runner.go index fcc556b4e..e07619f93 100644 --- a/server/neptune/workflows/internal/deploy/terraform/runner.go +++ b/server/neptune/workflows/internal/deploy/terraform/runner.go @@ -3,6 +3,7 @@ package terraform import ( "github.com/pkg/errors" terraformActivities "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/metrics" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/terraform" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/terraform/state" @@ -33,10 +34,18 @@ type stateReceiver interface { Receive(ctx workflow.Context, c workflow.ReceiveChannel, deploymentInfo DeploymentInfo) } -func NewWorkflowRunner(w Workflow, internalNotifiers []WorkflowNotifier, additionalNotifiers ...plugins.TerraformWorkflowNotifier) *WorkflowRunner { +type deployQueue interface { + GetOrderedMergedItems() []DeploymentInfo + GetQueuedRevisionsSummary() string + GetLockState() lock.LockState +} + +func NewWorkflowRunner(queue deployQueue, w Workflow, githubCheckRunCache CheckRunClient, internalNotifiers []WorkflowNotifier, additionalNotifiers ...plugins.TerraformWorkflowNotifier) *WorkflowRunner { return &WorkflowRunner{ Workflow: w, StateReceiver: &StateReceiver{ + Queue: queue, + CheckRunCache: githubCheckRunCache, InternalNotifiers: internalNotifiers, AdditionalNotifiers: additionalNotifiers, }, diff --git a/server/neptune/workflows/internal/deploy/terraform/state.go b/server/neptune/workflows/internal/deploy/terraform/state.go index fe94b07b0..8d806199a 100644 --- a/server/neptune/workflows/internal/deploy/terraform/state.go +++ b/server/neptune/workflows/internal/deploy/terraform/state.go @@ -1,10 +1,13 @@ package terraform import ( + "fmt" + "github.com/pkg/errors" "github.com/runatlantis/atlantis/server/metrics" + "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/notifier" - "github.com/runatlantis/atlantis/server/neptune/workflows/internal/terraform/state" "github.com/runatlantis/atlantis/server/neptune/workflows/plugins" "go.temporal.io/sdk/workflow" @@ -14,15 +17,23 @@ type WorkflowNotifier interface { Notify(workflow.Context, notifier.Info, *state.Workflow) error } +type CheckRunClient interface { + CreateOrUpdate(ctx workflow.Context, deploymentID string, request notifier.GithubCheckRunRequest) (int64, error) +} + type StateReceiver struct { // We have separate classes of notifiers since we can be more flexible with our internal ones in terms of the data model // What we support externally should be well thought out so for now this is kept to a minimum. + Queue deployQueue + CheckRunCache CheckRunClient InternalNotifiers []WorkflowNotifier AdditionalNotifiers []plugins.TerraformWorkflowNotifier } func (n *StateReceiver) Receive(ctx workflow.Context, c workflow.ReceiveChannel, deploymentInfo DeploymentInfo) { + // deploymentInfo is the current deployment being processed in TerraformWorkflow. Receive is triggered whenever the TerraformWorkflow has a state change. + var workflowState *state.Workflow c.Receive(ctx, &workflowState) @@ -38,11 +49,62 @@ func (n *StateReceiver) Receive(ctx workflow.Context, c workflow.ReceiveChannel, workflow.GetLogger(ctx).Error(errors.Wrap(err, "notifying workflow state change").Error()) } } - + // Updates github check run with Terraform statuses for the current running deployment + // TODO: do not notify github if workflowState.Result.Status == InProgressWorkflowStatus && workflowState.Result.Reason == UnknownCompletionReason for _, notifier := range n.InternalNotifiers { if err := notifier.Notify(ctx, deploymentInfo.ToInternalInfo(), workflowState); err != nil { workflow.GetMetricsHandler(ctx).Counter("notifier_failure").Inc(1) workflow.GetLogger(ctx).Error(errors.Wrap(err, "notifying workflow state change").Error()) } } + + // Updates all other deployments waiting in queue when the current deployment is pending a confirm/reject user action. Current deployment is not on the queue at this point since its child TerraformWorkflow was started. + // CheckRunCache.CreateOrUpdate executes an activity and is a nondeterministic operation (i.e. it is not guaranteed to be executed in the same order across different workflow runs). This is why we need to check the workflow version to determine if we should update the check run. + // See https://docs.temporal.io/develop/go/versioning#patching for how to upgrade workflow version. + v := workflow.GetVersion(ctx, "SurfaceQueueInCheckRuns", workflow.DefaultVersion, 1) + if v == workflow.DefaultVersion { + return + } + if workflowState.Apply != nil && + len(workflowState.Apply.OnWaitingActions.Actions) > 0 { + queuedDeployments := n.Queue.GetOrderedMergedItems() + revisionsSummary := n.Queue.GetQueuedRevisionsSummary() + runState := github.CheckRunQueued + var actions []github.CheckRunAction + var summary string + + if workflowState.Apply.Status == state.WaitingJobStatus { + runLink := github.BuildRunURLMarkdown(deploymentInfo.Repo.GetFullName(), deploymentInfo.Commit.Revision, deploymentInfo.CheckRunID) + summary = fmt.Sprintf("This deploy is queued pending action on run for revision %s.\n%s", runLink, revisionsSummary) + } else if workflowState.Apply.Status == state.RejectedJobStatus || workflowState.Apply.Status == state.InProgressJobStatus { + // If the current deployment is Rejected or In Progress status, we need to restore the queued check runs to reflect that the queued deployments are not blocked. + // If the queue is currently locked we need to provide the unlock action. + queueLock := n.Queue.GetLockState() + if queueLock.Status == lock.LockedStatus { + actions = append(actions, github.CreateUnlockAction()) + runState = github.CheckRunActionRequired + revisionLink := github.BuildRevisionURLMarkdown(deploymentInfo.Repo.GetFullName(), queueLock.Revision) + summary = fmt.Sprintf("This deploy is locked from a manual deployment for revision %s. Unlock to proceed.\n%s", revisionLink, revisionsSummary) + } else { + summary = "This deploy is queued and will be processed as soon as possible.\n" + revisionsSummary + } + } + for _, i := range queuedDeployments { + request := notifier.GithubCheckRunRequest{ + Title: notifier.BuildDeployCheckRunTitle(i.Root.Name), + Sha: i.Commit.Revision, + State: runState, + Repo: i.Repo, + Summary: summary, + Actions: actions, + } + + workflow.GetLogger(ctx).Debug(fmt.Sprintf("Updating action pending summary for deployment id: %s", i.ID.String())) + _, err := n.CheckRunCache.CreateOrUpdate(ctx, i.ID.String(), request) + + if err != nil { + workflow.GetLogger(ctx).Debug(fmt.Sprintf("updating check run for revision %s", i.Commit.Revision), err) + } + } + } } diff --git a/server/neptune/workflows/internal/deploy/terraform/state_test.go b/server/neptune/workflows/internal/deploy/terraform/state_test.go index edd3cfd8f..4f4041966 100644 --- a/server/neptune/workflows/internal/deploy/terraform/state_test.go +++ b/server/neptune/workflows/internal/deploy/terraform/state_test.go @@ -1,14 +1,18 @@ package terraform_test import ( - "github.com/runatlantis/atlantis/server/neptune/workflows/internal/notifier" + "fmt" "net/url" + "strings" "testing" "time" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/notifier" + "github.com/google/uuid" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/github" "github.com/runatlantis/atlantis/server/neptune/workflows/activities/terraform" + "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/lock" internalTerraform "github.com/runatlantis/atlantis/server/neptune/workflows/internal/deploy/terraform" "github.com/runatlantis/atlantis/server/neptune/workflows/internal/terraform/state" "github.com/runatlantis/atlantis/server/neptune/workflows/plugins" @@ -49,13 +53,46 @@ func (n *testExternalNotifier) Notify(ctx workflow.Context, info plugins.Terrafo return nil } +type testCheckRunClient struct { + called bool +} + +func (t *testCheckRunClient) CreateOrUpdate(ctx workflow.Context, deploymentID string, request notifier.GithubCheckRunRequest) (int64, error) { + t.called = true + return 1, nil +} + +type testQueue struct { + Queue []internalTerraform.DeploymentInfo + Lock lock.LockState +} + +func (q *testQueue) GetLockState() lock.LockState { + return q.Lock +} + +func (q *testQueue) GetQueuedRevisionsSummary() string { + var revisions []string + for _, deploy := range q.Queue { + revisions = append(revisions, deploy.Commit.Revision) + } + return fmt.Sprintf("Revisions in queue: %s", strings.Join(revisions, ", ")) +} + +func (q *testQueue) GetOrderedMergedItems() []internalTerraform.DeploymentInfo { + return q.Queue +} + type stateReceiveRequest struct { + Queue *testQueue + CheckRunCache testCheckRunClient State *state.Workflow DeploymentInfo internalTerraform.DeploymentInfo T *testing.T } type stateReceiveResponse struct { + CheckRunCacheCalled bool NotifierCalled bool ExternalNotifierCalled bool } @@ -79,6 +116,8 @@ func testStateReceiveWorkflow(ctx workflow.Context, r stateReceiveRequest) (stat } receiver := &internalTerraform.StateReceiver{ + Queue: r.Queue, + CheckRunCache: &r.CheckRunCache, InternalNotifiers: []internalTerraform.WorkflowNotifier{ notifier, }, @@ -94,6 +133,7 @@ func testStateReceiveWorkflow(ctx workflow.Context, r stateReceiveRequest) (stat receiver.Receive(ctx, ch, r.DeploymentInfo) return stateReceiveResponse{ + CheckRunCacheCalled: r.CheckRunCache.called, NotifierCalled: notifier.called, ExternalNotifierCalled: externalNotifier.called, }, nil @@ -117,16 +157,49 @@ func TestStateReceive(t *testing.T) { }, } + queue := &testQueue{ + Queue: []internalTerraform.DeploymentInfo{ + { + CheckRunID: 0, + ID: uuid.New(), + Root: terraform.Root{Name: "root"}, + Repo: github.Repo{Name: "hello"}, + Commit: github.Commit{ + Revision: "56789", + }, + }, + }, + } + t.Run("calls notifiers with state", func(t *testing.T) { ts := testsuite.WorkflowTestSuite{} env := ts.NewTestWorkflowEnvironment() env.ExecuteWorkflow(testStateReceiveWorkflow, stateReceiveRequest{ + Queue: queue, + CheckRunCache: testCheckRunClient{}, State: &state.Workflow{ Plan: &state.Job{ Output: jobOutput, Status: state.WaitingJobStatus, }, + Apply: &state.Job{ + Output: jobOutput, + Status: state.WaitingJobStatus, + OnWaitingActions: state.JobActions{ + Actions: []state.JobAction{ + { + ID: state.ConfirmAction, + Info: "Confirm this plan to proceed to apply", + }, + { + ID: state.RejectAction, + Info: "Reject this plan to prevent the apply", + }, + }, + Summary: "some reason", + }, + }, }, DeploymentInfo: internalDeploymentInfo, T: t, @@ -136,6 +209,7 @@ func TestStateReceive(t *testing.T) { var result stateReceiveResponse err = env.GetWorkflowResult(&result) + assert.True(t, result.CheckRunCacheCalled) assert.True(t, result.NotifierCalled) assert.True(t, result.ExternalNotifierCalled) assert.NoError(t, err)