Skip to content

Commit fbb12fe

Browse files
chrojunitrocode
andauthored
feat: set atlantis/apply check to successful if all plans are No Changes (runatlantis#3378)
* mod: rename updateCommitStatus func * feat: add PlannedNoChangesPlanStatus * Add skipApplyNoChanges option to PlanCommandRunner * Add skipApplyNoChanges option to ApplyCommandRunner * Add --skip-apply-no-changes flag * Fix typo Co-authored-by: nitrocode <[email protected]> * Rename --skip-apply-no-changes flag * Refactor updateCommitStatus functions * chore(docs): add detailed use case for the flag * test: add plan_command_runner set apply status * feat: set apply status to successful by default when result is 'No Changes' --------- Co-authored-by: chroju <[email protected]> Co-authored-by: nitrocode <[email protected]>
1 parent d6ba8f5 commit fbb12fe

File tree

7 files changed

+412
-15
lines changed

7 files changed

+412
-15
lines changed

server/events/apply_command_runner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ func (a *ApplyCommandRunner) updateCommitStatus(ctx *command.Context, pullStatus
199199
var numErrored int
200200
status := models.SuccessCommitStatus
201201

202-
numSuccess = pullStatus.StatusCount(models.AppliedPlanStatus)
202+
numSuccess = pullStatus.StatusCount(models.AppliedPlanStatus) + pullStatus.StatusCount(models.PlannedNoChangesPlanStatus)
203203
numErrored = pullStatus.StatusCount(models.ErroredApplyStatus)
204204

205205
if numErrored > 0 {

server/events/command/project_result.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ func (p ProjectResult) PlanStatus() models.ProjectPlanStatus {
5757
return models.ErroredPlanStatus
5858
} else if p.Failure != "" {
5959
return models.ErroredPlanStatus
60+
} else if p.PlanSuccess.NoChanges() {
61+
return models.PlannedNoChangesPlanStatus
6062
}
6163
return models.PlannedPlanStatus
6264
case PolicyCheck, ApprovePolicies:

server/events/command/project_result_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ func TestProjectResult_PlanStatus(t *testing.T) {
7979
},
8080
expStatus: models.PlannedPlanStatus,
8181
},
82+
{
83+
p: command.ProjectResult{
84+
Command: command.Plan,
85+
PlanSuccess: &models.PlanSuccess{
86+
TerraformOutput: "No changes. Infrastructure is up-to-date.",
87+
},
88+
},
89+
expStatus: models.PlannedNoChangesPlanStatus,
90+
},
8291
{
8392
p: command.ProjectResult{
8493
Command: command.Apply,

server/events/command_runner_internal_test.go

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ func TestApplyUpdateCommitStatus(t *testing.T) {
6767
expNumSuccess: 1,
6868
expNumTotal: 3,
6969
},
70+
"apply, one planned no changes": {
71+
cmd: command.Apply,
72+
pullStatus: models.PullStatus{
73+
Projects: []models.ProjectStatus{
74+
{
75+
Status: models.AppliedPlanStatus,
76+
},
77+
{
78+
Status: models.PlannedNoChangesPlanStatus,
79+
},
80+
},
81+
},
82+
expStatus: models.SuccessCommitStatus,
83+
expNumSuccess: 2,
84+
expNumTotal: 2,
85+
},
7086
}
7187

7288
for name, c := range cases {
@@ -86,7 +102,7 @@ func TestApplyUpdateCommitStatus(t *testing.T) {
86102
}
87103
}
88104

89-
func TestPlanUpdateCommitStatus(t *testing.T) {
105+
func TestPlanUpdatePlanCommitStatus(t *testing.T) {
90106
cases := map[string]struct {
91107
cmd command.Name
92108
pullStatus models.PullStatus
@@ -137,7 +153,104 @@ func TestPlanUpdateCommitStatus(t *testing.T) {
137153
cr := &PlanCommandRunner{
138154
commitStatusUpdater: csu,
139155
}
140-
cr.updateCommitStatus(&command.Context{}, c.pullStatus)
156+
cr.updateCommitStatus(&command.Context{}, c.pullStatus, command.Plan)
157+
Equals(t, models.Repo{}, csu.CalledRepo)
158+
Equals(t, models.PullRequest{}, csu.CalledPull)
159+
Equals(t, c.expStatus, csu.CalledStatus)
160+
Equals(t, c.cmd, csu.CalledCommand)
161+
Equals(t, c.expNumSuccess, csu.CalledNumSuccess)
162+
Equals(t, c.expNumTotal, csu.CalledNumTotal)
163+
})
164+
}
165+
}
166+
167+
func TestPlanUpdateApplyCommitStatus(t *testing.T) {
168+
cases := map[string]struct {
169+
cmd command.Name
170+
pullStatus models.PullStatus
171+
expStatus models.CommitStatus
172+
expNumSuccess int
173+
expNumTotal int
174+
}{
175+
"all plans success with no changes": {
176+
cmd: command.Apply,
177+
pullStatus: models.PullStatus{
178+
Projects: []models.ProjectStatus{
179+
{
180+
Status: models.PlannedNoChangesPlanStatus,
181+
},
182+
{
183+
Status: models.PlannedNoChangesPlanStatus,
184+
},
185+
},
186+
},
187+
expStatus: models.SuccessCommitStatus,
188+
expNumSuccess: 2,
189+
expNumTotal: 2,
190+
},
191+
"one plan, one plan success with no changes": {
192+
cmd: command.Apply,
193+
pullStatus: models.PullStatus{
194+
Projects: []models.ProjectStatus{
195+
{
196+
Status: models.PlannedNoChangesPlanStatus,
197+
},
198+
{
199+
Status: models.PlannedPlanStatus,
200+
},
201+
},
202+
},
203+
expStatus: models.PendingCommitStatus,
204+
expNumSuccess: 1,
205+
expNumTotal: 2,
206+
},
207+
"one plan, one apply, one plan success with no changes": {
208+
cmd: command.Apply,
209+
pullStatus: models.PullStatus{
210+
Projects: []models.ProjectStatus{
211+
{
212+
Status: models.PlannedNoChangesPlanStatus,
213+
},
214+
{
215+
Status: models.AppliedPlanStatus,
216+
},
217+
{
218+
Status: models.PlannedPlanStatus,
219+
},
220+
},
221+
},
222+
expStatus: models.PendingCommitStatus,
223+
expNumSuccess: 2,
224+
expNumTotal: 3,
225+
},
226+
"one apply error, one apply, one plan success with no changes": {
227+
cmd: command.Apply,
228+
pullStatus: models.PullStatus{
229+
Projects: []models.ProjectStatus{
230+
{
231+
Status: models.PlannedNoChangesPlanStatus,
232+
},
233+
{
234+
Status: models.AppliedPlanStatus,
235+
},
236+
{
237+
Status: models.ErroredApplyStatus,
238+
},
239+
},
240+
},
241+
expStatus: models.FailedCommitStatus,
242+
expNumSuccess: 2,
243+
expNumTotal: 3,
244+
},
245+
}
246+
247+
for name, c := range cases {
248+
t.Run(name, func(t *testing.T) {
249+
csu := &MockCSU{}
250+
cr := &PlanCommandRunner{
251+
commitStatusUpdater: csu,
252+
}
253+
cr.updateCommitStatus(&command.Context{}, c.pullStatus, command.Apply)
141254
Equals(t, models.Repo{}, csu.CalledRepo)
142255
Equals(t, models.PullRequest{}, csu.CalledPull)
143256
Equals(t, c.expStatus, csu.CalledStatus)

server/events/models/models.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,9 @@ const (
562562
// PlannedPlanStatus means that a plan has been successfully generated but
563563
// not yet applied.
564564
PlannedPlanStatus
565+
// PlannedNoChangesPlanStatus means that a plan has been successfully
566+
// generated with "No changes" and not yet applied.
567+
PlannedNoChangesPlanStatus
565568
// ErroredApplyStatus means that a plan has been generated but there was an
566569
// error while applying it.
567570
ErroredApplyStatus
@@ -586,6 +589,8 @@ func (p ProjectPlanStatus) String() string {
586589
return "plan_errored"
587590
case PlannedPlanStatus:
588591
return "planned"
592+
case PlannedNoChangesPlanStatus:
593+
return "planned_no_changes"
589594
case ErroredApplyStatus:
590595
return "apply_errored"
591596
case AppliedPlanStatus:

server/events/plan_command_runner.go

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ func (p *PlanCommandRunner) runAutoplan(ctx *command.Context) {
148148
ctx.Log.Err("writing results: %s", err)
149149
}
150150

151-
p.updateCommitStatus(ctx, pullStatus)
151+
p.updateCommitStatus(ctx, pullStatus, command.Plan)
152+
p.updateCommitStatus(ctx, pullStatus, command.Apply)
152153

153154
// Check if there are any planned projects and if there are any errors or if plans are being deleted
154155
if len(policyCheckCmds) > 0 &&
@@ -218,7 +219,7 @@ func (p *PlanCommandRunner) run(ctx *command.Context, cmd *CommentCommand) {
218219
return
219220
}
220221
ctx.Log.Debug("resetting VCS status")
221-
p.updateCommitStatus(ctx, *pullStatus)
222+
p.updateCommitStatus(ctx, *pullStatus, command.Plan)
222223
} else {
223224
// With a generic plan, we set successful commit statuses
224225
// with 0/0 projects planned successfully because some users require
@@ -272,7 +273,8 @@ func (p *PlanCommandRunner) run(ctx *command.Context, cmd *CommentCommand) {
272273
return
273274
}
274275

275-
p.updateCommitStatus(ctx, pullStatus)
276+
p.updateCommitStatus(ctx, pullStatus, command.Plan)
277+
p.updateCommitStatus(ctx, pullStatus, command.Apply)
276278

277279
// Runs policy checks step after all plans are successful.
278280
// This step does not approve any policies that require approval.
@@ -291,26 +293,39 @@ func (p *PlanCommandRunner) Run(ctx *command.Context, cmd *CommentCommand) {
291293
}
292294
}
293295

294-
func (p *PlanCommandRunner) updateCommitStatus(ctx *command.Context, pullStatus models.PullStatus) {
296+
func (p *PlanCommandRunner) updateCommitStatus(ctx *command.Context, pullStatus models.PullStatus, commandName command.Name) {
295297
var numSuccess int
296298
var numErrored int
297299
status := models.SuccessCommitStatus
298300

299-
numErrored = pullStatus.StatusCount(models.ErroredPlanStatus)
300-
// We consider anything that isn't a plan error as a plan success.
301-
// For example, if there is an apply error, that means that at least a
302-
// plan was generated successfully.
303-
numSuccess = len(pullStatus.Projects) - numErrored
301+
if commandName == command.Plan {
302+
numErrored = pullStatus.StatusCount(models.ErroredPlanStatus)
303+
// We consider anything that isn't a plan error as a plan success.
304+
// For example, if there is an apply error, that means that at least a
305+
// plan was generated successfully.
306+
numSuccess = len(pullStatus.Projects) - numErrored
304307

305-
if numErrored > 0 {
306-
status = models.FailedCommitStatus
308+
if numErrored > 0 {
309+
status = models.FailedCommitStatus
310+
}
311+
} else if commandName == command.Apply {
312+
numSuccess = pullStatus.StatusCount(models.AppliedPlanStatus) + pullStatus.StatusCount(models.PlannedNoChangesPlanStatus)
313+
numErrored = pullStatus.StatusCount(models.ErroredApplyStatus)
314+
315+
if numErrored > 0 {
316+
status = models.FailedCommitStatus
317+
} else if numSuccess < len(pullStatus.Projects) {
318+
// If there are plans that haven't been applied yet, we'll use a pending
319+
// status.
320+
status = models.PendingCommitStatus
321+
}
307322
}
308323

309324
if err := p.commitStatusUpdater.UpdateCombinedCount(
310325
ctx.Pull.BaseRepo,
311326
ctx.Pull,
312327
status,
313-
command.Plan,
328+
commandName,
314329
numSuccess,
315330
len(pullStatus.Projects),
316331
); err != nil {

0 commit comments

Comments
 (0)