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

Use workspace instead of env for tf >=0.10 #219

Merged
merged 2 commits into from
Dec 18, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
20 changes: 10 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
## Bug Fixes
* Don't set `as_user` to true for Slack webhooks so we can integrate as a workspace app. ([#206](https://github.com/hootsuite/atlantis/pull/206))

## Backwards Incompatibilities / Notes:
None

## Downloads
* [atlantis_darwin_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.1/atlantis_darwin_amd64.zip)
* [atlantis_linux_386.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.1/atlantis_linux_386.zip)
* [atlantis_linux_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.1/atlantis_linux_amd64.zip)
* [atlantis_linux_arm.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.1/atlantis_linux_arm.zip)

## Backwards Incompatibilities / Notes:
None

# v0.2.0
## Features
* GitLab is now supported! ([#190](https://github.com/hootsuite/atlantis/pull/190))
Expand All @@ -22,15 +22,15 @@ None
## Bug Fixes
None

## Backwards Incompatibilities / Notes:
None

## Downloads
* [atlantis_darwin_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.0/atlantis_darwin_amd64.zip)
* [atlantis_linux_386.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.0/atlantis_linux_386.zip)
* [atlantis_linux_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.0/atlantis_linux_amd64.zip)
* [atlantis_linux_arm.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.0/atlantis_linux_arm.zip)

## Backwards Incompatibilities / Notes:
None

# v0.1.3
## Features
* Environment variables are passed through to `extra_arguments`. ([#150](https://github.com/hootsuite/atlantis/pull/150))
Expand All @@ -40,16 +40,16 @@ None
* Modules in list of changed files weren't being filtered. ([#193](https://github.com/hootsuite/atlantis/pull/193))
* Nil pointer error in bootstrap mode. ([#181](https://github.com/hootsuite/atlantis/pull/181))

## Backwards Incompatibilities / Notes:
None

## Downloads
* [atlantis_darwin_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.1.3/atlantis_darwin_amd64.zip)
* [atlantis_linux_386.zip](https://github.com/hootsuite/atlantis/releases/download/v0.1.3/atlantis_linux_386.zip)
* [atlantis_linux_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.1.3/atlantis_linux_amd64.zip)
* [atlantis_linux_arm.zip](https://github.com/hootsuite/atlantis/releases/download/v0.1.3/atlantis_linux_arm.zip)

## Backwards Incompatibilities / Notes:
None

# v0.1.2
# v0.1.2
## Features
* all flags passed to `atlantis plan` or `atlantis apply` will now be passed through to `terraform`. ([#131](https://github.com/hootsuite/atlantis/pull/131))

Expand Down
7 changes: 0 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,6 @@ func TestLockingExisting(t *testing.T) {
```
- each test should have a `t.Log` that describes what the current state is and what should happen (like a behavioural test)

# Glossary
* **Run**: Encompasses the two steps (plan and apply) for modifying infrastructure in a specific environment
* **Project Lock**: When a run has started but is not yet completed, the infrastructure and environment that's being modified is "locked" against
other runs being started for the same set of infrastructure and environment. We determine what infrastructure is being modified by combining the
repository name, the directory in the repository at which the terraform commands need to be run, and the environment that's being modified
* **Project Path**: The path relative to the repository's root at which terraform commands need to be executed for this Run

# Creating a New Release
1. Update version number in
1. `main.go`
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ regen-mocks: ## Delete all mocks and then run go generate to regen them
go generate $$(go list ./... | grep -v e2e | grep -v vendor | grep -v static)

test: ## Run tests
go test $(PKG)
@go test $(PKG)

test-coverage:
./scripts/coverage.sh $(PKG)
Expand Down
46 changes: 24 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Read about [Why We Built Atlantis](https://www.atlantis.run/blog/atlantis-releas
* [Getting Started](#getting-started)
* [Pull Request Commands](#pull-request-commands)
* [Project Structure](#project-structure)
* [Environments](#environments)
* [Workspaces/Environments](#workspacesenvironments)
* [Terraform Versions](#terraform-versions)
* [Project-Specific Customization](#project-specific-customization)
* [Locking](#locking)
Expand All @@ -30,22 +30,22 @@ Read about [Why We Built Atlantis](https://www.atlantis.run/blog/atlantis-releas
* [AWS Credentials](#aws-credentials)
* [Glossary](#glossary)
* [Project](#project)
* [Environment](#environment)
* [Workspace/Environment](#workspaceenvironment)
* [FAQ](#faq)
* [Contributing](#contributing)
* [Credits](#credits)

## Features
➜ Collaborate on Terraform with your team
- Run terraform `plan` and `apply` **from GitHub pull requests** so everyone can review the output
- **Lock environments** until pull requests are merged to prevent concurrent modification and confusion
- **Lock workspaces** until pull requests are merged to prevent concurrent modification and confusion

➜ Developers can write Terraform safely
- **No need to distribute AWS credentials** to your whole team. Developers can submit Terraform changes and run `plan` and `apply` directly from the pull/merge request
- Optionally, require a **review and approval** prior to running `apply`

➜ Also
- No more **copy-pasted code across environments**. Atlantis supports using an `env/{env}.tfvars` file per environment so you can write your base configuration once
- No more **copy-pasted code across workspaces/environments**. Atlantis supports using an `env/{env}.tfvars` file per workspace/environment so you can write your base configuration once
- Support **multiple versions of Terraform** with a simple project config file

## Atlantis Works With
Expand Down Expand Up @@ -74,11 +74,11 @@ Atlantis currently supports three commands that can be run via pull request comm
#### `atlantis help`
View help

#### `atlantis plan [env]`
Runs `terraform plan` for the changes in this pull request. If `[env]` is specified, will switch to that environment (or workspace in terraform > 0.10), before running `plan`. Any additional arguments passed to `atlantis plan` will be passed on to `terraform plan`. For example if you'd like to run `terraform plan -target={target}` then you can comment `atlantis plan -target={target}`.
#### `atlantis plan [workspace]`
Runs `terraform plan` for the changes in this pull request. If `[workspace]` is specified, will switch to that workspace, before running `plan`. Any additional arguments passed to `atlantis plan` will be passed on to `terraform plan`. For example if you'd like to run `terraform plan -target={target}` then you can comment `atlantis plan -target={target}`.

#### `atlantis apply [env]`
Runs `terraform apply` for the plan generated by `atlantis plan`. If `[env]` is specified, will switch to that env/workspace.
#### `atlantis apply [workspace]`
Runs `terraform apply` for the plan generated by `atlantis plan`. If `[workspace]` is specified, will switch to that workspace.
Any additional arguments passed to `atlantis apply` will be passed on to `terraform apply`.

## Project Structure
Expand All @@ -99,7 +99,7 @@ Atlantis supports several Terraform project structures:
   ├── main.tf
└── ...
```
- one folder per environment
- one folder per set of configuration
```
.
├── staging
Expand All @@ -109,7 +109,7 @@ Atlantis supports several Terraform project structures:
   ├── main.tf
└── ...
```
- using `env/{env}.tfvars` to define environment specific variables. This works in both multi-project repos and single-project repos.
- using `env/{env}.tfvars` to define workspace specific variables. This works in both multi-project repos and single-project repos.
```
.
├── env
Expand All @@ -131,23 +131,23 @@ or
│   └── staging.tfvars
└── main.tf
```
With the above project structure you can de-duplicate your Terraform code between environments without requiring extensive use of modules. At Hootsuite we've found this project format to be very successful and use it in all of our 100+ Terraform repositories.
With the above project structure you can de-duplicate your Terraform code between workspaces/environments without requiring extensive use of modules. At Hootsuite we've found this project format to be very successful and use it in all of our 100+ Terraform repositories.

## Environments
Terraform recently introduced [State Environments](https://www.terraform.io/docs/state/environments.html) that
> allows a single folder of Terraform configurations to manage multiple distinct infrastructure resources
## Workspaces/Environments
Terraform introduced [Workspaces](https://www.terraform.io/docs/state/workspaces.html) in 0.9. They allow for
> a single directory of Terraform configuration to be used to manage multiple distinct sets of infrastructure resources

If you're using a Terraform version >= 0.9.0, Atlantis supports environments through an additional argument to the `atlantis plan` and `atlantis apply` commands.
If you're using a Terraform version >= 0.9.0, Atlantis supports workspaces through an additional argument to the `atlantis plan` and `atlantis apply` commands.
For example,
```
atlantis plan staging
```

If an environment is specified Atlantis will use `terraform env select {env}` prior to running `terraform plan` or `terraform apply`.
If a workspace is specified, Atlantis will use `terraform workspace select {workspace}` prior to running `terraform plan` or `terraform apply`.

If you're using the `env/{env}.tfvars` [project structure](#project-structure) we will also append `-tfvars=env/{env}.tfvars` to `plan` and `apply`.

If no environment is specified we will use `default` as the environment.
If no workspace is specified, terraform will use the `default` workspace by default.

## Terraform Versions
By default, Atlantis will use the `terraform` executable that is in its path. To use a specific version of Terraform just install that version on the server that Atlantis is running on.
Expand Down Expand Up @@ -215,13 +215,15 @@ be the value of that argument. Else it will be `default`
- `WORKSPACE`: absolute path to the root of the project on disk

## Locking
When `plan` is run, the [project](#project) and [environment](#environment) are **Locked** until an `apply` succeeds **and** the pull request/merge request is merged.
When `plan` is run, the [project](#project) and [workspace](#workspaceenvironment) are **Locked** until an `apply` succeeds **and** the pull request/merge request is merged.
This protects against concurrent modifications to the same set of infrastructure and prevents
users from seeing a `plan` that will be invalid if another pull request is merged.

To unlock the project and environment without completing an `apply` and merging, click the link
If you have multiple directories inside a single repository, only the directory will be locked. Not the whole repo.

To unlock the project and workspace without completing an `apply` and merging, click the link
at the bottom of the plan comment to discard the plan and delete the lock.
Once a plan is discarded, you'll need to run `plan` again prior to running `apply`.
Once a plan is discarded, you'll need to run `plan` again prior to running `apply` when you go back to that pull request.

## Approvals
If you'd like to require pull/merge requests to be approved prior to a user running `atlantis apply` simply run Atlantis with the `--require-approval` flag.
Expand Down Expand Up @@ -455,8 +457,8 @@ the assumed role specified in the `aws` provider and will have the session named
A Terraform project. Multiple projects can be in a single GitHub repo.
We identify a project by its repo **and** the path to the root of the project within that repo.

#### Environment
A Terraform environment. See [terraform docs](https://www.terraform.io/docs/state/environments.html) for more information.
#### Workspace/Environment
A Terraform workspace. See [terraform docs](https://www.terraform.io/docs/state/workspaces.html) for more information.

## FAQ
**Q: Does Atlantis affect Terraform [remote state](https://www.terraform.io/docs/state/remote.html)?**
Expand Down
2 changes: 1 addition & 1 deletion e2e/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
var defaultAtlantisURL = "http://localhost:4141"
var projectTypes = []Project{
{"standalone", "run plan", "run apply"},
{"standalone-with-env", "run plan staging", "run apply staging"},
{"standalone-with-workspace", "run plan staging", "run apply staging"},
}

type Project struct {
Expand Down
22 changes: 11 additions & 11 deletions server/events/apply_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import (
// ApplyExecutor handles executing terraform apply.
type ApplyExecutor struct {
VCSClient vcs.ClientProxy
Terraform *terraform.Client
Terraform *terraform.DefaultClient
RequireApproval bool
Run *run.Run
Workspace Workspace
Workspace AtlantisWorkspace
ProjectPreExecute *DefaultProjectPreExecutor
Webhooks webhooks.Sender
}
Expand All @@ -37,21 +37,21 @@ func (a *ApplyExecutor) Execute(ctx *CommandContext) CommandResponse {
ctx.Log.Info("confirmed pull request was approved")
}

repoDir, err := a.Workspace.GetWorkspace(ctx.BaseRepo, ctx.Pull, ctx.Command.Environment)
repoDir, err := a.Workspace.GetWorkspace(ctx.BaseRepo, ctx.Pull, ctx.Command.Workspace)
if err != nil {
return CommandResponse{Failure: "No workspace found. Did you run plan?"}
}
ctx.Log.Info("found workspace in %q", repoDir)

// Plans are stored at project roots by their environment names. We just
// Plans are stored at project roots by their workspace names. We just
// need to find them.
var plans []models.Plan
err = filepath.Walk(repoDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Check if the plan is for the right env,
if !info.IsDir() && info.Name() == ctx.Command.Environment+".tfplan" {
// Check if the plan is for the right workspace,
if !info.IsDir() && info.Name() == ctx.Command.Workspace+".tfplan" {
rel, _ := filepath.Rel(repoDir, filepath.Dir(path))
plans = append(plans, models.Plan{
Project: models.NewProject(ctx.BaseRepo.FullName, rel),
Expand All @@ -64,7 +64,7 @@ func (a *ApplyExecutor) Execute(ctx *CommandContext) CommandResponse {
return CommandResponse{Error: errors.Wrap(err, "finding plans")}
}
if len(plans) == 0 {
return CommandResponse{Failure: "No plans found for that environment."}
return CommandResponse{Failure: "No plans found for that workspace."}
}
var paths []string
for _, p := range plans {
Expand Down Expand Up @@ -92,12 +92,12 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P

applyExtraArgs := config.GetExtraArguments(ctx.Command.Name.String())
absolutePath := filepath.Join(repoDir, plan.Project.Path)
env := ctx.Command.Environment
workspace := ctx.Command.Workspace
tfApplyCmd := append(append(append([]string{"apply", "-no-color"}, applyExtraArgs...), ctx.Command.Flags...), plan.LocalPath)
output, err := a.Terraform.RunCommandWithVersion(ctx.Log, absolutePath, tfApplyCmd, terraformVersion, env)
output, err := a.Terraform.RunCommandWithVersion(ctx.Log, absolutePath, tfApplyCmd, terraformVersion, workspace)

a.Webhooks.Send(ctx.Log, webhooks.ApplyResult{ // nolint: errcheck
Workspace: env,
Workspace: workspace,
User: ctx.User,
Repo: ctx.BaseRepo,
Pull: ctx.Pull,
Expand All @@ -110,7 +110,7 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P
ctx.Log.Info("apply succeeded")

if len(config.PostApply) > 0 {
_, err := a.Run.Execute(ctx.Log, config.PostApply, absolutePath, env, terraformVersion, "post_apply")
_, err := a.Run.Execute(ctx.Log, config.PostApply, absolutePath, workspace, terraformVersion, "post_apply")
if err != nil {
return ProjectResult{Error: errors.Wrap(err, "running post apply commands")}
}
Expand Down
26 changes: 13 additions & 13 deletions server/events/workspace.go → server/events/atlantis_workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ import (

const workspacePrefix = "repos"

//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_workspace.go Workspace
//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_atlantis_workspace.go AtlantisWorkspace

// Workspace handles the workspace on disk for running commands.
type Workspace interface {
// AtlantisWorkspace handles the workspace on disk for running commands.
type AtlantisWorkspace interface {
// Clone git clones headRepo, checks out the branch and then returns the
// absolute path to the root of the cloned repo.
Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, env string) (string, error)
Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, workspace string) (string, error)
// GetWorkspace returns the path to the workspace for this repo and pull.
GetWorkspace(r models.Repo, p models.PullRequest, env string) (string, error)
GetWorkspace(r models.Repo, p models.PullRequest, workspace string) (string, error)
// Delete deletes the workspace for this repo and pull.
Delete(r models.Repo, p models.PullRequest) error
}

// FileWorkspace implements Workspace with the file system.
// FileWorkspace implements AtlantisWorkspace with the file system.
type FileWorkspace struct {
DataDir string
}
Expand All @@ -38,10 +38,10 @@ func (w *FileWorkspace) Clone(
baseRepo models.Repo,
headRepo models.Repo,
p models.PullRequest,
env string) (string, error) {
cloneDir := w.cloneDir(baseRepo, p, env)
workspace string) (string, error) {
cloneDir := w.cloneDir(baseRepo, p, workspace)

// This is safe to do because we lock runs on repo/pull/env so no one else
// This is safe to do because we lock runs on repo/pull/workspace so no one else
// is using this workspace.
log.Info("cleaning clone directory %q", cloneDir)
if err := os.RemoveAll(cloneDir); err != nil {
Expand Down Expand Up @@ -71,8 +71,8 @@ func (w *FileWorkspace) Clone(
}

// GetWorkspace returns the path to the workspace for this repo and pull.
func (w *FileWorkspace) GetWorkspace(r models.Repo, p models.PullRequest, env string) (string, error) {
repoDir := w.cloneDir(r, p, env)
func (w *FileWorkspace) GetWorkspace(r models.Repo, p models.PullRequest, workspace string) (string, error) {
repoDir := w.cloneDir(r, p, workspace)
if _, err := os.Stat(repoDir); err != nil {
return "", errors.Wrap(err, "checking if workspace exists")
}
Expand All @@ -88,6 +88,6 @@ func (w *FileWorkspace) repoPullDir(r models.Repo, p models.PullRequest) string
return filepath.Join(w.DataDir, workspacePrefix, r.FullName, strconv.Itoa(p.Num))
}

func (w *FileWorkspace) cloneDir(r models.Repo, p models.PullRequest, env string) string {
return filepath.Join(w.repoPullDir(r, p), env)
func (w *FileWorkspace) cloneDir(r models.Repo, p models.PullRequest, workspace string) string {
return filepath.Join(w.repoPullDir(r, p), workspace)
}
Loading