From 7c1220a655673a77e9555f79f4069a60bcdf2bc1 Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Sun, 17 Dec 2017 16:46:49 -0800 Subject: [PATCH] Rename from environment to workspace --- CHANGELOG.md | 20 +-- CONTRIBUTING.md | 7 - Makefile | 2 +- README.md | 46 +++---- e2e/main.go | 2 +- server/events/apply_executor.go | 20 +-- .../{workspace.go => atlantis_workspace.go} | 26 ++-- server/events/atlantis_workspace_locker.go | 61 +++++++++ .../events/atlantis_workspace_locker_test.go | 103 +++++++++++++++ server/events/command_handler.go | 10 +- server/events/command_handler_test.go | 26 ++-- server/events/env_lock.go | 50 ------- server/events/event_parser.go | 20 +-- server/events/event_parser_test.go | 12 +- server/events/locking/boltdb/boltdb.go | 22 ++-- server/events/locking/boltdb/boltdb_test.go | 40 +++--- server/events/locking/locking.go | 42 +++--- server/events/locking/locking_test.go | 26 ++-- server/events/locking/mocks/mock_backend.go | 24 ++-- server/events/locking/mocks/mock_locker.go | 12 +- server/events/markdown_renderer.go | 6 +- ...orkspace.go => mock_atlantis_workspace.go} | 82 ++++++------ .../mocks/mock_atlantis_workspace_locker.go | 123 ++++++++++++++++++ server/events/mocks/mock_workspace_locker.go | 123 ------------------ server/events/models/models.go | 4 +- server/events/plan_executor.go | 20 +-- server/events/plan_executor_test.go | 28 ++-- server/events/project_pre_execute.go | 14 +- server/events/pull_closed_executor.go | 36 ++--- server/events/pull_closed_executor_test.go | 54 ++++---- server/events/run/mocks/mock_runner.go | 12 +- server/events/run/run.go | 12 +- .../terraform/mocks/mock_terraform_client.go | 24 ++-- server/events/terraform/terraform_client.go | 21 +-- server/events/workspace_locker.go | 59 --------- server/events/workspace_locker_test.go | 103 --------------- server/server.go | 6 +- server/server_test.go | 8 +- server/web_templates.go | 8 +- website/src/themes/kube/layouts/index.html | 6 +- 40 files changed, 634 insertions(+), 686 deletions(-) rename server/events/{workspace.go => atlantis_workspace.go} (78%) create mode 100644 server/events/atlantis_workspace_locker.go create mode 100644 server/events/atlantis_workspace_locker_test.go delete mode 100644 server/events/env_lock.go rename server/events/mocks/{mock_workspace.go => mock_atlantis_workspace.go} (51%) create mode 100644 server/events/mocks/mock_atlantis_workspace_locker.go delete mode 100644 server/events/mocks/mock_workspace_locker.go delete mode 100644 server/events/workspace_locker.go delete mode 100644 server/events/workspace_locker_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 200b5e7de..9ecf4a973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) @@ -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)) @@ -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)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b24ba8fb..303e6167e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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` diff --git a/Makefile b/Makefile index 39983371c..a4dc29d28 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/README.md b/README.md index d35537112..f5d7f26e7 100644 --- a/README.md +++ b/README.md @@ -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) @@ -30,7 +30,7 @@ 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) @@ -38,14 +38,14 @@ Read about [Why We Built Atlantis](https://www.atlantis.run/blog/atlantis-releas ## 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 @@ -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 @@ -99,7 +99,7 @@ Atlantis supports several Terraform project structures:    ├── main.tf └── ... ``` -- one folder per environment +- one folder per set of configuration ``` . ├── staging @@ -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 @@ -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. @@ -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. @@ -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)?** diff --git a/e2e/main.go b/e2e/main.go index 8a3d77f78..f817495fe 100644 --- a/e2e/main.go +++ b/e2e/main.go @@ -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 { diff --git a/server/events/apply_executor.go b/server/events/apply_executor.go index 235841f6b..00d5f5d04 100644 --- a/server/events/apply_executor.go +++ b/server/events/apply_executor.go @@ -19,7 +19,7 @@ type ApplyExecutor struct { Terraform *terraform.DefaultClient RequireApproval bool Run *run.Run - Workspace Workspace + Workspace AtlantisWorkspace ProjectPreExecute *DefaultProjectPreExecutor Webhooks webhooks.Sender } @@ -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), @@ -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 { @@ -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, @@ -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")} } diff --git a/server/events/workspace.go b/server/events/atlantis_workspace.go similarity index 78% rename from server/events/workspace.go rename to server/events/atlantis_workspace.go index 644faaa16..650fd8ab3 100644 --- a/server/events/workspace.go +++ b/server/events/atlantis_workspace.go @@ -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 } @@ -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 { @@ -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") } @@ -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) } diff --git a/server/events/atlantis_workspace_locker.go b/server/events/atlantis_workspace_locker.go new file mode 100644 index 000000000..3b6780c4d --- /dev/null +++ b/server/events/atlantis_workspace_locker.go @@ -0,0 +1,61 @@ +package events + +import ( + "fmt" + "sync" +) + +//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_atlantis_workspace_locker.go AtlantisWorkspaceLocker + +// AtlantisWorkspaceLocker is used to prevent multiple commands from executing +// at the same time for a single repo, pull, and workspace. We need to prevent +// this from happening because a specific repo/pull/workspace has a single workspace +// on disk and we haven't written Atlantis (yet) to handle concurrent execution +// within this workspace. +// This locker is called AtlantisWorkspaceLocker to differentiate it from the +// Terraform concept of workspaces, not directories on disk managed by Atlantis. +type AtlantisWorkspaceLocker interface { + // TryLock tries to acquire a lock for this repo, workspace and pull. + TryLock(repoFullName string, workspace string, pullNum int) bool + // Unlock deletes the lock for this repo, workspace and pull. If there was no + // lock it will do nothing. + Unlock(repoFullName, workspace string, pullNum int) +} + +// DefaultAtlantisWorkspaceLocker implements AtlantisWorkspaceLocker. +type DefaultAtlantisWorkspaceLocker struct { + mutex sync.Mutex + locks map[string]interface{} +} + +// NewDefaultAtlantisWorkspaceLocker is a constructor. +func NewDefaultAtlantisWorkspaceLocker() *DefaultAtlantisWorkspaceLocker { + return &DefaultAtlantisWorkspaceLocker{ + locks: make(map[string]interface{}), + } +} + +// TryLock returns true if a lock is acquired for this repo, pull and workspace and +// false otherwise. +func (d *DefaultAtlantisWorkspaceLocker) TryLock(repoFullName string, workspace string, pullNum int) bool { + d.mutex.Lock() + defer d.mutex.Unlock() + + key := d.key(repoFullName, workspace, pullNum) + if _, ok := d.locks[key]; !ok { + d.locks[key] = true + return true + } + return false +} + +// Unlock unlocks the repo, pull and workspace. +func (d *DefaultAtlantisWorkspaceLocker) Unlock(repoFullName, workspace string, pullNum int) { + d.mutex.Lock() + defer d.mutex.Unlock() + delete(d.locks, d.key(repoFullName, workspace, pullNum)) +} + +func (d *DefaultAtlantisWorkspaceLocker) key(repo string, workspace string, pull int) string { + return fmt.Sprintf("%s/%s/%d", repo, workspace, pull) +} diff --git a/server/events/atlantis_workspace_locker_test.go b/server/events/atlantis_workspace_locker_test.go new file mode 100644 index 000000000..04527d744 --- /dev/null +++ b/server/events/atlantis_workspace_locker_test.go @@ -0,0 +1,103 @@ +package events_test + +import ( + "testing" + + "github.com/hootsuite/atlantis/server/events" + . "github.com/hootsuite/atlantis/testing" +) + +var repo = "repo/owner" +var workspace = "default" + +func TestTryLock(t *testing.T) { + locker := events.NewDefaultAtlantisWorkspaceLocker() + + t.Log("the first lock should succeed") + Equals(t, true, locker.TryLock(repo, workspace, 1)) + + t.Log("now another lock for the same repo, workspace, and pull should fail") + Equals(t, false, locker.TryLock(repo, workspace, 1)) +} + +func TestTryLockDifferentWorkspaces(t *testing.T) { + locker := events.NewDefaultAtlantisWorkspaceLocker() + + t.Log("a lock for the same repo and pull but different workspace should succeed") + Equals(t, true, locker.TryLock(repo, workspace, 1)) + Equals(t, true, locker.TryLock(repo, "new-workspace", 1)) + + t.Log("and both should now be locked") + Equals(t, false, locker.TryLock(repo, workspace, 1)) + Equals(t, false, locker.TryLock(repo, "new-workspace", 1)) +} + +func TestTryLockDifferentRepo(t *testing.T) { + locker := events.NewDefaultAtlantisWorkspaceLocker() + + t.Log("a lock for a different repo but the same workspace and pull should succeed") + Equals(t, true, locker.TryLock(repo, workspace, 1)) + newRepo := "owner/newrepo" + Equals(t, true, locker.TryLock(newRepo, workspace, 1)) + + t.Log("and both should now be locked") + Equals(t, false, locker.TryLock(repo, workspace, 1)) + Equals(t, false, locker.TryLock(newRepo, workspace, 1)) +} + +func TestTryLockDifferent1(t *testing.T) { + locker := events.NewDefaultAtlantisWorkspaceLocker() + + t.Log("a lock for a different pull but the same repo and workspace should succeed") + Equals(t, true, locker.TryLock(repo, workspace, 1)) + new1 := 2 + Equals(t, true, locker.TryLock(repo, workspace, new1)) + + t.Log("and both should now be locked") + Equals(t, false, locker.TryLock(repo, workspace, 1)) + Equals(t, false, locker.TryLock(repo, workspace, new1)) +} + +func TestUnlock(t *testing.T) { + locker := events.NewDefaultAtlantisWorkspaceLocker() + + t.Log("unlocking should work") + Equals(t, true, locker.TryLock(repo, workspace, 1)) + locker.Unlock(repo, workspace, 1) + Equals(t, true, locker.TryLock(repo, workspace, 1)) +} + +func TestUnlockDifferentWorkspaces(t *testing.T) { + locker := events.NewDefaultAtlantisWorkspaceLocker() + t.Log("unlocking should work for different workspaces") + Equals(t, true, locker.TryLock(repo, workspace, 1)) + Equals(t, true, locker.TryLock(repo, "new-workspace", 1)) + locker.Unlock(repo, workspace, 1) + locker.Unlock(repo, "new-workspace", 1) + Equals(t, true, locker.TryLock(repo, workspace, 1)) + Equals(t, true, locker.TryLock(repo, "new-workspace", 1)) +} + +func TestUnlockDifferentRepos(t *testing.T) { + locker := events.NewDefaultAtlantisWorkspaceLocker() + t.Log("unlocking should work for different repos") + Equals(t, true, locker.TryLock(repo, workspace, 1)) + newRepo := "owner/newrepo" + Equals(t, true, locker.TryLock(newRepo, workspace, 1)) + locker.Unlock(repo, workspace, 1) + locker.Unlock(newRepo, workspace, 1) + Equals(t, true, locker.TryLock(repo, workspace, 1)) + Equals(t, true, locker.TryLock(newRepo, workspace, 1)) +} + +func TestUnlockDifferentPulls(t *testing.T) { + locker := events.NewDefaultAtlantisWorkspaceLocker() + t.Log("unlocking should work for different 1s") + Equals(t, true, locker.TryLock(repo, workspace, 1)) + new1 := 2 + Equals(t, true, locker.TryLock(repo, workspace, new1)) + locker.Unlock(repo, workspace, 1) + locker.Unlock(repo, workspace, new1) + Equals(t, true, locker.TryLock(repo, workspace, 1)) + Equals(t, true, locker.TryLock(repo, workspace, new1)) +} diff --git a/server/events/command_handler.go b/server/events/command_handler.go index 429553833..d7d1c9b06 100644 --- a/server/events/command_handler.go +++ b/server/events/command_handler.go @@ -49,7 +49,7 @@ type CommandHandler struct { GitlabMergeRequestGetter GitlabMergeRequestGetter CommitStatusUpdater CommitStatusUpdater EventParser EventParsing - WorkspaceLocker WorkspaceLocker + AtlantisWorkspaceLocker AtlantisWorkspaceLocker MarkdownRenderer *MarkdownRenderer Logger logging.SimpleLogging } @@ -130,17 +130,17 @@ func (c *CommandHandler) run(ctx *CommandContext) { } c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, vcs.Pending, ctx.Command, ctx.VCSHost) // nolint: errcheck - if !c.WorkspaceLocker.TryLock(ctx.BaseRepo.FullName, ctx.Command.Environment, ctx.Pull.Num) { + if !c.AtlantisWorkspaceLocker.TryLock(ctx.BaseRepo.FullName, ctx.Command.Workspace, ctx.Pull.Num) { errMsg := fmt.Sprintf( - "The %s environment is currently locked by another"+ + "The %s workspace is currently locked by another"+ " command that is running for this pull request."+ " Wait until the previous command is complete and try again.", - ctx.Command.Environment) + ctx.Command.Workspace) ctx.Log.Warn(errMsg) c.updatePull(ctx, CommandResponse{Failure: errMsg}) return } - defer c.WorkspaceLocker.Unlock(ctx.BaseRepo.FullName, ctx.Command.Environment, ctx.Pull.Num) + defer c.AtlantisWorkspaceLocker.Unlock(ctx.BaseRepo.FullName, ctx.Command.Workspace, ctx.Pull.Num) var cr CommandResponse switch ctx.Command.Name { diff --git a/server/events/command_handler_test.go b/server/events/command_handler_test.go index aa797bb3b..c7a8f93c9 100644 --- a/server/events/command_handler_test.go +++ b/server/events/command_handler_test.go @@ -28,7 +28,7 @@ var vcsClient *vcsmocks.MockClientProxy var ghStatus *mocks.MockCommitStatusUpdater var githubGetter *mocks.MockGithubPullGetter var gitlabGetter *mocks.MockGitlabMergeRequestGetter -var workspaceLocker *mocks.MockWorkspaceLocker +var workspaceLocker *mocks.MockAtlantisWorkspaceLocker var ch events.CommandHandler var logBytes *bytes.Buffer @@ -39,7 +39,7 @@ func setup(t *testing.T) { planner = mocks.NewMockExecutor() eventParsing = mocks.NewMockEventParsing() ghStatus = mocks.NewMockCommitStatusUpdater() - workspaceLocker = mocks.NewMockWorkspaceLocker() + workspaceLocker = mocks.NewMockAtlantisWorkspaceLocker() vcsClient = vcsmocks.NewMockClientProxy() githubGetter = mocks.NewMockGithubPullGetter() gitlabGetter = mocks.NewMockGitlabMergeRequestGetter() @@ -53,7 +53,7 @@ func setup(t *testing.T) { VCSClient: vcsClient, CommitStatusUpdater: ghStatus, EventParser: eventParsing, - WorkspaceLocker: workspaceLocker, + AtlantisWorkspaceLocker: workspaceLocker, MarkdownRenderer: &events.MarkdownRenderer{}, GithubPullGetter: githubGetter, GitlabMergeRequestGetter: gitlabGetter, @@ -128,23 +128,23 @@ func TestExecuteCommand_ClosedPull(t *testing.T) { vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.Repo, modelPull, "Atlantis commands can't be run on closed pull requests", vcs.Github) } -func TestExecuteCommand_EnvLocked(t *testing.T) { - t.Log("if the environment is locked, should comment back on the pull") +func TestExecuteCommand_WorkspaceLocked(t *testing.T) { + t.Log("if the workspace is locked, should comment back on the pull") setup(t) pull := &github.PullRequest{ State: github.String("closed"), } cmd := events.Command{ - Name: events.Plan, - Environment: "env", + Name: events.Plan, + Workspace: "workspace", } When(githubGetter.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(pull, nil) When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.Repo, nil) - When(workspaceLocker.TryLock(fixtures.Repo.FullName, cmd.Environment, fixtures.Pull.Num)).ThenReturn(false) + When(workspaceLocker.TryLock(fixtures.Repo.FullName, cmd.Workspace, fixtures.Pull.Num)).ThenReturn(false) ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, fixtures.Pull.Num, &cmd, vcs.Github) - msg := "The env environment is currently locked by another" + + msg := "The workspace workspace is currently locked by another" + " command that is running for this pull request." + " Wait until the previous command is complete and try again." ghStatus.VerifyWasCalledOnce().Update(fixtures.Repo, fixtures.Pull, vcs.Pending, &cmd, vcs.Github) @@ -163,12 +163,12 @@ func TestExecuteCommand_FullRun(t *testing.T) { for _, c := range []events.CommandName{events.Help, events.Plan, events.Apply} { setup(t) cmd := events.Command{ - Name: c, - Environment: "env", + Name: c, + Workspace: "workspace", } When(githubGetter.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(pull, nil) When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.Repo, nil) - When(workspaceLocker.TryLock(fixtures.Repo.FullName, cmd.Environment, fixtures.Pull.Num)).ThenReturn(true) + When(workspaceLocker.TryLock(fixtures.Repo.FullName, cmd.Workspace, fixtures.Pull.Num)).ThenReturn(true) switch c { case events.Help: When(helper.Execute(matchers.AnyPtrToEventsCommandContext())).ThenReturn(cmdResponse) @@ -184,6 +184,6 @@ func TestExecuteCommand_FullRun(t *testing.T) { _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandResponse()).GetCapturedArguments() Equals(t, cmdResponse, response) vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), AnyString(), matchers.AnyVcsHost()) - workspaceLocker.VerifyWasCalledOnce().Unlock(fixtures.Repo.FullName, cmd.Environment, fixtures.Pull.Num) + workspaceLocker.VerifyWasCalledOnce().Unlock(fixtures.Repo.FullName, cmd.Workspace, fixtures.Pull.Num) } } diff --git a/server/events/env_lock.go b/server/events/env_lock.go deleted file mode 100644 index 7db6a9e9c..000000000 --- a/server/events/env_lock.go +++ /dev/null @@ -1,50 +0,0 @@ -package events - -import ( - "fmt" - "sync" -) - -//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_env_locker.go EnvLocker - -type EnvLocker interface { - TryLock(repoFullName string, env string, pullNum int) bool - Unlock(repoFullName, env string, pullNum int) -} - -// EnvLock is used to prevent multiple runs and commands from occurring at the same time for a single -// repo, pull, and environment -type EnvLock struct { - mutex sync.Mutex - locks map[string]interface{} -} - -func NewEnvLock() *EnvLock { - return &EnvLock{ - locks: make(map[string]interface{}), - } -} - -// TryLock returns true if you acquired the lock and false if someone else already has the lock -func (c *EnvLock) TryLock(repoFullName string, env string, pullNum int) bool { - c.mutex.Lock() - defer c.mutex.Unlock() - - key := c.key(repoFullName, env, pullNum) - if _, ok := c.locks[key]; !ok { - c.locks[key] = true - return true - } - return false -} - -// Unlock unlocks the repo and environment -func (c *EnvLock) Unlock(repoFullName, env string, pullNum int) { - c.mutex.Lock() - defer c.mutex.Unlock() - delete(c.locks, c.key(repoFullName, env, pullNum)) -} - -func (c *EnvLock) key(repo string, env string, pull int) string { - return fmt.Sprintf("%s/%s/%d", repo, env, pull) -} diff --git a/server/events/event_parser.go b/server/events/event_parser.go index 9cf20f3d2..4f15954dc 100644 --- a/server/events/event_parser.go +++ b/server/events/event_parser.go @@ -16,10 +16,10 @@ const gitlabPullOpened = "opened" //go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_event_parsing.go EventParsing type Command struct { - Name CommandName - Environment string - Verbose bool - Flags []string + Name CommandName + Workspace string + Verbose bool + Flags []string } type EventParsing interface { @@ -46,7 +46,7 @@ func (e *EventParser) DetermineCommand(comment string, vcsHost vcs.Host) (*Comma // valid commands contain: // the initial "executable" name, 'run' or 'atlantis' or '@GithubUser' where GithubUser is the api user atlantis is running as // then a command, either 'plan', 'apply', or 'help' - // then an optional environment argument, an optional --verbose flag and any other flags + // then an optional workspace argument, an optional --verbose flag and any other flags // // examples: // atlantis help @@ -60,7 +60,7 @@ func (e *EventParser) DetermineCommand(comment string, vcsHost vcs.Host) (*Comma return nil, err } - env := "default" + workspace := "default" verbose := false var flags []string @@ -82,10 +82,10 @@ func (e *EventParser) DetermineCommand(comment string, vcsHost vcs.Host) (*Comma if len(args) > 2 { flags = args[2:] - // if the third arg doesn't start with '-' then we assume it's an - // environment not a flag + // if the third arg doesn't start with '-' then we assume it's a + // workspace, not a flag if !strings.HasPrefix(args[2], "-") { - env = args[2] + workspace = args[2] flags = args[3:] } @@ -97,7 +97,7 @@ func (e *EventParser) DetermineCommand(comment string, vcsHost vcs.Host) (*Comma } } - c := &Command{Verbose: verbose, Environment: env, Flags: flags} + c := &Command{Verbose: verbose, Workspace: workspace, Flags: flags} switch command { case "plan": c.Name = Plan diff --git a/server/events/event_parser_test.go b/server/events/event_parser_test.go index b489f7298..869e8e242 100644 --- a/server/events/event_parser_test.go +++ b/server/events/event_parser_test.go @@ -65,7 +65,7 @@ func TestDetermineCommandHelp(t *testing.T) { func TestDetermineCommandPermutations(t *testing.T) { execNames := []string{"run", "atlantis", "@github-user", "@gitlab-user"} commandNames := []events.CommandName{events.Plan, events.Apply} - envs := []string{"", "default", "env", "env-dash", "env_underscore", "camelEnv"} + workspaces := []string{"", "default", "workspace", "workspace-dash", "workspace_underscore", "camelWorkspace"} flagCases := [][]string{ {}, {"--verbose"}, @@ -81,12 +81,12 @@ func TestDetermineCommandPermutations(t *testing.T) { // test all permutations for _, exec := range execNames { for _, name := range commandNames { - for _, env := range envs { + for _, workspace := range workspaces { for _, flags := range flagCases { // If github comments end in a newline they get \r\n appended. // Ensure that we parse commands properly either way. for _, lineEnding := range []string{"", "\r\n"} { - comment := strings.Join(append([]string{exec, name.String(), env}, flags...), " ") + lineEnding + comment := strings.Join(append([]string{exec, name.String(), workspace}, flags...), " ") + lineEnding t.Log("testing comment: " + comment) // In order to test gitlab without fully refactoring this test @@ -99,10 +99,10 @@ func TestDetermineCommandPermutations(t *testing.T) { c, err := parser.DetermineCommand(comment, vcsHost) Ok(t, err) Equals(t, name, c.Name) - if env == "" { - Equals(t, "default", c.Environment) + if workspace == "" { + Equals(t, "default", c.Workspace) } else { - Equals(t, env, c.Environment) + Equals(t, workspace, c.Workspace) } Equals(t, containsVerbose(flags), c.Verbose) diff --git a/server/events/locking/boltdb/boltdb.go b/server/events/locking/boltdb/boltdb.go index 959a1e330..c8a1f11ff 100644 --- a/server/events/locking/boltdb/boltdb.go +++ b/server/events/locking/boltdb/boltdb.go @@ -62,7 +62,7 @@ func NewWithDB(db *bolt.DB, bucket string) (*BoltLocker, error) { func (b *BoltLocker) TryLock(newLock models.ProjectLock) (bool, models.ProjectLock, error) { var lockAcquired bool var currLock models.ProjectLock - key := b.key(newLock.Project, newLock.Env) + key := b.key(newLock.Project, newLock.Workspace) newLockSerialized, _ := json.Marshal(newLock) transactionErr := b.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket(b.bucket) @@ -92,14 +92,14 @@ func (b *BoltLocker) TryLock(newLock models.ProjectLock) (bool, models.ProjectLo return lockAcquired, currLock, nil } -// Unlock attempts to unlock the project and environment. +// Unlock attempts to unlock the project and workspace. // If there is no lock, then it will return a nil pointer. // If there is a lock, then it will delete it, and then return a pointer // to the deleted lock. -func (b BoltLocker) Unlock(p models.Project, env string) (*models.ProjectLock, error) { +func (b BoltLocker) Unlock(p models.Project, workspace string) (*models.ProjectLock, error) { var lock models.ProjectLock foundLock := false - key := b.key(p, env) + key := b.key(p, workspace) err := b.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket(b.bucket) serialized := bucket.Get([]byte(key)) @@ -170,17 +170,17 @@ func (b BoltLocker) UnlockByPull(repoFullName string, pullNum int) ([]models.Pro // delete the locks for _, lock := range locks { - if _, err = b.Unlock(lock.Project, lock.Env); err != nil { - return locks, errors.Wrapf(err, "unlocking repo %s, path %s, env %s", lock.Project.RepoFullName, lock.Project.Path, lock.Env) + if _, err = b.Unlock(lock.Project, lock.Workspace); err != nil { + return locks, errors.Wrapf(err, "unlocking repo %s, path %s, workspace %s", lock.Project.RepoFullName, lock.Project.Path, lock.Workspace) } } return locks, nil } -// GetLock returns a pointer to the lock for that project and env. +// GetLock returns a pointer to the lock for that project and workspace. // If there is no lock, it returns a nil pointer. -func (b BoltLocker) GetLock(p models.Project, env string) (*models.ProjectLock, error) { - key := b.key(p, env) +func (b BoltLocker) GetLock(p models.Project, workspace string) (*models.ProjectLock, error) { + key := b.key(p, workspace) var lockBytes []byte err := b.db.View(func(tx *bolt.Tx) error { b := tx.Bucket(b.bucket) @@ -205,6 +205,6 @@ func (b BoltLocker) GetLock(p models.Project, env string) (*models.ProjectLock, return &lock, nil } -func (b BoltLocker) key(p models.Project, env string) string { - return fmt.Sprintf("%s/%s/%s", p.RepoFullName, p.Path, env) +func (b BoltLocker) key(p models.Project, workspace string) string { + return fmt.Sprintf("%s/%s/%s", p.RepoFullName, p.Path, workspace) } diff --git a/server/events/locking/boltdb/boltdb_test.go b/server/events/locking/boltdb/boltdb_test.go index 2434d3d8e..f89e397bc 100644 --- a/server/events/locking/boltdb/boltdb_test.go +++ b/server/events/locking/boltdb/boltdb_test.go @@ -15,7 +15,7 @@ import ( var lockBucket = "bucket" var project = models.NewProject("owner/repo", "parent/child") -var env = "default" +var workspace = "default" var pullNum = 1 var lock = models.ProjectLock{ Pull: models.PullRequest{ @@ -24,9 +24,9 @@ var lock = models.ProjectLock{ User: models.User{ Username: "lkysow", }, - Env: env, - Project: project, - Time: time.Now(), + Workspace: workspace, + Project: project, + Time: time.Now(), } func TestListNoLocks(t *testing.T) { @@ -88,7 +88,7 @@ func TestListAddRemove(t *testing.T) { defer cleanupDB(db) _, _, err := b.TryLock(lock) Ok(t, err) - _, err = b.Unlock(project, env) + _, err = b.Unlock(project, workspace) Ok(t, err) ls, err := b.List() @@ -123,10 +123,10 @@ func TestLockingExistingLock(t *testing.T) { Equals(t, pullNum, currLock.Pull.Num) } - t.Log("...succeed if the new project has a different environment") + t.Log("...succeed if the new project has a different workspace") { newLock := lock - newLock.Env = "different-env" + newLock.Workspace = "different-workspace" acquired, currLock, err := b.TryLock(newLock) Ok(t, err) Equals(t, true, acquired) @@ -158,7 +158,7 @@ func TestUnlockingNoLocks(t *testing.T) { t.Log("unlocking with no locks should succeed") db, b := newTestDB() defer cleanupDB(db) - _, err := b.Unlock(project, env) + _, err := b.Unlock(project, workspace) Ok(t, err) } @@ -170,7 +170,7 @@ func TestUnlocking(t *testing.T) { _, _, err := b.TryLock(lock) Ok(t, err) - _, err = b.Unlock(project, env) + _, err = b.Unlock(project, workspace) Ok(t, err) // should be no locks listed @@ -206,18 +206,18 @@ func TestUnlockingMultiple(t *testing.T) { Ok(t, err) new3 := lock - new3.Env = "new-env" + new3.Workspace = "new-workspace" _, _, err = b.TryLock(new3) Ok(t, err) // now try and unlock them - _, err = b.Unlock(new3.Project, new3.Env) + _, err = b.Unlock(new3.Project, new3.Workspace) Ok(t, err) - _, err = b.Unlock(new2.Project, env) + _, err = b.Unlock(new2.Project, workspace) Ok(t, err) - _, err = b.Unlock(new.Project, env) + _, err = b.Unlock(new.Project, workspace) Ok(t, err) - _, err = b.Unlock(project, env) + _, err = b.Unlock(project, workspace) Ok(t, err) // should be none left @@ -274,7 +274,7 @@ func TestUnlockByPullAfterUnlock(t *testing.T) { defer cleanupDB(db) _, _, err := b.TryLock(lock) Ok(t, err) - _, err = b.Unlock(project, env) + _, err = b.Unlock(project, workspace) Ok(t, err) _, err = b.UnlockByPull(project.RepoFullName, pullNum) @@ -291,13 +291,13 @@ func TestUnlockByPullMatching(t *testing.T) { _, _, err := b.TryLock(lock) Ok(t, err) - // add additional locks with the same repo and pull num but different paths/envs + // add additional locks with the same repo and pull num but different paths/workspaces new := lock new.Project.Path = "dif/path" _, _, err = b.TryLock(new) Ok(t, err) new2 := lock - new2.Env = "new-env" + new2.Workspace = "new-workspace" _, _, err = b.TryLock(new2) Ok(t, err) @@ -318,7 +318,7 @@ func TestGetLockNotThere(t *testing.T) { t.Log("getting a lock that doesn't exist should return a nil pointer") db, b := newTestDB() defer cleanupDB(db) - l, err := b.GetLock(project, env) + l, err := b.GetLock(project, workspace) Ok(t, err) Equals(t, (*models.ProjectLock)(nil), l) } @@ -330,11 +330,11 @@ func TestGetLock(t *testing.T) { _, _, err := b.TryLock(lock) Ok(t, err) - l, err := b.GetLock(project, env) + l, err := b.GetLock(project, workspace) Ok(t, err) // can't compare against time so doing each field Equals(t, lock.Project, l.Project) - Equals(t, lock.Env, l.Env) + Equals(t, lock.Workspace, l.Workspace) Equals(t, lock.Pull, l.Pull) Equals(t, lock.User, l.User) } diff --git a/server/events/locking/locking.go b/server/events/locking/locking.go index 98d0d596d..4f1088848 100644 --- a/server/events/locking/locking.go +++ b/server/events/locking/locking.go @@ -15,9 +15,9 @@ import ( // Backend is an implementation of the locking API we require. type Backend interface { TryLock(lock models.ProjectLock) (bool, models.ProjectLock, error) - Unlock(project models.Project, env string) (*models.ProjectLock, error) + Unlock(project models.Project, workspace string) (*models.ProjectLock, error) List() ([]models.ProjectLock, error) - GetLock(project models.Project, env string) (*models.ProjectLock, error) + GetLock(project models.Project, workspace string) (*models.ProjectLock, error) UnlockByPull(repoFullName string, pullNum int) ([]models.ProjectLock, error) } @@ -39,7 +39,7 @@ type Client struct { //go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_locker.go Locker type Locker interface { - TryLock(p models.Project, env string, pull models.PullRequest, user models.User) (TryLockResponse, error) + TryLock(p models.Project, workspace string, pull models.PullRequest, user models.User) (TryLockResponse, error) Unlock(key string) (*models.ProjectLock, error) List() (map[string]models.ProjectLock, error) UnlockByPull(repoFullName string, pullNum int) ([]models.ProjectLock, error) @@ -53,35 +53,35 @@ func NewClient(backend Backend) *Client { } } -// keyRegex matches and captures {repoFullName}/{path}/{env} where path can have multiple /'s in it. +// keyRegex matches and captures {repoFullName}/{path}/{workspace} where path can have multiple /'s in it. var keyRegex = regexp.MustCompile(`^(.*?\/.*?)\/(.*)\/(.*)$`) -// TryLock attempts to acquire a lock to a project and environment. -func (c *Client) TryLock(p models.Project, env string, pull models.PullRequest, user models.User) (TryLockResponse, error) { +// TryLock attempts to acquire a lock to a project and workspace. +func (c *Client) TryLock(p models.Project, workspace string, pull models.PullRequest, user models.User) (TryLockResponse, error) { lock := models.ProjectLock{ - Env: env, - Time: time.Now().Local(), - Project: p, - User: user, - Pull: pull, + Workspace: workspace, + Time: time.Now().Local(), + Project: p, + User: user, + Pull: pull, } lockAcquired, currLock, err := c.backend.TryLock(lock) if err != nil { return TryLockResponse{}, err } - return TryLockResponse{lockAcquired, currLock, c.key(p, env)}, nil + return TryLockResponse{lockAcquired, currLock, c.key(p, workspace)}, nil } -// Unlock attempts to unlock a project and environment. If successful, +// Unlock attempts to unlock a project and workspace. If successful, // a pointer to the now deleted lock will be returned. Else, that // pointer will be nil. An error will only be returned if there was // an error deleting the lock (i.e. not if there was no lock). func (c *Client) Unlock(key string) (*models.ProjectLock, error) { - project, env, err := c.lockKeyToProjectEnv(key) + project, workspace, err := c.lockKeyToProjectWorkspace(key) if err != nil { return nil, err } - return c.backend.Unlock(project, env) + return c.backend.Unlock(project, workspace) } // List returns a map of all locks with their lock key as the map key. @@ -93,7 +93,7 @@ func (c *Client) List() (map[string]models.ProjectLock, error) { return m, err } for _, lock := range locks { - m[c.key(lock.Project, lock.Env)] = lock + m[c.key(lock.Project, lock.Workspace)] = lock } return m, nil } @@ -108,12 +108,12 @@ func (c *Client) UnlockByPull(repoFullName string, pullNum int) ([]models.Projec // An error will only be returned if there was an error getting the lock // (i.e. not if there was no lock). func (c *Client) GetLock(key string) (*models.ProjectLock, error) { - project, env, err := c.lockKeyToProjectEnv(key) + project, workspace, err := c.lockKeyToProjectWorkspace(key) if err != nil { return nil, err } - projectLock, err := c.backend.GetLock(project, env) + projectLock, err := c.backend.GetLock(project, workspace) if err != nil { return nil, err } @@ -121,11 +121,11 @@ func (c *Client) GetLock(key string) (*models.ProjectLock, error) { return projectLock, nil } -func (c *Client) key(p models.Project, env string) string { - return fmt.Sprintf("%s/%s/%s", p.RepoFullName, p.Path, env) +func (c *Client) key(p models.Project, workspace string) string { + return fmt.Sprintf("%s/%s/%s", p.RepoFullName, p.Path, workspace) } -func (c *Client) lockKeyToProjectEnv(key string) (models.Project, string, error) { +func (c *Client) lockKeyToProjectWorkspace(key string) (models.Project, string, error) { matches := keyRegex.FindStringSubmatch(key) if len(matches) != 4 { return models.Project{}, "", errors.New("invalid key format") diff --git a/server/events/locking/locking_test.go b/server/events/locking/locking_test.go index 023a2c1b1..148758137 100644 --- a/server/events/locking/locking_test.go +++ b/server/events/locking/locking_test.go @@ -16,12 +16,12 @@ import ( ) var project = models.NewProject("owner/repo", "path") -var env = "env" +var workspace = "workspace" var pull = models.PullRequest{} var user = models.User{} var expectedErr = errors.New("err") var timeNow = time.Now().Local() -var pl = models.ProjectLock{Project: project, Pull: pull, User: user, Env: env, Time: timeNow} +var pl = models.ProjectLock{Project: project, Pull: pull, User: user, Workspace: workspace, Time: timeNow} func TestTryLock_Err(t *testing.T) { RegisterMockTestingT(t) @@ -29,7 +29,7 @@ func TestTryLock_Err(t *testing.T) { When(backend.TryLock(matchers.AnyModelsProjectLock())).ThenReturn(false, models.ProjectLock{}, expectedErr) t.Log("when the backend returns an error, TryLock should return that error") l := locking.NewClient(backend) - _, err := l.TryLock(project, env, pull, user) + _, err := l.TryLock(project, workspace, pull, user) Equals(t, err, err) } @@ -39,9 +39,9 @@ func TestTryLock_Success(t *testing.T) { backend := mocks.NewMockBackend() When(backend.TryLock(matchers.AnyModelsProjectLock())).ThenReturn(true, currLock, nil) l := locking.NewClient(backend) - r, err := l.TryLock(project, env, pull, user) + r, err := l.TryLock(project, workspace, pull, user) Ok(t, err) - Equals(t, locking.TryLockResponse{LockAcquired: true, CurrLock: currLock, LockKey: "owner/repo/path/env"}, r) + Equals(t, locking.TryLockResponse{LockAcquired: true, CurrLock: currLock, LockKey: "owner/repo/path/workspace"}, r) } func TestUnlock_InvalidKey(t *testing.T) { @@ -59,9 +59,9 @@ func TestUnlock_Err(t *testing.T) { backend := mocks.NewMockBackend() When(backend.Unlock(matchers.AnyModelsProject(), AnyString())).ThenReturn(nil, expectedErr) l := locking.NewClient(backend) - _, err := l.Unlock("owner/repo/path/env") + _, err := l.Unlock("owner/repo/path/workspace") Equals(t, err, err) - backend.VerifyWasCalledOnce().Unlock(project, "env") + backend.VerifyWasCalledOnce().Unlock(project, "workspace") } func TestUnlock(t *testing.T) { @@ -69,7 +69,7 @@ func TestUnlock(t *testing.T) { backend := mocks.NewMockBackend() When(backend.Unlock(matchers.AnyModelsProject(), AnyString())).ThenReturn(&pl, nil) l := locking.NewClient(backend) - lock, err := l.Unlock("owner/repo/path/env") + lock, err := l.Unlock("owner/repo/path/workspace") Ok(t, err) Equals(t, &pl, lock) } @@ -91,7 +91,7 @@ func TestList(t *testing.T) { list, err := l.List() Ok(t, err) Equals(t, map[string]models.ProjectLock{ - "owner/repo/path/env": pl, + "owner/repo/path/workspace": pl, }, list) } @@ -116,18 +116,18 @@ func TestGetLock_BadKey(t *testing.T) { func TestGetLock_Err(t *testing.T) { RegisterMockTestingT(t) backend := mocks.NewMockBackend() - When(backend.GetLock(project, env)).ThenReturn(nil, expectedErr) + When(backend.GetLock(project, workspace)).ThenReturn(nil, expectedErr) l := locking.NewClient(backend) - _, err := l.GetLock("owner/repo/path/env") + _, err := l.GetLock("owner/repo/path/workspace") Equals(t, expectedErr, err) } func TestGetLock(t *testing.T) { RegisterMockTestingT(t) backend := mocks.NewMockBackend() - When(backend.GetLock(project, env)).ThenReturn(&pl, nil) + When(backend.GetLock(project, workspace)).ThenReturn(&pl, nil) l := locking.NewClient(backend) - lock, err := l.GetLock("owner/repo/path/env") + lock, err := l.GetLock("owner/repo/path/workspace") Ok(t, err) Equals(t, &pl, lock) } diff --git a/server/events/locking/mocks/mock_backend.go b/server/events/locking/mocks/mock_backend.go index 4006813bc..2f5494f4f 100644 --- a/server/events/locking/mocks/mock_backend.go +++ b/server/events/locking/mocks/mock_backend.go @@ -38,8 +38,8 @@ func (mock *MockBackend) TryLock(lock models.ProjectLock) (bool, models.ProjectL return ret0, ret1, ret2 } -func (mock *MockBackend) Unlock(project models.Project, env string) (*models.ProjectLock, error) { - params := []pegomock.Param{project, env} +func (mock *MockBackend) Unlock(project models.Project, workspace string) (*models.ProjectLock, error) { + params := []pegomock.Param{project, workspace} result := pegomock.GetGenericMockFrom(mock).Invoke("Unlock", params, []reflect.Type{reflect.TypeOf((**models.ProjectLock)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) var ret0 *models.ProjectLock var ret1 error @@ -70,8 +70,8 @@ func (mock *MockBackend) List() ([]models.ProjectLock, error) { return ret0, ret1 } -func (mock *MockBackend) GetLock(project models.Project, env string) (*models.ProjectLock, error) { - params := []pegomock.Param{project, env} +func (mock *MockBackend) GetLock(project models.Project, workspace string) (*models.ProjectLock, error) { + params := []pegomock.Param{project, workspace} result := pegomock.GetGenericMockFrom(mock).Invoke("GetLock", params, []reflect.Type{reflect.TypeOf((**models.ProjectLock)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) var ret0 *models.ProjectLock var ret1 error @@ -147,8 +147,8 @@ func (c *Backend_TryLock_OngoingVerification) GetAllCapturedArguments() (_param0 return } -func (verifier *VerifierBackend) Unlock(project models.Project, env string) *Backend_Unlock_OngoingVerification { - params := []pegomock.Param{project, env} +func (verifier *VerifierBackend) Unlock(project models.Project, workspace string) *Backend_Unlock_OngoingVerification { + params := []pegomock.Param{project, workspace} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Unlock", params) return &Backend_Unlock_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -159,8 +159,8 @@ type Backend_Unlock_OngoingVerification struct { } func (c *Backend_Unlock_OngoingVerification) GetCapturedArguments() (models.Project, string) { - project, env := c.GetAllCapturedArguments() - return project[len(project)-1], env[len(env)-1] + project, workspace := c.GetAllCapturedArguments() + return project[len(project)-1], workspace[len(workspace)-1] } func (c *Backend_Unlock_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Project, _param1 []string) { @@ -195,8 +195,8 @@ func (c *Backend_List_OngoingVerification) GetCapturedArguments() { func (c *Backend_List_OngoingVerification) GetAllCapturedArguments() { } -func (verifier *VerifierBackend) GetLock(project models.Project, env string) *Backend_GetLock_OngoingVerification { - params := []pegomock.Param{project, env} +func (verifier *VerifierBackend) GetLock(project models.Project, workspace string) *Backend_GetLock_OngoingVerification { + params := []pegomock.Param{project, workspace} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetLock", params) return &Backend_GetLock_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -207,8 +207,8 @@ type Backend_GetLock_OngoingVerification struct { } func (c *Backend_GetLock_OngoingVerification) GetCapturedArguments() (models.Project, string) { - project, env := c.GetAllCapturedArguments() - return project[len(project)-1], env[len(env)-1] + project, workspace := c.GetAllCapturedArguments() + return project[len(project)-1], workspace[len(workspace)-1] } func (c *Backend_GetLock_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Project, _param1 []string) { diff --git a/server/events/locking/mocks/mock_locker.go b/server/events/locking/mocks/mock_locker.go index 02dfc26cf..bff1311e7 100644 --- a/server/events/locking/mocks/mock_locker.go +++ b/server/events/locking/mocks/mock_locker.go @@ -19,8 +19,8 @@ func NewMockLocker() *MockLocker { return &MockLocker{fail: pegomock.GlobalFailHandler} } -func (mock *MockLocker) TryLock(p models.Project, env string, pull models.PullRequest, user models.User) (locking.TryLockResponse, error) { - params := []pegomock.Param{p, env, pull, user} +func (mock *MockLocker) TryLock(p models.Project, workspace string, pull models.PullRequest, user models.User) (locking.TryLockResponse, error) { + params := []pegomock.Param{p, workspace, pull, user} result := pegomock.GetGenericMockFrom(mock).Invoke("TryLock", params, []reflect.Type{reflect.TypeOf((*locking.TryLockResponse)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) var ret0 locking.TryLockResponse var ret1 error @@ -117,8 +117,8 @@ type VerifierLocker struct { inOrderContext *pegomock.InOrderContext } -func (verifier *VerifierLocker) TryLock(p models.Project, env string, pull models.PullRequest, user models.User) *Locker_TryLock_OngoingVerification { - params := []pegomock.Param{p, env, pull, user} +func (verifier *VerifierLocker) TryLock(p models.Project, workspace string, pull models.PullRequest, user models.User) *Locker_TryLock_OngoingVerification { + params := []pegomock.Param{p, workspace, pull, user} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "TryLock", params) return &Locker_TryLock_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -129,8 +129,8 @@ type Locker_TryLock_OngoingVerification struct { } func (c *Locker_TryLock_OngoingVerification) GetCapturedArguments() (models.Project, string, models.PullRequest, models.User) { - p, env, pull, user := c.GetAllCapturedArguments() - return p[len(p)-1], env[len(env)-1], pull[len(pull)-1], user[len(user)-1] + p, workspace, pull, user := c.GetAllCapturedArguments() + return p[len(p)-1], workspace[len(workspace)-1], pull[len(pull)-1], user[len(user)-1] } func (c *Locker_TryLock_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Project, _param1 []string, _param2 []models.PullRequest, _param3 []models.User) { diff --git a/server/events/markdown_renderer.go b/server/events/markdown_renderer.go index ab3ae792d..184106860 100644 --- a/server/events/markdown_renderer.go +++ b/server/events/markdown_renderer.go @@ -101,7 +101,7 @@ var helpTmpl = template.Must(template.New("").Parse("```cmake\n" + `atlantis - Terraform collaboration tool that enables you to collaborate on infrastructure safely and securely. -Usage: atlantis [environment] [--verbose] +Usage: atlantis [workspace] [--verbose] Commands: plan Runs 'terraform plan' on the files changed in the pull request @@ -110,13 +110,13 @@ help Get help Examples: -# Generates a plan for staging environment +# Generates a plan for staging workspace atlantis plan staging # Generates a plan for a standalone terraform project atlantis plan -# Applies a plan for staging environment +# Applies a plan for staging workspace atlantis apply staging # Applies a plan for a standalone terraform project diff --git a/server/events/mocks/mock_workspace.go b/server/events/mocks/mock_atlantis_workspace.go similarity index 51% rename from server/events/mocks/mock_workspace.go rename to server/events/mocks/mock_atlantis_workspace.go index da54ac50f..8d79994e5 100644 --- a/server/events/mocks/mock_workspace.go +++ b/server/events/mocks/mock_atlantis_workspace.go @@ -1,5 +1,5 @@ // Automatically generated by pegomock. DO NOT EDIT! -// Source: github.com/hootsuite/atlantis/server/events (interfaces: Workspace) +// Source: github.com/hootsuite/atlantis/server/events (interfaces: AtlantisWorkspace) package mocks @@ -11,16 +11,16 @@ import ( pegomock "github.com/petergtz/pegomock" ) -type MockWorkspace struct { +type MockAtlantisWorkspace struct { fail func(message string, callerSkip ...int) } -func NewMockWorkspace() *MockWorkspace { - return &MockWorkspace{fail: pegomock.GlobalFailHandler} +func NewMockAtlantisWorkspace() *MockAtlantisWorkspace { + return &MockAtlantisWorkspace{fail: pegomock.GlobalFailHandler} } -func (mock *MockWorkspace) Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, env string) (string, error) { - params := []pegomock.Param{log, baseRepo, headRepo, p, env} +func (mock *MockAtlantisWorkspace) Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, workspace string) (string, error) { + params := []pegomock.Param{log, baseRepo, headRepo, p, workspace} result := pegomock.GetGenericMockFrom(mock).Invoke("Clone", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) var ret0 string var ret1 error @@ -35,8 +35,8 @@ func (mock *MockWorkspace) Clone(log *logging.SimpleLogger, baseRepo models.Repo return ret0, ret1 } -func (mock *MockWorkspace) GetWorkspace(r models.Repo, p models.PullRequest, env string) (string, error) { - params := []pegomock.Param{r, p, env} +func (mock *MockAtlantisWorkspace) GetWorkspace(r models.Repo, p models.PullRequest, workspace string) (string, error) { + params := []pegomock.Param{r, p, workspace} result := pegomock.GetGenericMockFrom(mock).Invoke("GetWorkspace", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) var ret0 string var ret1 error @@ -51,7 +51,7 @@ func (mock *MockWorkspace) GetWorkspace(r models.Repo, p models.PullRequest, env return ret0, ret1 } -func (mock *MockWorkspace) Delete(r models.Repo, p models.PullRequest) error { +func (mock *MockAtlantisWorkspace) Delete(r models.Repo, p models.PullRequest) error { params := []pegomock.Param{r, p} result := pegomock.GetGenericMockFrom(mock).Invoke("Delete", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) var ret0 error @@ -63,41 +63,41 @@ func (mock *MockWorkspace) Delete(r models.Repo, p models.PullRequest) error { return ret0 } -func (mock *MockWorkspace) VerifyWasCalledOnce() *VerifierWorkspace { - return &VerifierWorkspace{mock, pegomock.Times(1), nil} +func (mock *MockAtlantisWorkspace) VerifyWasCalledOnce() *VerifierAtlantisWorkspace { + return &VerifierAtlantisWorkspace{mock, pegomock.Times(1), nil} } -func (mock *MockWorkspace) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierWorkspace { - return &VerifierWorkspace{mock, invocationCountMatcher, nil} +func (mock *MockAtlantisWorkspace) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierAtlantisWorkspace { + return &VerifierAtlantisWorkspace{mock, invocationCountMatcher, nil} } -func (mock *MockWorkspace) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierWorkspace { - return &VerifierWorkspace{mock, invocationCountMatcher, inOrderContext} +func (mock *MockAtlantisWorkspace) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierAtlantisWorkspace { + return &VerifierAtlantisWorkspace{mock, invocationCountMatcher, inOrderContext} } -type VerifierWorkspace struct { - mock *MockWorkspace +type VerifierAtlantisWorkspace struct { + mock *MockAtlantisWorkspace invocationCountMatcher pegomock.Matcher inOrderContext *pegomock.InOrderContext } -func (verifier *VerifierWorkspace) Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, env string) *Workspace_Clone_OngoingVerification { - params := []pegomock.Param{log, baseRepo, headRepo, p, env} +func (verifier *VerifierAtlantisWorkspace) Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, workspace string) *AtlantisWorkspace_Clone_OngoingVerification { + params := []pegomock.Param{log, baseRepo, headRepo, p, workspace} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Clone", params) - return &Workspace_Clone_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} + return &AtlantisWorkspace_Clone_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } -type Workspace_Clone_OngoingVerification struct { - mock *MockWorkspace +type AtlantisWorkspace_Clone_OngoingVerification struct { + mock *MockAtlantisWorkspace methodInvocations []pegomock.MethodInvocation } -func (c *Workspace_Clone_OngoingVerification) GetCapturedArguments() (*logging.SimpleLogger, models.Repo, models.Repo, models.PullRequest, string) { - log, baseRepo, headRepo, p, env := c.GetAllCapturedArguments() - return log[len(log)-1], baseRepo[len(baseRepo)-1], headRepo[len(headRepo)-1], p[len(p)-1], env[len(env)-1] +func (c *AtlantisWorkspace_Clone_OngoingVerification) GetCapturedArguments() (*logging.SimpleLogger, models.Repo, models.Repo, models.PullRequest, string) { + log, baseRepo, headRepo, p, workspace := c.GetAllCapturedArguments() + return log[len(log)-1], baseRepo[len(baseRepo)-1], headRepo[len(headRepo)-1], p[len(p)-1], workspace[len(workspace)-1] } -func (c *Workspace_Clone_OngoingVerification) GetAllCapturedArguments() (_param0 []*logging.SimpleLogger, _param1 []models.Repo, _param2 []models.Repo, _param3 []models.PullRequest, _param4 []string) { +func (c *AtlantisWorkspace_Clone_OngoingVerification) GetAllCapturedArguments() (_param0 []*logging.SimpleLogger, _param1 []models.Repo, _param2 []models.Repo, _param3 []models.PullRequest, _param4 []string) { params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) if len(params) > 0 { _param0 = make([]*logging.SimpleLogger, len(params[0])) @@ -124,23 +124,23 @@ func (c *Workspace_Clone_OngoingVerification) GetAllCapturedArguments() (_param0 return } -func (verifier *VerifierWorkspace) GetWorkspace(r models.Repo, p models.PullRequest, env string) *Workspace_GetWorkspace_OngoingVerification { - params := []pegomock.Param{r, p, env} +func (verifier *VerifierAtlantisWorkspace) GetWorkspace(r models.Repo, p models.PullRequest, workspace string) *AtlantisWorkspace_GetWorkspace_OngoingVerification { + params := []pegomock.Param{r, p, workspace} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetWorkspace", params) - return &Workspace_GetWorkspace_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} + return &AtlantisWorkspace_GetWorkspace_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } -type Workspace_GetWorkspace_OngoingVerification struct { - mock *MockWorkspace +type AtlantisWorkspace_GetWorkspace_OngoingVerification struct { + mock *MockAtlantisWorkspace methodInvocations []pegomock.MethodInvocation } -func (c *Workspace_GetWorkspace_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, string) { - r, p, env := c.GetAllCapturedArguments() - return r[len(r)-1], p[len(p)-1], env[len(env)-1] +func (c *AtlantisWorkspace_GetWorkspace_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, string) { + r, p, workspace := c.GetAllCapturedArguments() + return r[len(r)-1], p[len(p)-1], workspace[len(workspace)-1] } -func (c *Workspace_GetWorkspace_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []string) { +func (c *AtlantisWorkspace_GetWorkspace_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []string) { params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) if len(params) > 0 { _param0 = make([]models.Repo, len(params[0])) @@ -159,23 +159,23 @@ func (c *Workspace_GetWorkspace_OngoingVerification) GetAllCapturedArguments() ( return } -func (verifier *VerifierWorkspace) Delete(r models.Repo, p models.PullRequest) *Workspace_Delete_OngoingVerification { +func (verifier *VerifierAtlantisWorkspace) Delete(r models.Repo, p models.PullRequest) *AtlantisWorkspace_Delete_OngoingVerification { params := []pegomock.Param{r, p} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Delete", params) - return &Workspace_Delete_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} + return &AtlantisWorkspace_Delete_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } -type Workspace_Delete_OngoingVerification struct { - mock *MockWorkspace +type AtlantisWorkspace_Delete_OngoingVerification struct { + mock *MockAtlantisWorkspace methodInvocations []pegomock.MethodInvocation } -func (c *Workspace_Delete_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest) { +func (c *AtlantisWorkspace_Delete_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest) { r, p := c.GetAllCapturedArguments() return r[len(r)-1], p[len(p)-1] } -func (c *Workspace_Delete_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest) { +func (c *AtlantisWorkspace_Delete_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest) { params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) if len(params) > 0 { _param0 = make([]models.Repo, len(params[0])) diff --git a/server/events/mocks/mock_atlantis_workspace_locker.go b/server/events/mocks/mock_atlantis_workspace_locker.go new file mode 100644 index 000000000..4549a2b65 --- /dev/null +++ b/server/events/mocks/mock_atlantis_workspace_locker.go @@ -0,0 +1,123 @@ +// Automatically generated by pegomock. DO NOT EDIT! +// Source: github.com/hootsuite/atlantis/server/events (interfaces: AtlantisWorkspaceLocker) + +package mocks + +import ( + "reflect" + + pegomock "github.com/petergtz/pegomock" +) + +type MockAtlantisWorkspaceLocker struct { + fail func(message string, callerSkip ...int) +} + +func NewMockAtlantisWorkspaceLocker() *MockAtlantisWorkspaceLocker { + return &MockAtlantisWorkspaceLocker{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockAtlantisWorkspaceLocker) TryLock(repoFullName string, workspace string, pullNum int) bool { + params := []pegomock.Param{repoFullName, workspace, pullNum} + result := pegomock.GetGenericMockFrom(mock).Invoke("TryLock", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) + var ret0 bool + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(bool) + } + } + return ret0 +} + +func (mock *MockAtlantisWorkspaceLocker) Unlock(repoFullName string, workspace string, pullNum int) { + params := []pegomock.Param{repoFullName, workspace, pullNum} + pegomock.GetGenericMockFrom(mock).Invoke("Unlock", params, []reflect.Type{}) +} + +func (mock *MockAtlantisWorkspaceLocker) VerifyWasCalledOnce() *VerifierAtlantisWorkspaceLocker { + return &VerifierAtlantisWorkspaceLocker{mock, pegomock.Times(1), nil} +} + +func (mock *MockAtlantisWorkspaceLocker) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierAtlantisWorkspaceLocker { + return &VerifierAtlantisWorkspaceLocker{mock, invocationCountMatcher, nil} +} + +func (mock *MockAtlantisWorkspaceLocker) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierAtlantisWorkspaceLocker { + return &VerifierAtlantisWorkspaceLocker{mock, invocationCountMatcher, inOrderContext} +} + +type VerifierAtlantisWorkspaceLocker struct { + mock *MockAtlantisWorkspaceLocker + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext +} + +func (verifier *VerifierAtlantisWorkspaceLocker) TryLock(repoFullName string, workspace string, pullNum int) *AtlantisWorkspaceLocker_TryLock_OngoingVerification { + params := []pegomock.Param{repoFullName, workspace, pullNum} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "TryLock", params) + return &AtlantisWorkspaceLocker_TryLock_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type AtlantisWorkspaceLocker_TryLock_OngoingVerification struct { + mock *MockAtlantisWorkspaceLocker + methodInvocations []pegomock.MethodInvocation +} + +func (c *AtlantisWorkspaceLocker_TryLock_OngoingVerification) GetCapturedArguments() (string, string, int) { + repoFullName, workspace, pullNum := c.GetAllCapturedArguments() + return repoFullName[len(repoFullName)-1], workspace[len(workspace)-1], pullNum[len(pullNum)-1] +} + +func (c *AtlantisWorkspaceLocker_TryLock_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 []int) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(string) + } + _param1 = make([]string, len(params[1])) + for u, param := range params[1] { + _param1[u] = param.(string) + } + _param2 = make([]int, len(params[2])) + for u, param := range params[2] { + _param2[u] = param.(int) + } + } + return +} + +func (verifier *VerifierAtlantisWorkspaceLocker) Unlock(repoFullName string, workspace string, pullNum int) *AtlantisWorkspaceLocker_Unlock_OngoingVerification { + params := []pegomock.Param{repoFullName, workspace, pullNum} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Unlock", params) + return &AtlantisWorkspaceLocker_Unlock_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type AtlantisWorkspaceLocker_Unlock_OngoingVerification struct { + mock *MockAtlantisWorkspaceLocker + methodInvocations []pegomock.MethodInvocation +} + +func (c *AtlantisWorkspaceLocker_Unlock_OngoingVerification) GetCapturedArguments() (string, string, int) { + repoFullName, workspace, pullNum := c.GetAllCapturedArguments() + return repoFullName[len(repoFullName)-1], workspace[len(workspace)-1], pullNum[len(pullNum)-1] +} + +func (c *AtlantisWorkspaceLocker_Unlock_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 []int) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(string) + } + _param1 = make([]string, len(params[1])) + for u, param := range params[1] { + _param1[u] = param.(string) + } + _param2 = make([]int, len(params[2])) + for u, param := range params[2] { + _param2[u] = param.(int) + } + } + return +} diff --git a/server/events/mocks/mock_workspace_locker.go b/server/events/mocks/mock_workspace_locker.go deleted file mode 100644 index c2800f35c..000000000 --- a/server/events/mocks/mock_workspace_locker.go +++ /dev/null @@ -1,123 +0,0 @@ -// Automatically generated by pegomock. DO NOT EDIT! -// Source: github.com/hootsuite/atlantis/server/events (interfaces: WorkspaceLocker) - -package mocks - -import ( - "reflect" - - pegomock "github.com/petergtz/pegomock" -) - -type MockWorkspaceLocker struct { - fail func(message string, callerSkip ...int) -} - -func NewMockWorkspaceLocker() *MockWorkspaceLocker { - return &MockWorkspaceLocker{fail: pegomock.GlobalFailHandler} -} - -func (mock *MockWorkspaceLocker) TryLock(repoFullName string, env string, pullNum int) bool { - params := []pegomock.Param{repoFullName, env, pullNum} - result := pegomock.GetGenericMockFrom(mock).Invoke("TryLock", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockWorkspaceLocker) Unlock(repoFullName string, env string, pullNum int) { - params := []pegomock.Param{repoFullName, env, pullNum} - pegomock.GetGenericMockFrom(mock).Invoke("Unlock", params, []reflect.Type{}) -} - -func (mock *MockWorkspaceLocker) VerifyWasCalledOnce() *VerifierWorkspaceLocker { - return &VerifierWorkspaceLocker{mock, pegomock.Times(1), nil} -} - -func (mock *MockWorkspaceLocker) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierWorkspaceLocker { - return &VerifierWorkspaceLocker{mock, invocationCountMatcher, nil} -} - -func (mock *MockWorkspaceLocker) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierWorkspaceLocker { - return &VerifierWorkspaceLocker{mock, invocationCountMatcher, inOrderContext} -} - -type VerifierWorkspaceLocker struct { - mock *MockWorkspaceLocker - invocationCountMatcher pegomock.Matcher - inOrderContext *pegomock.InOrderContext -} - -func (verifier *VerifierWorkspaceLocker) TryLock(repoFullName string, env string, pullNum int) *WorkspaceLocker_TryLock_OngoingVerification { - params := []pegomock.Param{repoFullName, env, pullNum} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "TryLock", params) - return &WorkspaceLocker_TryLock_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type WorkspaceLocker_TryLock_OngoingVerification struct { - mock *MockWorkspaceLocker - methodInvocations []pegomock.MethodInvocation -} - -func (c *WorkspaceLocker_TryLock_OngoingVerification) GetCapturedArguments() (string, string, int) { - repoFullName, env, pullNum := c.GetAllCapturedArguments() - return repoFullName[len(repoFullName)-1], env[len(env)-1], pullNum[len(pullNum)-1] -} - -func (c *WorkspaceLocker_TryLock_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 []int) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(string) - } - _param1 = make([]string, len(params[1])) - for u, param := range params[1] { - _param1[u] = param.(string) - } - _param2 = make([]int, len(params[2])) - for u, param := range params[2] { - _param2[u] = param.(int) - } - } - return -} - -func (verifier *VerifierWorkspaceLocker) Unlock(repoFullName string, env string, pullNum int) *WorkspaceLocker_Unlock_OngoingVerification { - params := []pegomock.Param{repoFullName, env, pullNum} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Unlock", params) - return &WorkspaceLocker_Unlock_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type WorkspaceLocker_Unlock_OngoingVerification struct { - mock *MockWorkspaceLocker - methodInvocations []pegomock.MethodInvocation -} - -func (c *WorkspaceLocker_Unlock_OngoingVerification) GetCapturedArguments() (string, string, int) { - repoFullName, env, pullNum := c.GetAllCapturedArguments() - return repoFullName[len(repoFullName)-1], env[len(env)-1], pullNum[len(pullNum)-1] -} - -func (c *WorkspaceLocker_Unlock_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 []int) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(string) - } - _param1 = make([]string, len(params[1])) - for u, param := range params[1] { - _param1[u] = param.(string) - } - _param2 = make([]int, len(params[2])) - for u, param := range params[2] { - _param2[u] = param.(int) - } - } - return -} diff --git a/server/events/models/models.go b/server/events/models/models.go index 20fc3234e..c32b24be5 100644 --- a/server/events/models/models.go +++ b/server/events/models/models.go @@ -68,9 +68,9 @@ type ProjectLock struct { // User is the username of the user that ran the command // that created this lock. User User - // Env is the Terraform environment that this + // Workspace is the Terraform workspace that this // lock is being held against. - Env string + Workspace string // Time is the time at which the lock was first created. Time time.Time } diff --git a/server/events/plan_executor.go b/server/events/plan_executor.go index a2de31f4c..89d133d4c 100644 --- a/server/events/plan_executor.go +++ b/server/events/plan_executor.go @@ -33,7 +33,7 @@ type PlanExecutor struct { Locker locking.Locker LockURL func(id string) (url string) Run run.Runner - Workspace Workspace + Workspace AtlantisWorkspace ProjectPreExecute ProjectPreExecutor ProjectFinder ProjectFinder } @@ -63,7 +63,7 @@ func (p *PlanExecutor) Execute(ctx *CommandContext) CommandResponse { return CommandResponse{Failure: "No Terraform files were modified."} } - cloneDir, err := p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.Command.Environment) + cloneDir, err := p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.Command.Workspace) if err != nil { return CommandResponse{Error: err} } @@ -85,20 +85,20 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models. } config := preExecute.ProjectConfig terraformVersion := preExecute.TerraformVersion - tfEnv := ctx.Command.Environment + workspace := ctx.Command.Workspace // Run terraform plan. - planFile := filepath.Join(repoDir, project.Path, fmt.Sprintf("%s.tfplan", tfEnv)) + planFile := filepath.Join(repoDir, project.Path, fmt.Sprintf("%s.tfplan", workspace)) userVar := fmt.Sprintf("%s=%s", atlantisUserTFVar, ctx.User.Username) planExtraArgs := config.GetExtraArguments(ctx.Command.Name.String()) tfPlanCmd := append(append([]string{"plan", "-refresh", "-no-color", "-out", planFile, "-var", userVar}, planExtraArgs...), ctx.Command.Flags...) - // Check if env/{environment}.tfvars exist. - tfEnvFileName := filepath.Join("env", tfEnv+".tfvars") - if _, err := os.Stat(filepath.Join(repoDir, project.Path, tfEnvFileName)); err == nil { - tfPlanCmd = append(tfPlanCmd, "-var-file", tfEnvFileName) + // Check if env/{workspace}.tfvars exist. + envFileName := filepath.Join("env", workspace+".tfvars") + if _, err := os.Stat(filepath.Join(repoDir, project.Path, envFileName)); err == nil { + tfPlanCmd = append(tfPlanCmd, "-var-file", envFileName) } - output, err := p.Terraform.RunCommandWithVersion(ctx.Log, filepath.Join(repoDir, project.Path), tfPlanCmd, terraformVersion, tfEnv) + output, err := p.Terraform.RunCommandWithVersion(ctx.Log, filepath.Join(repoDir, project.Path), tfPlanCmd, terraformVersion, workspace) if err != nil { // Plan failed so unlock the state. if _, unlockErr := p.Locker.Unlock(preExecute.LockResponse.LockKey); unlockErr != nil { @@ -111,7 +111,7 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models. // If there are post plan commands then run them. if len(config.PostPlan) > 0 { absolutePath := filepath.Join(repoDir, project.Path) - _, err := p.Run.Execute(ctx.Log, config.PostPlan, absolutePath, tfEnv, terraformVersion, "post_plan") + _, err := p.Run.Execute(ctx.Log, config.PostPlan, absolutePath, workspace, terraformVersion, "post_plan") if err != nil { return ProjectResult{Error: errors.Wrap(err, "running post plan commands")} } diff --git a/server/events/plan_executor_test.go b/server/events/plan_executor_test.go index 53a602dc3..23f993c22 100644 --- a/server/events/plan_executor_test.go +++ b/server/events/plan_executor_test.go @@ -20,8 +20,8 @@ import ( var planCtx = events.CommandContext{ Command: &events.Command{ - Name: events.Plan, - Environment: "env", + Name: events.Plan, + Workspace: "workspace", }, Log: logging.NewNoopLogger(), BaseRepo: models.Repo{}, @@ -53,10 +53,10 @@ func TestExecute_NoModifiedProjects(t *testing.T) { } func TestExecute_CloneErr(t *testing.T) { - t.Log("If Workspace.Clone returns an error we return an error") + t.Log("If AtlantisWorkspace.Clone returns an error we return an error") p, _, _ := setupPlanExecutorTest(t) When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), matchers.AnyVcsHost())).ThenReturn([]string{"file.tf"}, nil) - When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")).ThenReturn("", errors.New("err")) + When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")).ThenReturn("", errors.New("err")) r := p.Execute(&planCtx) Assert(t, r.Error != nil, "exp .Error to be set") @@ -67,7 +67,7 @@ func TestExecute_Success(t *testing.T) { t.Log("If there are no errors, the plan should be returned") p, runner, _ := setupPlanExecutorTest(t) When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), matchers.AnyVcsHost())).ThenReturn([]string{"file.tf"}, nil) - When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")). + When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")). ThenReturn("/tmp/clone-repo", nil) When(p.ProjectPreExecute.Execute(&planCtx, "/tmp/clone-repo", models.Project{RepoFullName: "", Path: "."})). ThenReturn(events.PreExecuteResult{ @@ -81,9 +81,9 @@ func TestExecute_Success(t *testing.T) { runner.VerifyWasCalledOnce().RunCommandWithVersion( planCtx.Log, "/tmp/clone-repo", - []string{"plan", "-refresh", "-no-color", "-out", "/tmp/clone-repo/env.tfplan", "-var", "atlantis_user=anubhavmishra"}, + []string{"plan", "-refresh", "-no-color", "-out", "/tmp/clone-repo/workspace.tfplan", "-var", "atlantis_user=anubhavmishra"}, nil, - "env", + "workspace", ) Assert(t, len(r.ProjectResults) == 1, "exp one project result") result := r.ProjectResults[0] @@ -96,7 +96,7 @@ func TestExecute_PreExecuteResult(t *testing.T) { t.Log("If DefaultProjectPreExecutor.Execute returns a ProjectResult we should return it") p, _, _ := setupPlanExecutorTest(t) When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), matchers.AnyVcsHost())).ThenReturn([]string{"file.tf"}, nil) - When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")). + When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")). ThenReturn("/tmp/clone-repo", nil) projectResult := events.ProjectResult{ Failure: "failure", @@ -115,7 +115,7 @@ func TestExecute_MultiProjectFailure(t *testing.T) { p, runner, locker := setupPlanExecutorTest(t) // Two projects have been modified so we should run plan in two paths. When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), matchers.AnyVcsHost())).ThenReturn([]string{"path1/file.tf", "path2/file.tf"}, nil) - When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")). + When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")). ThenReturn("/tmp/clone-repo", nil) // Both projects will succeed in the PreExecute stage. @@ -128,9 +128,9 @@ func TestExecute_MultiProjectFailure(t *testing.T) { When(runner.RunCommandWithVersion( planCtx.Log, "/tmp/clone-repo/path1", - []string{"plan", "-refresh", "-no-color", "-out", "/tmp/clone-repo/path1/env.tfplan", "-var", "atlantis_user=anubhavmishra"}, + []string{"plan", "-refresh", "-no-color", "-out", "/tmp/clone-repo/path1/workspace.tfplan", "-var", "atlantis_user=anubhavmishra"}, nil, - "env", + "workspace", )).ThenReturn("", errors.New("path1 err")) // The second will succeed. We don't need to stub it because by default it // will return a nil error. @@ -155,13 +155,13 @@ func TestExecute_PostPlanCommands(t *testing.T) { t.Log("Should execute post-plan commands and return if there is an error") p, _, _ := setupPlanExecutorTest(t) When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), matchers.AnyVcsHost())).ThenReturn([]string{"file.tf"}, nil) - When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")). + When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")). ThenReturn("/tmp/clone-repo", nil) When(p.ProjectPreExecute.Execute(&planCtx, "/tmp/clone-repo", models.Project{RepoFullName: "", Path: "."})). ThenReturn(events.PreExecuteResult{ ProjectConfig: events.ProjectConfig{PostPlan: []string{"post-plan"}}, }) - When(p.Run.Execute(planCtx.Log, []string{"post-plan"}, "/tmp/clone-repo", "env", nil, "post_plan")). + When(p.Run.Execute(planCtx.Log, []string{"post-plan"}, "/tmp/clone-repo", "workspace", nil, "post_plan")). ThenReturn("", errors.New("err")) r := p.Execute(&planCtx) @@ -175,7 +175,7 @@ func TestExecute_PostPlanCommands(t *testing.T) { func setupPlanExecutorTest(t *testing.T) (*events.PlanExecutor, *tmocks.MockClient, *lmocks.MockLocker) { RegisterMockTestingT(t) vcsProxy := vcsmocks.NewMockClientProxy() - w := mocks.NewMockWorkspace() + w := mocks.NewMockAtlantisWorkspace() ppe := mocks.NewMockProjectPreExecutor() runner := tmocks.NewMockClient() locker := lmocks.NewMockLocker() diff --git a/server/events/project_pre_execute.go b/server/events/project_pre_execute.go index fb174fd60..c7481d434 100644 --- a/server/events/project_pre_execute.go +++ b/server/events/project_pre_execute.go @@ -40,8 +40,8 @@ type PreExecuteResult struct { // Execute executes the pre plan/apply tasks. func (p *DefaultProjectPreExecutor) Execute(ctx *CommandContext, repoDir string, project models.Project) PreExecuteResult { - tfEnv := ctx.Command.Environment - lockAttempt, err := p.Locker.TryLock(project, tfEnv, ctx.Pull, ctx.User) + workspace := ctx.Command.Workspace + lockAttempt, err := p.Locker.TryLock(project, workspace, ctx.Pull, ctx.User) if err != nil { return PreExecuteResult{ProjectResult: ProjectResult{Error: errors.Wrap(err, "acquiring lock")}} } @@ -72,25 +72,25 @@ func (p *DefaultProjectPreExecutor) Execute(ctx *CommandContext, repoDir string, if constraints.Check(terraformVersion) { ctx.Log.Info("determined that we are running terraform with version >= 0.9.0. Running version %s", terraformVersion) if len(config.PreInit) > 0 { - _, err := p.Run.Execute(ctx.Log, config.PreInit, absolutePath, tfEnv, terraformVersion, "pre_init") + _, err := p.Run.Execute(ctx.Log, config.PreInit, absolutePath, workspace, terraformVersion, "pre_init") if err != nil { return PreExecuteResult{ProjectResult: ProjectResult{Error: errors.Wrapf(err, "running %s commands", "pre_init")}} } } - _, err := p.Terraform.Init(ctx.Log, absolutePath, tfEnv, config.GetExtraArguments("init"), terraformVersion) + _, err := p.Terraform.Init(ctx.Log, absolutePath, workspace, config.GetExtraArguments("init"), terraformVersion) if err != nil { return PreExecuteResult{ProjectResult: ProjectResult{Error: err}} } } else { ctx.Log.Info("determined that we are running terraform with version < 0.9.0. Running version %s", terraformVersion) if len(config.PreGet) > 0 { - _, err := p.Run.Execute(ctx.Log, config.PreGet, absolutePath, tfEnv, terraformVersion, "pre_get") + _, err := p.Run.Execute(ctx.Log, config.PreGet, absolutePath, workspace, terraformVersion, "pre_get") if err != nil { return PreExecuteResult{ProjectResult: ProjectResult{Error: errors.Wrapf(err, "running %s commands", "pre_get")}} } } terraformGetCmd := append([]string{"get", "-no-color"}, config.GetExtraArguments("get")...) - _, err := p.Terraform.RunCommandWithVersion(ctx.Log, absolutePath, terraformGetCmd, terraformVersion, tfEnv) + _, err := p.Terraform.RunCommandWithVersion(ctx.Log, absolutePath, terraformGetCmd, terraformVersion, workspace) if err != nil { return PreExecuteResult{ProjectResult: ProjectResult{Error: err}} } @@ -104,7 +104,7 @@ func (p *DefaultProjectPreExecutor) Execute(ctx *CommandContext, repoDir string, commands = config.PreApply } if len(commands) > 0 { - _, err := p.Run.Execute(ctx.Log, commands, absolutePath, tfEnv, terraformVersion, stage) + _, err := p.Run.Execute(ctx.Log, commands, absolutePath, workspace, terraformVersion, stage) if err != nil { return PreExecuteResult{ProjectResult: ProjectResult{Error: errors.Wrapf(err, "running %s commands", stage)}} } diff --git a/server/events/pull_closed_executor.go b/server/events/pull_closed_executor.go index c3a2d6315..7ede3a00a 100644 --- a/server/events/pull_closed_executor.go +++ b/server/events/pull_closed_executor.go @@ -18,7 +18,7 @@ import ( // PullCleaner cleans up pull requests after they're closed/merged. type PullCleaner interface { // CleanUpPull deletes the workspaces used by the pull request on disk - // and deletes any locks associated with this pull request for all envs. + // and deletes any locks associated with this pull request for all workspaces. CleanUpPull(repo models.Repo, pull models.PullRequest, host vcs.Host) error } @@ -27,18 +27,18 @@ type PullCleaner interface { type PullClosedExecutor struct { Locker locking.Locker VCSClient vcs.ClientProxy - Workspace Workspace + Workspace AtlantisWorkspace } type templatedProject struct { - Path string - Envs string + Path string + Workspaces string } var pullClosedTemplate = template.Must(template.New("").Parse( - "Locks and plans deleted for the projects and environments modified in this pull request:\n" + + "Locks and plans deleted for the projects and workspaces modified in this pull request:\n" + "{{ range . }}\n" + - "- path: `{{ .Path }}` {{ .Envs }}{{ end }}")) + "- path: `{{ .Path }}` {{ .Workspaces }}{{ end }}")) // CleanUpPull cleans up after a closed pull request. func (p *PullClosedExecutor) CleanUpPull(repo models.Repo, pull models.PullRequest, host vcs.Host) error { @@ -68,36 +68,36 @@ func (p *PullClosedExecutor) CleanUpPull(repo models.Repo, pull models.PullReque } // buildTemplateData formats the lock data into a slice that can easily be -// templated for the VCS comment. We organize all the environments by their +// templated for the VCS comment. We organize all the workspaces by their // respective project paths so the comment can look like: -// path: {path}, environments: {all-envs} +// path: {path}, workspaces: {all-workspaces} func (p *PullClosedExecutor) buildTemplateData(locks []models.ProjectLock) []templatedProject { - envsByPath := make(map[string][]string) + workspacesByPath := make(map[string][]string) for _, l := range locks { path := l.Project.RepoFullName + "/" + l.Project.Path - envsByPath[path] = append(envsByPath[path], l.Env) + workspacesByPath[path] = append(workspacesByPath[path], l.Workspace) } // sort keys so we can write deterministic tests var sortedPaths []string - for p := range envsByPath { + for p := range workspacesByPath { sortedPaths = append(sortedPaths, p) } sort.Strings(sortedPaths) var projects []templatedProject for _, p := range sortedPaths { - env := envsByPath[p] - envsStr := fmt.Sprintf("`%s`", strings.Join(env, "`, `")) - if len(env) == 1 { + workspace := workspacesByPath[p] + workspacesStr := fmt.Sprintf("`%s`", strings.Join(workspace, "`, `")) + if len(workspace) == 1 { projects = append(projects, templatedProject{ - Path: p, - Envs: "environment: " + envsStr, + Path: p, + Workspaces: "workspace: " + workspacesStr, }) } else { projects = append(projects, templatedProject{ - Path: p, - Envs: "environments: " + envsStr, + Path: p, + Workspaces: "workspaces: " + workspacesStr, }) } diff --git a/server/events/pull_closed_executor_test.go b/server/events/pull_closed_executor_test.go index 62e9e4433..f2ae1b8de 100644 --- a/server/events/pull_closed_executor_test.go +++ b/server/events/pull_closed_executor_test.go @@ -19,7 +19,7 @@ import ( func TestCleanUpPullWorkspaceErr(t *testing.T) { t.Log("when workspace.Delete returns an error, we return it") RegisterMockTestingT(t) - w := mocks.NewMockWorkspace() + w := mocks.NewMockAtlantisWorkspace() pce := events.PullClosedExecutor{ Workspace: w, } @@ -32,7 +32,7 @@ func TestCleanUpPullWorkspaceErr(t *testing.T) { func TestCleanUpPullUnlockErr(t *testing.T) { t.Log("when locker.UnlockByPull returns an error, we return it") RegisterMockTestingT(t) - w := mocks.NewMockWorkspace() + w := mocks.NewMockAtlantisWorkspace() l := lockmocks.NewMockLocker() pce := events.PullClosedExecutor{ Locker: l, @@ -47,7 +47,7 @@ func TestCleanUpPullUnlockErr(t *testing.T) { func TestCleanUpPullNoLocks(t *testing.T) { t.Log("when there are no locks to clean up, we don't comment") RegisterMockTestingT(t) - w := mocks.NewMockWorkspace() + w := mocks.NewMockAtlantisWorkspace() l := lockmocks.NewMockLocker() cp := vcsmocks.NewMockClientProxy() pce := events.PullClosedExecutor{ @@ -73,61 +73,61 @@ func TestCleanUpPullComments(t *testing.T) { "single lock, empty path", []models.ProjectLock{ { - Project: models.NewProject("owner/repo", ""), - Env: "default", + Project: models.NewProject("owner/repo", ""), + Workspace: "default", }, }, - "- path: `owner/repo/.` environment: `default`", + "- path: `owner/repo/.` workspace: `default`", }, { "single lock, non-empty path", []models.ProjectLock{ { - Project: models.NewProject("owner/repo", "path"), - Env: "default", + Project: models.NewProject("owner/repo", "path"), + Workspace: "default", }, }, - "- path: `owner/repo/path` environment: `default`", + "- path: `owner/repo/path` workspace: `default`", }, { - "single path, multiple environments", + "single path, multiple workspaces", []models.ProjectLock{ { - Project: models.NewProject("owner/repo", "path"), - Env: "env1", + Project: models.NewProject("owner/repo", "path"), + Workspace: "workspace1", }, { - Project: models.NewProject("owner/repo", "path"), - Env: "env2", + Project: models.NewProject("owner/repo", "path"), + Workspace: "workspace2", }, }, - "- path: `owner/repo/path` environments: `env1`, `env2`", + "- path: `owner/repo/path` workspaces: `workspace1`, `workspace2`", }, { - "multiple paths, multiple environments", + "multiple paths, multiple workspaces", []models.ProjectLock{ { - Project: models.NewProject("owner/repo", "path"), - Env: "env1", + Project: models.NewProject("owner/repo", "path"), + Workspace: "workspace1", }, { - Project: models.NewProject("owner/repo", "path"), - Env: "env2", + Project: models.NewProject("owner/repo", "path"), + Workspace: "workspace2", }, { - Project: models.NewProject("owner/repo", "path2"), - Env: "env1", + Project: models.NewProject("owner/repo", "path2"), + Workspace: "workspace1", }, { - Project: models.NewProject("owner/repo", "path2"), - Env: "env2", + Project: models.NewProject("owner/repo", "path2"), + Workspace: "workspace2", }, }, - "- path: `owner/repo/path` environments: `env1`, `env2`\n- path: `owner/repo/path2` environments: `env1`, `env2`", + "- path: `owner/repo/path` workspaces: `workspace1`, `workspace2`\n- path: `owner/repo/path2` workspaces: `workspace1`, `workspace2`", }, } for _, c := range cases { - w := mocks.NewMockWorkspace() + w := mocks.NewMockAtlantisWorkspace() cp := vcsmocks.NewMockClientProxy() l := lockmocks.NewMockLocker() pce := events.PullClosedExecutor{ @@ -141,7 +141,7 @@ func TestCleanUpPullComments(t *testing.T) { Ok(t, err) _, _, comment, _ := cp.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), AnyString(), matchers.AnyVcsHost()).GetCapturedArguments() - expected := "Locks and plans deleted for the projects and environments modified in this pull request:\n\n" + c.Exp + expected := "Locks and plans deleted for the projects and workspaces modified in this pull request:\n\n" + c.Exp Equals(t, expected, comment) } } diff --git a/server/events/run/mocks/mock_runner.go b/server/events/run/mocks/mock_runner.go index 5c9d42ce4..9bae9461a 100644 --- a/server/events/run/mocks/mock_runner.go +++ b/server/events/run/mocks/mock_runner.go @@ -19,8 +19,8 @@ func NewMockRunner() *MockRunner { return &MockRunner{fail: pegomock.GlobalFailHandler} } -func (mock *MockRunner) Execute(log *logging.SimpleLogger, commands []string, path string, environment string, terraformVersion *go_version.Version, stage string) (string, error) { - params := []pegomock.Param{log, commands, path, environment, terraformVersion, stage} +func (mock *MockRunner) Execute(log *logging.SimpleLogger, commands []string, path string, workspace string, terraformVersion *go_version.Version, stage string) (string, error) { + params := []pegomock.Param{log, commands, path, workspace, terraformVersion, stage} result := pegomock.GetGenericMockFrom(mock).Invoke("Execute", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) var ret0 string var ret1 error @@ -53,8 +53,8 @@ type VerifierRunner struct { inOrderContext *pegomock.InOrderContext } -func (verifier *VerifierRunner) Execute(log *logging.SimpleLogger, commands []string, path string, environment string, terraformVersion *go_version.Version, stage string) *Runner_Execute_OngoingVerification { - params := []pegomock.Param{log, commands, path, environment, terraformVersion, stage} +func (verifier *VerifierRunner) Execute(log *logging.SimpleLogger, commands []string, path string, workspace string, terraformVersion *go_version.Version, stage string) *Runner_Execute_OngoingVerification { + params := []pegomock.Param{log, commands, path, workspace, terraformVersion, stage} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Execute", params) return &Runner_Execute_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -65,8 +65,8 @@ type Runner_Execute_OngoingVerification struct { } func (c *Runner_Execute_OngoingVerification) GetCapturedArguments() (*logging.SimpleLogger, []string, string, string, *go_version.Version, string) { - log, commands, path, environment, terraformVersion, stage := c.GetAllCapturedArguments() - return log[len(log)-1], commands[len(commands)-1], path[len(path)-1], environment[len(environment)-1], terraformVersion[len(terraformVersion)-1], stage[len(stage)-1] + log, commands, path, workspace, terraformVersion, stage := c.GetAllCapturedArguments() + return log[len(log)-1], commands[len(commands)-1], path[len(path)-1], workspace[len(workspace)-1], terraformVersion[len(terraformVersion)-1], stage[len(stage)-1] } func (c *Runner_Execute_OngoingVerification) GetAllCapturedArguments() (_param0 []*logging.SimpleLogger, _param1 [][]string, _param2 []string, _param3 []string, _param4 []*go_version.Version, _param5 []string) { diff --git a/server/events/run/run.go b/server/events/run/run.go index 816acc591..a2d32b734 100644 --- a/server/events/run/run.go +++ b/server/events/run/run.go @@ -20,7 +20,7 @@ const inlineShebang = "#!/bin/sh -e" //go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_runner.go Runner type Runner interface { - Execute(log *logging.SimpleLogger, commands []string, path string, environment string, terraformVersion *version.Version, stage string) (string, error) + Execute(log *logging.SimpleLogger, commands []string, path string, workspace string, terraformVersion *version.Version, stage string) (string, error) } type Run struct{} @@ -31,7 +31,7 @@ func (p *Run) Execute( log *logging.SimpleLogger, commands []string, path string, - environment string, + workspace string, terraformVersion *version.Version, stage string) (string, error) { // we create a script from the commands provided @@ -48,11 +48,11 @@ func (p *Run) Execute( log.Info("running %s commands: %v", stage, commands) // set environment variable for the run. - // this is to support scripts to use the ENVIRONMENT, ATLANTIS_TERRAFORM_VERSION - // and WORKSPACE variables in their scripts - os.Setenv("ENVIRONMENT", environment) // nolint: errcheck + // this is to support scripts to use the WORKSPACE, ATLANTIS_TERRAFORM_VERSION + // and DIR variables in their scripts + os.Setenv("WORKSPACE", workspace) // nolint: errcheck os.Setenv("ATLANTIS_TERRAFORM_VERSION", terraformVersion.String()) // nolint: errcheck - os.Setenv("WORKSPACE", path) // nolint: errcheck + os.Setenv("DIR", path) // nolint: errcheck return execute(s) } diff --git a/server/events/terraform/mocks/mock_terraform_client.go b/server/events/terraform/mocks/mock_terraform_client.go index 018730a94..0afb08cc4 100644 --- a/server/events/terraform/mocks/mock_terraform_client.go +++ b/server/events/terraform/mocks/mock_terraform_client.go @@ -31,8 +31,8 @@ func (mock *MockClient) Version() *go_version.Version { return ret0 } -func (mock *MockClient) RunCommandWithVersion(log *logging.SimpleLogger, path string, args []string, v *go_version.Version, env string) (string, error) { - params := []pegomock.Param{log, path, args, v, env} +func (mock *MockClient) RunCommandWithVersion(log *logging.SimpleLogger, path string, args []string, v *go_version.Version, workspace string) (string, error) { + params := []pegomock.Param{log, path, args, v, workspace} result := pegomock.GetGenericMockFrom(mock).Invoke("RunCommandWithVersion", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) var ret0 string var ret1 error @@ -47,8 +47,8 @@ func (mock *MockClient) RunCommandWithVersion(log *logging.SimpleLogger, path st return ret0, ret1 } -func (mock *MockClient) Init(log *logging.SimpleLogger, path string, env string, extraInitArgs []string, version *go_version.Version) ([]string, error) { - params := []pegomock.Param{log, path, env, extraInitArgs, version} +func (mock *MockClient) Init(log *logging.SimpleLogger, path string, workspace string, extraInitArgs []string, version *go_version.Version) ([]string, error) { + params := []pegomock.Param{log, path, workspace, extraInitArgs, version} result := pegomock.GetGenericMockFrom(mock).Invoke("Init", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) var ret0 []string var ret1 error @@ -98,8 +98,8 @@ func (c *Client_Version_OngoingVerification) GetCapturedArguments() { func (c *Client_Version_OngoingVerification) GetAllCapturedArguments() { } -func (verifier *VerifierClient) RunCommandWithVersion(log *logging.SimpleLogger, path string, args []string, v *go_version.Version, env string) *Client_RunCommandWithVersion_OngoingVerification { - params := []pegomock.Param{log, path, args, v, env} +func (verifier *VerifierClient) RunCommandWithVersion(log *logging.SimpleLogger, path string, args []string, v *go_version.Version, workspace string) *Client_RunCommandWithVersion_OngoingVerification { + params := []pegomock.Param{log, path, args, v, workspace} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RunCommandWithVersion", params) return &Client_RunCommandWithVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -110,8 +110,8 @@ type Client_RunCommandWithVersion_OngoingVerification struct { } func (c *Client_RunCommandWithVersion_OngoingVerification) GetCapturedArguments() (*logging.SimpleLogger, string, []string, *go_version.Version, string) { - log, path, args, v, env := c.GetAllCapturedArguments() - return log[len(log)-1], path[len(path)-1], args[len(args)-1], v[len(v)-1], env[len(env)-1] + log, path, args, v, workspace := c.GetAllCapturedArguments() + return log[len(log)-1], path[len(path)-1], args[len(args)-1], v[len(v)-1], workspace[len(workspace)-1] } func (c *Client_RunCommandWithVersion_OngoingVerification) GetAllCapturedArguments() (_param0 []*logging.SimpleLogger, _param1 []string, _param2 [][]string, _param3 []*go_version.Version, _param4 []string) { @@ -141,8 +141,8 @@ func (c *Client_RunCommandWithVersion_OngoingVerification) GetAllCapturedArgumen return } -func (verifier *VerifierClient) Init(log *logging.SimpleLogger, path string, env string, extraInitArgs []string, version *go_version.Version) *Client_Init_OngoingVerification { - params := []pegomock.Param{log, path, env, extraInitArgs, version} +func (verifier *VerifierClient) Init(log *logging.SimpleLogger, path string, workspace string, extraInitArgs []string, version *go_version.Version) *Client_Init_OngoingVerification { + params := []pegomock.Param{log, path, workspace, extraInitArgs, version} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Init", params) return &Client_Init_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -153,8 +153,8 @@ type Client_Init_OngoingVerification struct { } func (c *Client_Init_OngoingVerification) GetCapturedArguments() (*logging.SimpleLogger, string, string, []string, *go_version.Version) { - log, path, env, extraInitArgs, version := c.GetAllCapturedArguments() - return log[len(log)-1], path[len(path)-1], env[len(env)-1], extraInitArgs[len(extraInitArgs)-1], version[len(version)-1] + log, path, workspace, extraInitArgs, version := c.GetAllCapturedArguments() + return log[len(log)-1], path[len(path)-1], workspace[len(workspace)-1], extraInitArgs[len(extraInitArgs)-1], version[len(version)-1] } func (c *Client_Init_OngoingVerification) GetAllCapturedArguments() (_param0 []*logging.SimpleLogger, _param1 []string, _param2 []string, _param3 [][]string, _param4 []*go_version.Version) { diff --git a/server/events/terraform/terraform_client.go b/server/events/terraform/terraform_client.go index 14404f42c..f66114317 100644 --- a/server/events/terraform/terraform_client.go +++ b/server/events/terraform/terraform_client.go @@ -17,8 +17,8 @@ import ( type Client interface { Version() *version.Version - RunCommandWithVersion(log *logging.SimpleLogger, path string, args []string, v *version.Version, env string) (string, error) - Init(log *logging.SimpleLogger, path string, env string, extraInitArgs []string, version *version.Version) ([]string, error) + RunCommandWithVersion(log *logging.SimpleLogger, path string, args []string, v *version.Version, workspace string) (string, error) + Init(log *logging.SimpleLogger, path string, workspace string, extraInitArgs []string, version *version.Version) ([]string, error) } type DefaultClient struct { @@ -62,9 +62,10 @@ func (c *DefaultClient) Version() *version.Version { } // RunCommandWithVersion executes the provided version of terraform with -// the provided args in path. The variable "v" is the version of terraform executable to use and the variable "env" is the -// environment specified by the user commenting "atlantis plan/apply {env}" which is set to "default" by default. -func (c *DefaultClient) RunCommandWithVersion(log *logging.SimpleLogger, path string, args []string, v *version.Version, env string) (string, error) { +// the provided args in path. v is the version of terraform executable to use +// and workspace is the workspace specified by the user commenting +// "atlantis plan/apply {workspace}" which is set to "default" by default. +func (c *DefaultClient) RunCommandWithVersion(log *logging.SimpleLogger, path string, args []string, v *version.Version, workspace string) (string, error) { tfExecutable := "terraform" // if version is the same as the default, don't need to prepend the version name to the executable if !v.Equal(c.defaultVersion) { @@ -72,14 +73,14 @@ func (c *DefaultClient) RunCommandWithVersion(log *logging.SimpleLogger, path st } // set environment variables - // this is to support scripts to use the ENVIRONMENT, ATLANTIS_TERRAFORM_VERSION - // and WORKSPACE variables in their scripts + // this is to support scripts to use the WORKSPACE, ATLANTIS_TERRAFORM_VERSION + // and DIR variables in their scripts // append current process's environment variables // this is to prevent the $PATH variable being removed from the environment envVars := []string{ - fmt.Sprintf("ENVIRONMENT=%s", env), + fmt.Sprintf("WORKSPACE=%s", workspace), fmt.Sprintf("ATLANTIS_TERRAFORM_VERSION=%s", v.String()), - fmt.Sprintf("WORKSPACE=%s", path), + fmt.Sprintf("DIR=%s", path), } envVars = append(envVars, os.Environ()...) @@ -120,7 +121,7 @@ func (c *DefaultClient) Init(log *logging.SimpleLogger, path string, workspace s // In 0.10 the env command was renamed to workspace. workspaceCommand := "workspace" if zeroPointNine.Check(version) { - workspaceCommand = "env" + workspaceCommand = "workspace" } output, err = c.RunCommandWithVersion(log, path, []string{workspaceCommand, "select", "-no-color", workspace}, version, workspace) diff --git a/server/events/workspace_locker.go b/server/events/workspace_locker.go deleted file mode 100644 index 13e918903..000000000 --- a/server/events/workspace_locker.go +++ /dev/null @@ -1,59 +0,0 @@ -package events - -import ( - "fmt" - "sync" -) - -//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_workspace_locker.go WorkspaceLocker - -// WorkspaceLocker is used to prevent multiple commands from executing at the same -// time for a single repo, pull, and environment. We need to prevent this from -// happening because a specific repo/pull/env has a single workspace on disk -// and we haven't written Atlantis (yet) to handle concurrent execution within -// this workspace. -type WorkspaceLocker interface { - // TryLock tries to acquire a lock for this repo, env and pull. - TryLock(repoFullName string, env string, pullNum int) bool - // Unlock deletes the lock for this repo, env and pull. If there was no - // lock it will do nothing. - Unlock(repoFullName, env string, pullNum int) -} - -// DefaultWorkspaceLocker implements WorkspaceLocker. -type DefaultWorkspaceLocker struct { - mutex sync.Mutex - locks map[string]interface{} -} - -// NewDefaultWorkspaceLocker is a constructor. -func NewDefaultWorkspaceLocker() *DefaultWorkspaceLocker { - return &DefaultWorkspaceLocker{ - locks: make(map[string]interface{}), - } -} - -// TryLock returns true if a lock is acquired for this repo, pull and env and -// false otherwise. -func (d *DefaultWorkspaceLocker) TryLock(repoFullName string, env string, pullNum int) bool { - d.mutex.Lock() - defer d.mutex.Unlock() - - key := d.key(repoFullName, env, pullNum) - if _, ok := d.locks[key]; !ok { - d.locks[key] = true - return true - } - return false -} - -// Unlock unlocks the repo, pull and env. -func (d *DefaultWorkspaceLocker) Unlock(repoFullName, env string, pullNum int) { - d.mutex.Lock() - defer d.mutex.Unlock() - delete(d.locks, d.key(repoFullName, env, pullNum)) -} - -func (d *DefaultWorkspaceLocker) key(repo string, env string, pull int) string { - return fmt.Sprintf("%s/%s/%d", repo, env, pull) -} diff --git a/server/events/workspace_locker_test.go b/server/events/workspace_locker_test.go deleted file mode 100644 index 08ea8cb69..000000000 --- a/server/events/workspace_locker_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package events_test - -import ( - "testing" - - "github.com/hootsuite/atlantis/server/events" - . "github.com/hootsuite/atlantis/testing" -) - -var repo = "repo/owner" -var env = "default" - -func TestTryLock(t *testing.T) { - locker := events.NewDefaultWorkspaceLocker() - - t.Log("the first lock should succeed") - Equals(t, true, locker.TryLock(repo, env, 1)) - - t.Log("now another lock for the same repo, env, and pull should fail") - Equals(t, false, locker.TryLock(repo, env, 1)) -} - -func TestTryLockDifferentEnv(t *testing.T) { - locker := events.NewDefaultWorkspaceLocker() - - t.Log("a lock for the same repo and pull but different env should succeed") - Equals(t, true, locker.TryLock(repo, env, 1)) - Equals(t, true, locker.TryLock(repo, "new-env", 1)) - - t.Log("and both should now be locked") - Equals(t, false, locker.TryLock(repo, env, 1)) - Equals(t, false, locker.TryLock(repo, "new-env", 1)) -} - -func TestTryLockDifferentRepo(t *testing.T) { - locker := events.NewDefaultWorkspaceLocker() - - t.Log("a lock for a different repo but the same env and pull should succeed") - Equals(t, true, locker.TryLock(repo, env, 1)) - newRepo := "owner/newrepo" - Equals(t, true, locker.TryLock(newRepo, env, 1)) - - t.Log("and both should now be locked") - Equals(t, false, locker.TryLock(repo, env, 1)) - Equals(t, false, locker.TryLock(newRepo, env, 1)) -} - -func TestTryLockDifferent1(t *testing.T) { - locker := events.NewDefaultWorkspaceLocker() - - t.Log("a lock for a different pull but the same repo and env should succeed") - Equals(t, true, locker.TryLock(repo, env, 1)) - new1 := 2 - Equals(t, true, locker.TryLock(repo, env, new1)) - - t.Log("and both should now be locked") - Equals(t, false, locker.TryLock(repo, env, 1)) - Equals(t, false, locker.TryLock(repo, env, new1)) -} - -func TestUnlock(t *testing.T) { - locker := events.NewDefaultWorkspaceLocker() - - t.Log("unlocking should work") - Equals(t, true, locker.TryLock(repo, env, 1)) - locker.Unlock(repo, env, 1) - Equals(t, true, locker.TryLock(repo, env, 1)) -} - -func TestUnlockDifferentEnvs(t *testing.T) { - locker := events.NewDefaultWorkspaceLocker() - t.Log("unlocking should work for different envs") - Equals(t, true, locker.TryLock(repo, env, 1)) - Equals(t, true, locker.TryLock(repo, "new-env", 1)) - locker.Unlock(repo, env, 1) - locker.Unlock(repo, "new-env", 1) - Equals(t, true, locker.TryLock(repo, env, 1)) - Equals(t, true, locker.TryLock(repo, "new-env", 1)) -} - -func TestUnlockDifferentRepos(t *testing.T) { - locker := events.NewDefaultWorkspaceLocker() - t.Log("unlocking should work for different repos") - Equals(t, true, locker.TryLock(repo, env, 1)) - newRepo := "owner/newrepo" - Equals(t, true, locker.TryLock(newRepo, env, 1)) - locker.Unlock(repo, env, 1) - locker.Unlock(newRepo, env, 1) - Equals(t, true, locker.TryLock(repo, env, 1)) - Equals(t, true, locker.TryLock(newRepo, env, 1)) -} - -func TestUnlockDifferentPulls(t *testing.T) { - locker := events.NewDefaultWorkspaceLocker() - t.Log("unlocking should work for different 1s") - Equals(t, true, locker.TryLock(repo, env, 1)) - new1 := 2 - Equals(t, true, locker.TryLock(repo, env, new1)) - locker.Unlock(repo, env, 1) - locker.Unlock(repo, env, new1) - Equals(t, true, locker.TryLock(repo, env, 1)) - Equals(t, true, locker.TryLock(repo, env, new1)) -} diff --git a/server/server.go b/server/server.go index c5de54097..9fdb6043c 100644 --- a/server/server.go +++ b/server/server.go @@ -137,7 +137,7 @@ func NewServer(config Config) (*Server, error) { lockingClient := locking.NewClient(boltdb) run := &run.Run{} configReader := &events.ProjectConfigManager{} - concurrentRunLocker := events.NewDefaultWorkspaceLocker() + workspaceLocker := events.NewDefaultAtlantisWorkspaceLocker() workspace := &events.FileWorkspace{ DataDir: config.DataDir, } @@ -188,7 +188,7 @@ func NewServer(config Config) (*Server, error) { GithubPullGetter: githubClient, GitlabMergeRequestGetter: gitlabClient, CommitStatusUpdater: commitStatusUpdater, - WorkspaceLocker: concurrentRunLocker, + AtlantisWorkspaceLocker: workspaceLocker, MarkdownRenderer: markdownRenderer, Logger: logger, } @@ -329,7 +329,7 @@ func (s *Server) GetLock(w http.ResponseWriter, _ *http.Request, id string) { RepoName: repo[1], PullRequestLink: lock.Pull.URL, LockedBy: lock.Pull.Author, - Environment: lock.Env, + Workspace: lock.Workspace, } s.LockDetailTemplate.Execute(w, l) // nolint: errcheck diff --git a/server/server_test.go b/server/server_test.go index 63c4012bf..3fc48f061 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -136,9 +136,9 @@ func TestGetLock_Success(t *testing.T) { RegisterMockTestingT(t) l := mocks.NewMockLocker() When(l.GetLock("id")).ThenReturn(&models.ProjectLock{ - Project: models.Project{RepoFullName: "owner/repo", Path: "path"}, - Pull: models.PullRequest{URL: "url", Author: "lkysow"}, - Env: "env", + Project: models.Project{RepoFullName: "owner/repo", Path: "path"}, + Pull: models.PullRequest{URL: "url", Author: "lkysow"}, + Workspace: "workspace", }, nil) tmpl := sMocks.NewMockTemplateWriter() s := server.Server{ @@ -155,7 +155,7 @@ func TestGetLock_Success(t *testing.T) { RepoName: "repo", PullRequestLink: "url", LockedBy: "lkysow", - Environment: "env", + Workspace: "workspace", }) responseContains(t, w, http.StatusOK, "") } diff --git a/server/web_templates.go b/server/web_templates.go index 460f33e2c..a42a206f8 100644 --- a/server/web_templates.go +++ b/server/web_templates.go @@ -61,7 +61,7 @@ var indexTemplate = template.Must(template.New("index.html.tmpl").Parse(`
-

Environments

+

Locks

{{ if . }} {{ range . }} @@ -73,7 +73,7 @@ var indexTemplate = template.Must(template.New("index.html.tmpl").Parse(` {{ end }} {{ else }} -

No environments found.

+

No locks found.

{{ end }}
@@ -90,7 +90,7 @@ type LockDetailData struct { RepoName string PullRequestLink string LockedBy string - Environment string + Workspace string Time time.Time } @@ -124,7 +124,7 @@ var lockTemplate = template.Must(template.New("lock.html.tmpl").Parse(`
Repo Name: {{.RepoName}}
Pull Request Link: {{.PullRequestLink}}
Locked By: {{.LockedBy}}
-
Environment: {{.Environment}}
+
Workspace: {{.Workspace}}

diff --git a/website/src/themes/kube/layouts/index.html b/website/src/themes/kube/layouts/index.html index 71e5f54d1..5e2386523 100644 --- a/website/src/themes/kube/layouts/index.html +++ b/website/src/themes/kube/layouts/index.html @@ -33,10 +33,10 @@
- Lock Environment + Lock Workspaces/Environments
-

Lock environments

-

Lock environments until pull requests are merged to prevent concurrent modification and +

Lock workspaces/environments

+

Lock workspaces/environments until pull requests are merged to prevent concurrent modification and confusion.