From d31039c1571e26a8e62f990a375af8106ce355b7 Mon Sep 17 00:00:00 2001 From: Tiffany Chiang <14845269+tlin4194@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:00:53 -0500 Subject: [PATCH] Restore unlock action on queued checkruns (#774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we update checkruns for queued commits (after the blocking commit gets confirmed or rejected), we need to check if the queue was previously locked by something else and restore the unlock button for each commit. ### Sequence of events without this change: 1. Queue is locked due to diverged commits on latest deployment vs current plan (i.e. if someone previously force applied). All queued commits should have a unlock button. 2. New commit comes in with a pending confirm/reject action. My other PR makes it so that all queued checkruns are updated to reflect that the queue is waiting on this confirm reject action. Screenshot 2024-11-08 at 3 08 07 PM 4. Confirm/reject commit gets resolved. All queued commit checkruns get updated to the default status, without the unlock button. Currently users would have to click on the commit they want to unlock (first screenshot below), then click on an older checkrun of that commit (linked in blue) to access the unlock button (2nd screenshot below). Screenshot 2024-11-08 at 3 07 08 PM Screenshot 2024-11-08 at 3 07 17 PM ### After this change: 4. (Previous step 4) All queued commit checkruns get updated back to its original status from before the confirm/reject commit was applied, **including** unlock actions if the queue was previously locked. ### Additional notes: * LockState is migrated out of the queue package into its own `lock` package to avoid cyclic imports with terraform module * bumping max interfacebloat golangci error to allow adding GetLockState() function to queue interface ### Testing 1. Open PR 1. Force apply PR 1, and verify that the force apply is successful. 2. Open PR 2. Merge PR 2 to master, and verify that PR 2 deployment failed and is now locked on PR 1's last commit (there should be an unlock button and link to the locking commit). 3. Open PR 3. Force apply PR 3, and verify that there is a Confirm/Reject action. 4. Verify that PR 2's deployment checkrun on master has an updated summary with link to the pending Confirm/Reject run on PR 3. 5. Reject forced apply on PR 3. 6. Verify that PR 2's deployment checkrun updates again, this time back to the original checkrun summary from step 2. If the queue was previously locked (which it was), there should be an unlock button. --- .golangci.yml | 2 +- .../workflows/internal/deploy/lock/lock.go | 15 ++++++++ .../deploy/revision/notifier/slack.go | 3 +- .../deploy/revision/notifier/slack_test.go | 11 +++--- .../internal/deploy/revision/queue/queue.go | 29 +++++---------- .../deploy/revision/queue/queue_test.go | 17 ++++----- .../internal/deploy/revision/queue/updater.go | 7 ++-- .../deploy/revision/queue/updater_test.go | 7 ++-- .../internal/deploy/revision/queue/worker.go | 12 ++++--- .../revision/queue/worker_state_test.go | 11 +++--- .../deploy/revision/queue/worker_test.go | 31 +++++++++------- .../internal/deploy/revision/revision.go | 15 ++++---- .../internal/deploy/revision/revision_test.go | 35 ++++++++++--------- .../internal/deploy/terraform/runner.go | 2 ++ .../internal/deploy/terraform/state.go | 17 +++++++-- .../internal/deploy/terraform/state_test.go | 6 ++++ 16 files changed, 129 insertions(+), 91 deletions(-) create mode 100644 server/neptune/workflows/internal/deploy/lock/lock.go 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/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 7a77a0199..051cecce2 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/queue.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/queue.go @@ -7,32 +7,19 @@ 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/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 { @@ -43,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) @@ -58,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() } @@ -79,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 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 e9fa950c4..dfd42bd30 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/updater.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/updater.go @@ -7,6 +7,7 @@ import ( "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" ) @@ -20,17 +21,17 @@ 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) + 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) } 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 c99cafab7..9fd6f85df 100644 --- a/server/neptune/workflows/internal/deploy/revision/queue/updater_test.go +++ b/server/neptune/workflows/internal/deploy/revision/queue/updater_test.go @@ -10,6 +10,7 @@ import ( "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" @@ -126,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{ @@ -152,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 a4839c332..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,9 +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 { @@ -114,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, }) } @@ -181,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 4363a907b..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 { @@ -55,10 +56,14 @@ func (q *testQueue) GetOrderedMergedItems() []internalTerraform.DeploymentInfo { return result } -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) GetLockState() lock.LockState { + return q.Lock +} + func (q *testQueue) GetQueuedRevisionsSummary() string { return "Revisions in queue" } @@ -68,7 +73,7 @@ type workerRequest struct { ExpectedValidationErrors []*queue.ValidationError ExpectedPlanRejectionErrros []*internalTerraform.PlanRejectionError ExpectedTerraformClientErrors []*activities.TerraformClientError - InitialLockStatus queue.LockStatus + InitialLockStatus lock.LockStatus } type workerResponse struct { @@ -80,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 } @@ -125,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 { @@ -191,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) @@ -201,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) @@ -400,7 +405,7 @@ func TestNewWorker(t *testing.T) { } type res struct { - Lock queue.LockState + Lock lock.LockState } testWorkflow := func(ctx workflow.Context) (res, error) { @@ -444,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) }) @@ -475,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) { @@ -497,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 48de52081..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,8 +44,8 @@ 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 } @@ -115,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, }) } @@ -135,16 +136,16 @@ 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 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) + 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) } diff --git a/server/neptune/workflows/internal/deploy/revision/revision_test.go b/server/neptune/workflows/internal/deploy/revision/revision_test.go index d5a43156e..e43215176 100644 --- a/server/neptune/workflows/internal/deploy/revision/revision_test.go +++ b/server/neptune/workflows/internal/deploy/revision/revision_test.go @@ -11,6 +11,7 @@ import ( "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" @@ -35,7 +36,7 @@ func (t *testCheckRunClient) CreateOrUpdate(ctx workflow.Context, deploymentID s type testQueue struct { Queue []terraformWorkflow.DeploymentInfo - Lock queue.LockState + Lock lock.LockState } func (q *testQueue) Scan() []terraformWorkflow.DeploymentInfo { @@ -46,11 +47,11 @@ 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 } @@ -79,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 @@ -88,7 +89,7 @@ type req struct { type response struct { Queue []terraformWorkflow.DeploymentInfo - Lock queue.LockState + Lock lock.LockState Timeout bool } @@ -186,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) } @@ -247,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) @@ -279,9 +280,9 @@ 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{ @@ -314,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) @@ -360,9 +361,9 @@ func TestEnqueue_MergeTrigger_QueueAlreadyLocked(t *testing.T) { env.ExecuteWorkflow(testWorkflow, req{ ID: id, InitialElements: []terraformWorkflow.DeploymentInfo{deploymentInfo}, - Lock: queue.LockState{ + Lock: lock.LockState{ // ensure that the lock gets updated - Status: queue.LockedStatus, + Status: lock.LockedStatus, Revision: "123334444555", }, ExpectedRequest: notifier.GithubCheckRunRequest{ @@ -397,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 87d5baa75..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" @@ -36,6 +37,7 @@ type stateReceiver interface { type deployQueue interface { GetOrderedMergedItems() []DeploymentInfo GetQueuedRevisionsSummary() string + GetLockState() lock.LockState } func NewWorkflowRunner(queue deployQueue, w Workflow, githubCheckRunCache CheckRunClient, internalNotifiers []WorkflowNotifier, additionalNotifiers ...plugins.TerraformWorkflowNotifier) *WorkflowRunner { diff --git a/server/neptune/workflows/internal/deploy/terraform/state.go b/server/neptune/workflows/internal/deploy/terraform/state.go index 2b621395b..8d806199a 100644 --- a/server/neptune/workflows/internal/deploy/terraform/state.go +++ b/server/neptune/workflows/internal/deploy/terraform/state.go @@ -6,6 +6,7 @@ import ( "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" @@ -68,6 +69,8 @@ func (n *StateReceiver) Receive(ctx workflow.Context, c workflow.ReceiveChannel, 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 { @@ -75,15 +78,25 @@ func (n *StateReceiver) Receive(ctx workflow.Context, c workflow.ReceiveChannel, 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. - summary = "This deploy is queued and will be processed as soon as possible.\n" + revisionsSummary + // 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: github.CheckRunQueued, + 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())) diff --git a/server/neptune/workflows/internal/deploy/terraform/state_test.go b/server/neptune/workflows/internal/deploy/terraform/state_test.go index 27dac5e0a..4f4041966 100644 --- a/server/neptune/workflows/internal/deploy/terraform/state_test.go +++ b/server/neptune/workflows/internal/deploy/terraform/state_test.go @@ -12,6 +12,7 @@ import ( "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" @@ -63,6 +64,11 @@ func (t *testCheckRunClient) CreateOrUpdate(ctx workflow.Context, deploymentID s type testQueue struct { Queue []internalTerraform.DeploymentInfo + Lock lock.LockState +} + +func (q *testQueue) GetLockState() lock.LockState { + return q.Lock } func (q *testQueue) GetQueuedRevisionsSummary() string {