Skip to content

Commit

Permalink
Merge pull request #216 from runatlantis/apply-all
Browse files Browse the repository at this point in the history
plan/apply all
  • Loading branch information
lkysow authored Aug 10, 2018
2 parents 6e7af89 + ca27c31 commit 65b94c4
Show file tree
Hide file tree
Showing 55 changed files with 1,689 additions and 840 deletions.
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
# v0.4.5 (unreleased)

## Features
* `atlantis apply` now applies **all** unapplied plans instead of just the plan in the root directory. ([#169](https://github.com/runatlantis/atlantis/issues/169))
* `atlantis plan` now plans **all** modified projects instead of just the root directory.

## Bugfixes
* Ignore approvals from the pull request author (Bitbucket Cloud only). Fixes ([#201](https://github.com/runatlantis/atlantis/issues/201))
* When double clicking on a GitHub comment, ex.
```
atlantis apply
```
GitHub would add two newlines to the end. If this was then pasted into a new
comment, Atlantis would accept it because of the extra newlines. This has been fixed
and the comment with two newlines will be accepted.
## Backwards Incompatibilities / Notes:
* `atlantis apply` now applies **all** unapplied plans. Previously it would only apply the plan in the root directory and default workspace.
* `atlantis plan` now plans **all** modified projects. Previously it would only run plan in the root directory and default workspace.
## Downloads
* [atlantis_darwin_amd64.zip](https://github.com/runatlantis/atlantis/releases/download/v0.4.5/atlantis_darwin_amd64.zip)
* [atlantis_linux_386.zip](https://github.com/runatlantis/atlantis/releases/download/v0.4.5/atlantis_linux_386.zip)
* [atlantis_linux_amd64.zip](https://github.com/runatlantis/atlantis/releases/download/v0.4.5/atlantis_linux_amd64.zip)
* [atlantis_linux_arm.zip](https://github.com/runatlantis/atlantis/releases/download/v0.4.5/atlantis_linux_arm.zip)
## Docker
`runatlantis/atlantis:v0.4.5`
# v0.4.4
## Features
Expand Down
8 changes: 4 additions & 4 deletions runatlantis.io/docs/atlantis-yaml-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ workflows:
## Usage Notes
* `atlantis.yaml` files must be placed at the root of the repo
* The only supported name is `atlantis.yaml`. Not `atlantis.yml` or `.atlantis.yaml`.
* Once an `atlantis.yaml` file exists in a repo Atlantis will not automatically plan
any other projects. This means if you have multiple projects in the same repo, once
you add an `atlantis.yaml` you'll need to add entries for each project.
* Once an `atlantis.yaml` file exists in a repo, Atlantis won't try to determine
where to run plan automatically. Instead it will just follow the configuration.
This means that you'll need to define each project in your repo.
* Atlantis uses the `atlantis.yaml` version from the pull request.

## Security
Expand All @@ -60,7 +60,7 @@ pull request so anyone that can submit a pull request can submit a malicious fil
As such, **`atlantis.yaml` files should only be enabled in a trusted environment**.

::: danger
It should be noted that `atlantis apply` itself could be exploited if run on a malicious file. See [Security](security.html#exploits).
It should be noted that `atlantis apply` itself could be exploited if run on a malicious terraform file. See [Security](security.html#exploits).
:::

## Reference
Expand Down
63 changes: 52 additions & 11 deletions runatlantis.io/docs/pull-request-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,44 @@ Atlantis currently supports three commands that can be run via pull request comm
```bash
atlantis help
```
**Explanation**: View help
### Explanation
View help

---
## atlantis plan
![Plan Command](./images/pr-comment-plan.png)
```bash
atlantis plan [options] -- [terraform plan flags]
```
**Explanation**: Runs `terraform plan` on the pull request's branch. You may wish to re-run plan after Atlantis has already done
### Explanation
Runs `terraform plan` on the pull request's branch. You may wish to re-run plan after Atlantis has already done
so if you've changed some resources manually.

Options:
* `-d directory` Which directory to run plan in relative to root of repo. Use `.` for root. Defaults to root.
### Examples
```bash
# Runs plan for any projects that Atlantis thinks were modified.
# If an `atlantis.yaml` file is specified, runs plan on the projects that
# were modified as determined by the `when_modified` config.
atlantis plan

# Runs plan in the root directory of the repo with workspace `default`.
atlantis plan -d .

# Runs plan in the `project1` directory of the repo with workspace `default`
atlantis plan -d project1

# Runs plan in the root directory of the repo with workspace `staging`
atlantis plan -w staging
```

### Options
* `-d directory` Which directory to run plan in relative to root of repo. Use `.` for root.
* Ex. `atlantis plan -d child/dir`
* `-p project` Which project to run plan for. Refers to the name of the project configured in the repo's [`atlantis.yaml` file](/docs/atlantis-yaml-reference.html). Cannot be used at same time as `-d` or `-w` because the project defines this already.
* `-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.

Additional Terraform flags:
### 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`
you can append them to the end of the comment after `--`, ex.
Expand All @@ -40,18 +59,40 @@ If you always need to append a certain flag, see [Project-Specific Customization
```bash
atlantis apply [options] -- [terraform apply flags]
```
**Explanation**: Runs `terraform apply` for the plan generated previously that matches the directory/project/workspace.
### Explanation
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**.
:::

### Examples
```bash
# Runs apply for all unapplied plans.
atlantis apply

# Runs apply in the root directory of the repo with workspace `default`.
atlantis apply -d .

# Runs apply in the `project1` directory of the repo with workspace `default`
atlantis apply -d project1

# Runs apply in the root directory of the repo with workspace `staging`
atlantis apply -w staging
```

Options:
* `-d directory` Apply the plan for this directory, relative to root of repo. Use `.` for root. Defaults to root.
### Options
* `-d directory` Apply the plan for this directory, relative to root of repo. Use `.` for root.
* `-p project` Apply the plan for this project. Refers to the name of the project configured in the repo's [`atlantis.yaml` file](/docs/atlantis-yaml-reference.html). Cannot be used at same time as `-d` or `-w`.
* `-w workspace` Apply the plan for this [Terraform workspace](https://www.terraform.io/docs/state/workspaces.html). Defaults to `default`. If not using Terraform workspaces you can ignore this.
* `-w workspace` Apply the plan for this [Terraform workspace](https://www.terraform.io/docs/state/workspaces.html). If not using Terraform workspaces you can ignore this.
* `--verbose` Append Atlantis log to comment.

Additional Terraform flags:
### Additional Terraform flags

Because Atlantis under the hood is running `terraform apply` on the planfile generated in the previous step, any Terraform options that would change the `plan` are ignored:
Because Atlantis under the hood is running `terraform apply plan.tfplan`, any Terraform options that would change the `plan` are ignored, ex:
* `-target=resource`
* `-var 'foo=bar'`
* `-var-file=myfile.tfvars`

They're ignored because they can't be specified for an already generated planfile.
If you would like to specify these flags, do it while running `atlantis plan`.
57 changes: 26 additions & 31 deletions server/events/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,7 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
return
}

var results []ProjectResult
for _, cmd := range projectCmds {
res := c.ProjectCommandRunner.Plan(cmd)
results = append(results, ProjectResult{
ProjectCommandResult: res,
RepoRelDir: cmd.RepoRelDir,
Workspace: cmd.Workspace,
})
}
results := c.runProjectCmds(projectCmds, PlanCommand)
c.updatePull(ctx, AutoplanCommand{}, CommandResult{ProjectResults: results})
}

Expand Down Expand Up @@ -152,45 +144,48 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
BaseRepo: baseRepo,
}
defer c.logPanics(ctx)

if !c.validateCtxAndComment(ctx) {
return
}

if err := c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, models.PendingCommitStatus, cmd.CommandName()); err != nil {
if err = c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, models.PendingCommitStatus, cmd.CommandName()); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}

var result ProjectCommandResult
var projectCmds []models.ProjectCommandContext
switch cmd.Name {
case PlanCommand:
projectCmd, err := c.ProjectCommandBuilder.BuildPlanCommand(ctx, cmd)
if err != nil {
c.updatePull(ctx, cmd, CommandResult{Error: err})
return
}
result = c.ProjectCommandRunner.Plan(projectCmd)
projectCmds, err = c.ProjectCommandBuilder.BuildPlanCommands(ctx, cmd)
case ApplyCommand:
projectCmd, err := c.ProjectCommandBuilder.BuildApplyCommand(ctx, cmd)
if err != nil {
c.updatePull(ctx, cmd, CommandResult{Error: err})
return
}
result = c.ProjectCommandRunner.Apply(projectCmd)
projectCmds, err = c.ProjectCommandBuilder.BuildApplyCommands(ctx, cmd)
default:
ctx.Log.Err("failed to determine desired command, neither plan nor apply")
return
}

if err != nil {
c.updatePull(ctx, cmd, CommandResult{Error: err})
return
}
results := c.runProjectCmds(projectCmds, cmd.Name)
c.updatePull(
ctx,
cmd,
CommandResult{
ProjectResults: []ProjectResult{{
RepoRelDir: cmd.RepoRelDir,
Workspace: cmd.Workspace,
ProjectCommandResult: result,
}}})
ProjectResults: results})
}

func (c *DefaultCommandRunner) runProjectCmds(cmds []models.ProjectCommandContext, cmdName CommandName) []ProjectResult {
var results []ProjectResult
for _, pCmd := range cmds {
var res ProjectResult
switch cmdName {
case PlanCommand:
res = c.ProjectCommandRunner.Plan(pCmd)
case ApplyCommand:
res = c.ProjectCommandRunner.Apply(pCmd)
}
results = append(results, res)
}
return results
}

func (c *DefaultCommandRunner) getGithubData(baseRepo models.Repo, pullNum int) (models.PullRequest, models.Repo, error) {
Expand Down
66 changes: 6 additions & 60 deletions server/events/command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,18 @@ import (

var projectCommandBuilder *mocks.MockProjectCommandBuilder
var eventParsing *mocks.MockEventParsing
var vcsClient *vcsmocks.MockClientProxy
var ghStatus *mocks.MockCommitStatusUpdater
var githubGetter *mocks.MockGithubPullGetter
var gitlabGetter *mocks.MockGitlabMergeRequestGetter
var ch events.DefaultCommandRunner
var logBytes *bytes.Buffer

func setup(t *testing.T) {
func setup(t *testing.T) *vcsmocks.MockClientProxy {
RegisterMockTestingT(t)
projectCommandBuilder = mocks.NewMockProjectCommandBuilder()
eventParsing = mocks.NewMockEventParsing()
ghStatus = mocks.NewMockCommitStatusUpdater()
vcsClient = vcsmocks.NewMockClientProxy()
vcsClient := vcsmocks.NewMockClientProxy()
githubGetter = mocks.NewMockGithubPullGetter()
gitlabGetter = mocks.NewMockGitlabMergeRequestGetter()
logger := logmocks.NewMockSimpleLogging()
Expand All @@ -66,11 +65,12 @@ func setup(t *testing.T) {
ProjectCommandBuilder: projectCommandBuilder,
ProjectCommandRunner: projectCommandRunner,
}
return vcsClient
}

func TestRunCommentCommand_LogPanics(t *testing.T) {
t.Log("if there is a panic it is commented back on the pull request")
setup(t)
vcsClient := setup(t)
ch.AllowForkPRs = true // Lets us get to the panic code.
defer func() { ch.AllowForkPRs = false }()
When(ghStatus.Update(fixtures.GithubRepo, fixtures.Pull, models.PendingCommitStatus, events.PlanCommand)).ThenPanic("panic")
Expand Down Expand Up @@ -125,7 +125,7 @@ func TestRunCommentCommand_GithubPullParseErr(t *testing.T) {
func TestRunCommentCommand_ForkPRDisabled(t *testing.T) {
t.Log("if a command is run on a forked pull request and this is disabled atlantis should" +
" comment saying that this is not allowed")
setup(t)
vcsClient := setup(t)
ch.AllowForkPRs = false // by default it's false so don't need to reset
var pull github.PullRequest
modelPull := models.PullRequest{State: models.OpenPullState}
Expand All @@ -143,7 +143,7 @@ func TestRunCommentCommand_ForkPRDisabled(t *testing.T) {
func TestRunCommentCommand_ClosedPull(t *testing.T) {
t.Log("if a command is run on a closed pull request atlantis should" +
" comment saying that this is not allowed")
setup(t)
vcsClient := setup(t)
pull := &github.PullRequest{
State: github.String("closed"),
}
Expand All @@ -154,57 +154,3 @@ func TestRunCommentCommand_ClosedPull(t *testing.T) {
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, nil)
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, modelPull.Num, "Atlantis commands can't be run on closed pull requests")
}

func TestRunCommentCommand_FullRun(t *testing.T) {
pull := &github.PullRequest{
State: github.String("closed"),
}
expCmdResult := events.CommandResult{
ProjectResults: []events.ProjectResult{
{
RepoRelDir: ".",
Workspace: "default",
},
},
}
for _, c := range []events.CommandName{events.PlanCommand, events.ApplyCommand} {
setup(t)
cmd := events.NewCommentCommand(".", nil, c, false, "default", "")
When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.GithubRepo, fixtures.GithubRepo, nil)

cmdCtx := models.ProjectCommandContext{RepoRelDir: "."}
switch c {
case events.PlanCommand:
When(projectCommandBuilder.BuildPlanCommand(matchers.AnyPtrToEventsCommandContext(), matchers.AnyPtrToEventsCommentCommand())).ThenReturn(cmdCtx, nil)
case events.ApplyCommand:
When(projectCommandBuilder.BuildApplyCommand(matchers.AnyPtrToEventsCommandContext(), matchers.AnyPtrToEventsCommentCommand())).ThenReturn(cmdCtx, nil)
}

ch.RunCommentCommand(fixtures.GithubRepo, nil, nil, fixtures.User, fixtures.Pull.Num, cmd)

ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, models.PendingCommitStatus, c)
_, _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandName(), matchers.AnyEventsCommandResult()).GetCapturedArguments()
Equals(t, expCmdResult, response)
vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())
}
}

func TestRunAutoplanCommands(t *testing.T) {
expCmdResult := events.CommandResult{
ProjectResults: []events.ProjectResult{
{
RepoRelDir: ".",
Workspace: "default",
},
},
}
setup(t)
When(projectCommandBuilder.BuildAutoplanCommands(matchers.AnyPtrToEventsCommandContext())).ThenReturn([]models.ProjectCommandContext{{RepoRelDir: ".", Workspace: "default"}}, nil)
ch.RunAutoplanCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.Pull, fixtures.User)

ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, models.PendingCommitStatus, events.PlanCommand)
_, _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandName(), matchers.AnyEventsCommandResult()).GetCapturedArguments()
Equals(t, expCmdResult, response)
vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())
}
Loading

0 comments on commit 65b94c4

Please sign in to comment.