Rename from environment to workspace

This commit is contained in:
Luke Kysow
2017-12-17 16:46:49 -08:00
parent 741295067a
commit 7c1220a655
40 changed files with 634 additions and 686 deletions

View File

@@ -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))

View File

@@ -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`

View File

@@ -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)

View File

@@ -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)?**

View File

@@ -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 {

View File

@@ -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")}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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))
}

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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 <command> [environment] [--verbose]
Usage: atlantis <command> [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

View File

@@ -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]))

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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")}
}

View File

@@ -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()

View File

@@ -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)}}
}

View File

@@ -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,
})
}

View File

@@ -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)
}
}

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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))
}

View File

@@ -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

View File

@@ -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, "")
}

View File

@@ -61,7 +61,7 @@ var indexTemplate = template.Must(template.New("index.html.tmpl").Parse(`
<div class="navbar-spacer"></div>
<br>
<section>
<p class="title-heading small"><strong>Environments</strong></p>
<p class="title-heading small"><strong>Locks</strong></p>
{{ if . }}
{{ range . }}
<a href="{{.LockURL}}">
@@ -73,7 +73,7 @@ var indexTemplate = template.Must(template.New("index.html.tmpl").Parse(`
</a>
{{ end }}
{{ else }}
<p class="placeholder">No environments found.</p>
<p class="placeholder">No locks found.</p>
{{ end }}
</section>
</div>
@@ -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(`
<h6><code>Repo Name</code>: <strong>{{.RepoName}}</strong></h6>
<h6><code>Pull Request Link</code>: <a href="{{.PullRequestLink}}" target="_blank"><strong>{{.PullRequestLink}}</strong></a></h6>
<h6><code>Locked By</code>: <strong>{{.LockedBy}}</strong></h6>
<h6><code>Environment</code>: <strong>{{.Environment}}</strong></h6>
<h6><code>Workspace</code>: <strong>{{.Workspace}}</strong></h6>
<br>
</div>
<div class="four columns">

View File

@@ -33,10 +33,10 @@
</div>
<div class="col col-4 item">
<figure>
<img alt="Lock Environment" height="48" src="/img/locking.png" width="48">
<img alt="Lock Workspaces/Environments" height="48" src="/img/locking.png" width="48">
</figure>
<h3>Lock environments</h3>
<p>Lock environments until pull requests are merged to prevent concurrent modification and
<h3>Lock workspaces/environments</h3>
<p>Lock workspaces/environments until pull requests are merged to prevent concurrent modification and
confusion.</p>
</div>
</div>