Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete previous plans on autoplan or atlantis plan #1633

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions runatlantis.io/docs/using-atlantis.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ atlantis plan -w staging
* `-w workspace` Switch to this [Terraform workspace](https://www.terraform.io/docs/state/workspaces.html) before planning. Defaults to `default`. If not using Terraform workspaces you can ignore this.
* `--verbose` Append Atlantis log to comment.

::: warning NOTE
A `atlantis plan` (without flags), like autoplans, discards all plans previously created with `atlantis plan` `-p`/`-d`/`-w`
:::

### Additional Terraform flags

If you need to run `terraform plan` with additional arguments, like `-target=resource` or `-var 'foo-bar'` or `-var-file myfile.tfvars`
Expand All @@ -65,6 +69,7 @@ Runs `terraform apply` for the plan that matches the directory/project/workspace

::: tip
If no directory/project/workspace is specified, ex. `atlantis apply`, this command will apply **all unapplied plans from this pull request**.
This includes all projects that have been planned manually with `atlantis plan` `-p`/`-d`/`-w` since the last autoplan or `atlantis plan` command.
:::

### Examples
Expand Down
1 change: 1 addition & 0 deletions server/controllers/events/events_controller_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,7 @@ func setupE2E(t *testing.T, repoDir string) (events_controllers.VCSEventsControl
parallelPoolSize,
silenceNoProjects,
boltdb,
lockingClient,
)

e2ePullReqStatusFetcher := vcs.NewPullReqStatusFetcher(e2eVCSClient)
Expand Down
77 changes: 75 additions & 2 deletions server/events/command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ var policyCheckCommandRunner *events.PolicyCheckCommandRunner
var approvePoliciesCommandRunner *events.ApprovePoliciesCommandRunner
var planCommandRunner *events.PlanCommandRunner
var applyLockChecker *lockingmocks.MockApplyLockChecker
var locker *lockingmocks.MockLocker
var applyCommandRunner *events.ApplyCommandRunner
var unlockCommandRunner *events.UnlockCommandRunner
var preWorkflowHooksCommandRunner events.PreWorkflowHooksCommandRunner
Expand All @@ -89,6 +90,7 @@ func setup(t *testing.T) *vcsmocks.MockClient {
drainer = &events.Drainer{}
deleteLockCommand = eventmocks.NewMockDeleteLockCommand()
applyLockChecker = lockingmocks.NewMockApplyLockChecker()
locker = lockingmocks.NewMockLocker()

dbUpdater = &events.DBUpdater{
Backend: defaultBoltDB,
Expand Down Expand Up @@ -133,6 +135,7 @@ func setup(t *testing.T) *vcsmocks.MockClient {
parallelPoolSize,
SilenceNoProjects,
defaultBoltDB,
locker,
)

pullReqStatusFetcher := vcs.NewPullReqStatusFetcher(vcsClient)
Expand Down Expand Up @@ -529,9 +532,78 @@ func TestRunUnlockCommandFail_VCSComment(t *testing.T) {
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, fixtures.Pull.Num, "Failed to delete PR locks", "unlock")
}

func TestRunAutoplanCommand_DeletePlans(t *testing.T) {
setup(t)
tmp, cleanup := TempDir(t)
defer cleanup()
boltDB, err := db.New(tmp)
Ok(t, err)
dbUpdater.Backend = boltDB
applyCommandRunner.Backend = boltDB
autoMerger.GlobalAutomerge = true
defer func() { autoMerger.GlobalAutomerge = false }()

When(projectCommandBuilder.BuildAutoplanCommands(matchers.AnyPtrToEventsCommandContext())).
ThenReturn([]command.ProjectContext{
{
CommandName: command.Plan,
},
{
CommandName: command.Plan,
},
}, nil)
When(projectCommandRunner.Plan(matchers.AnyModelsProjectCommandContext())).ThenReturn(command.ProjectResult{PlanSuccess: &models.PlanSuccess{}})
When(workingDir.GetPullDir(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn(tmp, nil)
fixtures.Pull.BaseRepo = fixtures.GithubRepo
ch.RunAutoplanCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.Pull, fixtures.User)
pendingPlanFinder.VerifyWasCalledOnce().DeletePlans(tmp)
locker.VerifyWasCalledOnce().UnlockByPull(fixtures.Pull.BaseRepo.FullName, fixtures.Pull.Num)
}

func TestRunGenericPlanCommand_DeletePlans(t *testing.T) {
setup(t)
tmp, cleanup := TempDir(t)
defer cleanup()
boltDB, err := db.New(tmp)
Ok(t, err)
dbUpdater.Backend = boltDB
applyCommandRunner.Backend = boltDB
autoMerger.GlobalAutomerge = true
defer func() { autoMerger.GlobalAutomerge = false }()

When(projectCommandRunner.Plan(matchers.AnyModelsProjectCommandContext())).ThenReturn(command.ProjectResult{PlanSuccess: &models.PlanSuccess{}})
When(workingDir.GetPullDir(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn(tmp, nil)
pull := &github.PullRequest{State: github.String("open")}
modelPull := models.PullRequest{BaseRepo: fixtures.GithubRepo, State: models.OpenPullState, Num: fixtures.Pull.Num}
When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, modelPull.BaseRepo, fixtures.GithubRepo, nil)
fixtures.Pull.BaseRepo = fixtures.GithubRepo
ch.RunCommentCommand(fixtures.GithubRepo, nil, nil, fixtures.User, fixtures.Pull.Num, &events.CommentCommand{Name: command.Plan})
pendingPlanFinder.VerifyWasCalledOnce().DeletePlans(tmp)
locker.VerifyWasCalledOnce().UnlockByPull(fixtures.Pull.BaseRepo.FullName, fixtures.Pull.Num)
}

func TestRunSpecificPlanCommandDoesnt_DeletePlans(t *testing.T) {
setup(t)
tmp, cleanup := TempDir(t)
defer cleanup()
boltDB, err := db.New(tmp)
Ok(t, err)
dbUpdater.Backend = boltDB
applyCommandRunner.Backend = boltDB
autoMerger.GlobalAutomerge = true
defer func() { autoMerger.GlobalAutomerge = false }()

When(projectCommandRunner.Plan(matchers.AnyModelsProjectCommandContext())).ThenReturn(command.ProjectResult{PlanSuccess: &models.PlanSuccess{}})
When(workingDir.GetPullDir(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn(tmp, nil)
fixtures.Pull.BaseRepo = fixtures.GithubRepo
ch.RunCommentCommand(fixtures.GithubRepo, nil, nil, fixtures.User, fixtures.Pull.Num, &events.CommentCommand{Name: command.Plan, ProjectName: "default"})
pendingPlanFinder.VerifyWasCalled(Never()).DeletePlans(tmp)
}

// Test that if one plan fails and we are using automerge, that
// we delete the plans.
func TestRunAutoplanCommand_DeletePlans(t *testing.T) {
func TestRunAutoplanCommandWithError_DeletePlans(t *testing.T) {
setup(t)
tmp, cleanup := TempDir(t)
defer cleanup()
Expand Down Expand Up @@ -574,7 +646,8 @@ func TestRunAutoplanCommand_DeletePlans(t *testing.T) {
ThenReturn(tmp, nil)
fixtures.Pull.BaseRepo = fixtures.GithubRepo
ch.RunAutoplanCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.Pull, fixtures.User)
pendingPlanFinder.VerifyWasCalledOnce().DeletePlans(tmp)
// gets called twice: the first time before the plan starts, the second time after the plan errors
pendingPlanFinder.VerifyWasCalled(Times(2)).DeletePlans(tmp)
}

func TestFailedApprovalCreatesFailedStatusUpdate(t *testing.T) {
Expand Down
23 changes: 23 additions & 0 deletions server/events/plan_command_runner.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package events

import (
"github.com/runatlantis/atlantis/server/core/locking"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs"
Expand All @@ -22,6 +23,7 @@ func NewPlanCommandRunner(
parallelPoolSize int,
SilenceNoProjects bool,
pullStatusFetcher PullStatusFetcher,
locker locking.Locker,
) *PlanCommandRunner {
return &PlanCommandRunner{
silenceVCSStatusNoPlans: silenceVCSStatusNoPlans,
Expand All @@ -39,6 +41,7 @@ func NewPlanCommandRunner(
parallelPoolSize: parallelPoolSize,
SilenceNoProjects: SilenceNoProjects,
pullStatusFetcher: pullStatusFetcher,
locker: locker,
}
}

Expand All @@ -64,6 +67,7 @@ type PlanCommandRunner struct {
autoMerger *AutoMerger
parallelPoolSize int
pullStatusFetcher PullStatusFetcher
locker locking.Locker
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name does not follow the naming conversion of the struct

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jamengual I've now fixed this

}

func (p *PlanCommandRunner) runAutoplan(ctx *command.Context) {
Expand Down Expand Up @@ -106,6 +110,14 @@ func (p *PlanCommandRunner) runAutoplan(ctx *command.Context) {
ctx.Log.Warn("unable to update plan commit status: %s", err)
}

// discard previous plans that might not be relevant anymore
ctx.Log.Debug("deleting previous plans and locks")
p.deletePlans(ctx)
_, err = p.locker.UnlockByPull(baseRepo.FullName, pull.Num)
if err != nil {
ctx.Log.Err("deleting locks: %s", err)
}

// Only run commands in parallel if enabled
var result command.Result
if p.isParallelEnabled(projectCmds) {
Expand Down Expand Up @@ -180,6 +192,17 @@ func (p *PlanCommandRunner) run(ctx *command.Context, cmd *CommentCommand) {

projectCmds, policyCheckCmds := p.partitionProjectCmds(ctx, projectCmds)

// if the plan is generic, new plans will be generated based on changes
// discard previous plans that might not be relevant anymore
if !cmd.IsForSpecificProject() {
ctx.Log.Debug("deleting previous plans and locks")
p.deletePlans(ctx)
_, err = p.locker.UnlockByPull(baseRepo.FullName, pull.Num)
if err != nil {
ctx.Log.Err("deleting locks: %s", err)
}
}

// Only run commands in parallel if enabled
var result command.Result
if p.isParallelEnabled(projectCmds) {
Expand Down
1 change: 1 addition & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
userConfig.ParallelPoolSize,
userConfig.SilenceNoProjects,
backend,
lockingClient,
)

pullReqStatusFetcher := vcs.NewPullReqStatusFetcher(vcsClient)
Expand Down