mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-28 23:48:20 +00:00
Rename from environment to workspace
This commit is contained in:
20
CHANGELOG.md
20
CHANGELOG.md
@@ -5,15 +5,15 @@
|
||||
## Bug Fixes
|
||||
* Don't set `as_user` to true for Slack webhooks so we can integrate as a workspace app. ([#206](https://github.com/hootsuite/atlantis/pull/206))
|
||||
|
||||
## Backwards Incompatibilities / Notes:
|
||||
None
|
||||
|
||||
## Downloads
|
||||
* [atlantis_darwin_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.1/atlantis_darwin_amd64.zip)
|
||||
* [atlantis_linux_386.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.1/atlantis_linux_386.zip)
|
||||
* [atlantis_linux_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.1/atlantis_linux_amd64.zip)
|
||||
* [atlantis_linux_arm.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.1/atlantis_linux_arm.zip)
|
||||
|
||||
## Backwards Incompatibilities / Notes:
|
||||
None
|
||||
|
||||
# v0.2.0
|
||||
## Features
|
||||
* GitLab is now supported! ([#190](https://github.com/hootsuite/atlantis/pull/190))
|
||||
@@ -22,15 +22,15 @@ None
|
||||
## Bug Fixes
|
||||
None
|
||||
|
||||
## Backwards Incompatibilities / Notes:
|
||||
None
|
||||
|
||||
## Downloads
|
||||
* [atlantis_darwin_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.0/atlantis_darwin_amd64.zip)
|
||||
* [atlantis_linux_386.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.0/atlantis_linux_386.zip)
|
||||
* [atlantis_linux_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.0/atlantis_linux_amd64.zip)
|
||||
* [atlantis_linux_arm.zip](https://github.com/hootsuite/atlantis/releases/download/v0.2.0/atlantis_linux_arm.zip)
|
||||
|
||||
## Backwards Incompatibilities / Notes:
|
||||
None
|
||||
|
||||
# v0.1.3
|
||||
## Features
|
||||
* Environment variables are passed through to `extra_arguments`. ([#150](https://github.com/hootsuite/atlantis/pull/150))
|
||||
@@ -40,16 +40,16 @@ None
|
||||
* Modules in list of changed files weren't being filtered. ([#193](https://github.com/hootsuite/atlantis/pull/193))
|
||||
* Nil pointer error in bootstrap mode. ([#181](https://github.com/hootsuite/atlantis/pull/181))
|
||||
|
||||
## Backwards Incompatibilities / Notes:
|
||||
None
|
||||
|
||||
## Downloads
|
||||
* [atlantis_darwin_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.1.3/atlantis_darwin_amd64.zip)
|
||||
* [atlantis_linux_386.zip](https://github.com/hootsuite/atlantis/releases/download/v0.1.3/atlantis_linux_386.zip)
|
||||
* [atlantis_linux_amd64.zip](https://github.com/hootsuite/atlantis/releases/download/v0.1.3/atlantis_linux_amd64.zip)
|
||||
* [atlantis_linux_arm.zip](https://github.com/hootsuite/atlantis/releases/download/v0.1.3/atlantis_linux_arm.zip)
|
||||
|
||||
## Backwards Incompatibilities / Notes:
|
||||
None
|
||||
|
||||
# v0.1.2
|
||||
# v0.1.2
|
||||
## Features
|
||||
* all flags passed to `atlantis plan` or `atlantis apply` will now be passed through to `terraform`. ([#131](https://github.com/hootsuite/atlantis/pull/131))
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
2
Makefile
2
Makefile
@@ -35,7 +35,7 @@ regen-mocks: ## Delete all mocks and then run go generate to regen them
|
||||
go generate $$(go list ./... | grep -v e2e | grep -v vendor | grep -v static)
|
||||
|
||||
test: ## Run tests
|
||||
go test $(PKG)
|
||||
@go test $(PKG)
|
||||
|
||||
test-coverage:
|
||||
./scripts/coverage.sh $(PKG)
|
||||
|
||||
46
README.md
46
README.md
@@ -19,7 +19,7 @@ Read about [Why We Built Atlantis](https://www.atlantis.run/blog/atlantis-releas
|
||||
* [Getting Started](#getting-started)
|
||||
* [Pull Request Commands](#pull-request-commands)
|
||||
* [Project Structure](#project-structure)
|
||||
* [Environments](#environments)
|
||||
* [Workspaces/Environments](#workspacesenvironments)
|
||||
* [Terraform Versions](#terraform-versions)
|
||||
* [Project-Specific Customization](#project-specific-customization)
|
||||
* [Locking](#locking)
|
||||
@@ -30,7 +30,7 @@ Read about [Why We Built Atlantis](https://www.atlantis.run/blog/atlantis-releas
|
||||
* [AWS Credentials](#aws-credentials)
|
||||
* [Glossary](#glossary)
|
||||
* [Project](#project)
|
||||
* [Environment](#environment)
|
||||
* [Workspace/Environment](#workspaceenvironment)
|
||||
* [FAQ](#faq)
|
||||
* [Contributing](#contributing)
|
||||
* [Credits](#credits)
|
||||
@@ -38,14 +38,14 @@ Read about [Why We Built Atlantis](https://www.atlantis.run/blog/atlantis-releas
|
||||
## Features
|
||||
➜ Collaborate on Terraform with your team
|
||||
- Run terraform `plan` and `apply` **from GitHub pull requests** so everyone can review the output
|
||||
- **Lock environments** until pull requests are merged to prevent concurrent modification and confusion
|
||||
- **Lock workspaces** until pull requests are merged to prevent concurrent modification and confusion
|
||||
|
||||
➜ Developers can write Terraform safely
|
||||
- **No need to distribute AWS credentials** to your whole team. Developers can submit Terraform changes and run `plan` and `apply` directly from the pull/merge request
|
||||
- Optionally, require a **review and approval** prior to running `apply`
|
||||
|
||||
➜ Also
|
||||
- No more **copy-pasted code across environments**. Atlantis supports using an `env/{env}.tfvars` file per environment so you can write your base configuration once
|
||||
- No more **copy-pasted code across workspaces/environments**. Atlantis supports using an `env/{env}.tfvars` file per workspace/environment so you can write your base configuration once
|
||||
- Support **multiple versions of Terraform** with a simple project config file
|
||||
|
||||
## Atlantis Works With
|
||||
@@ -74,11 +74,11 @@ Atlantis currently supports three commands that can be run via pull request comm
|
||||
#### `atlantis help`
|
||||
View help
|
||||
|
||||
#### `atlantis plan [env]`
|
||||
Runs `terraform plan` for the changes in this pull request. If `[env]` is specified, will switch to that environment (or workspace in terraform > 0.10), before running `plan`. Any additional arguments passed to `atlantis plan` will be passed on to `terraform plan`. For example if you'd like to run `terraform plan -target={target}` then you can comment `atlantis plan -target={target}`.
|
||||
#### `atlantis plan [workspace]`
|
||||
Runs `terraform plan` for the changes in this pull request. If `[workspace]` is specified, will switch to that workspace, before running `plan`. Any additional arguments passed to `atlantis plan` will be passed on to `terraform plan`. For example if you'd like to run `terraform plan -target={target}` then you can comment `atlantis plan -target={target}`.
|
||||
|
||||
#### `atlantis apply [env]`
|
||||
Runs `terraform apply` for the plan generated by `atlantis plan`. If `[env]` is specified, will switch to that env/workspace.
|
||||
#### `atlantis apply [workspace]`
|
||||
Runs `terraform apply` for the plan generated by `atlantis plan`. If `[workspace]` is specified, will switch to that workspace.
|
||||
Any additional arguments passed to `atlantis apply` will be passed on to `terraform apply`.
|
||||
|
||||
## Project Structure
|
||||
@@ -99,7 +99,7 @@ Atlantis supports several Terraform project structures:
|
||||
├── main.tf
|
||||
└── ...
|
||||
```
|
||||
- one folder per environment
|
||||
- one folder per set of configuration
|
||||
```
|
||||
.
|
||||
├── staging
|
||||
@@ -109,7 +109,7 @@ Atlantis supports several Terraform project structures:
|
||||
├── main.tf
|
||||
└── ...
|
||||
```
|
||||
- using `env/{env}.tfvars` to define environment specific variables. This works in both multi-project repos and single-project repos.
|
||||
- using `env/{env}.tfvars` to define workspace specific variables. This works in both multi-project repos and single-project repos.
|
||||
```
|
||||
.
|
||||
├── env
|
||||
@@ -131,23 +131,23 @@ or
|
||||
│ └── staging.tfvars
|
||||
└── main.tf
|
||||
```
|
||||
With the above project structure you can de-duplicate your Terraform code between environments without requiring extensive use of modules. At Hootsuite we've found this project format to be very successful and use it in all of our 100+ Terraform repositories.
|
||||
With the above project structure you can de-duplicate your Terraform code between workspaces/environments without requiring extensive use of modules. At Hootsuite we've found this project format to be very successful and use it in all of our 100+ Terraform repositories.
|
||||
|
||||
## Environments
|
||||
Terraform recently introduced [State Environments](https://www.terraform.io/docs/state/environments.html) that
|
||||
> allows a single folder of Terraform configurations to manage multiple distinct infrastructure resources
|
||||
## Workspaces/Environments
|
||||
Terraform introduced [Workspaces](https://www.terraform.io/docs/state/workspaces.html) in 0.9. They allow for
|
||||
> a single directory of Terraform configuration to be used to manage multiple distinct sets of infrastructure resources
|
||||
|
||||
If you're using a Terraform version >= 0.9.0, Atlantis supports environments through an additional argument to the `atlantis plan` and `atlantis apply` commands.
|
||||
If you're using a Terraform version >= 0.9.0, Atlantis supports workspaces through an additional argument to the `atlantis plan` and `atlantis apply` commands.
|
||||
For example,
|
||||
```
|
||||
atlantis plan staging
|
||||
```
|
||||
|
||||
If an environment is specified Atlantis will use `terraform env select {env}` prior to running `terraform plan` or `terraform apply`.
|
||||
If a workspace is specified, Atlantis will use `terraform workspace select {workspace}` prior to running `terraform plan` or `terraform apply`.
|
||||
|
||||
If you're using the `env/{env}.tfvars` [project structure](#project-structure) we will also append `-tfvars=env/{env}.tfvars` to `plan` and `apply`.
|
||||
|
||||
If no environment is specified we will use `default` as the environment.
|
||||
If no workspace is specified, terraform will use the `default` workspace by default.
|
||||
|
||||
## Terraform Versions
|
||||
By default, Atlantis will use the `terraform` executable that is in its path. To use a specific version of Terraform just install that version on the server that Atlantis is running on.
|
||||
@@ -215,13 +215,15 @@ be the value of that argument. Else it will be `default`
|
||||
- `WORKSPACE`: absolute path to the root of the project on disk
|
||||
|
||||
## Locking
|
||||
When `plan` is run, the [project](#project) and [environment](#environment) are **Locked** until an `apply` succeeds **and** the pull request/merge request is merged.
|
||||
When `plan` is run, the [project](#project) and [workspace](#workspaceenvironment) are **Locked** until an `apply` succeeds **and** the pull request/merge request is merged.
|
||||
This protects against concurrent modifications to the same set of infrastructure and prevents
|
||||
users from seeing a `plan` that will be invalid if another pull request is merged.
|
||||
|
||||
To unlock the project and environment without completing an `apply` and merging, click the link
|
||||
If you have multiple directories inside a single repository, only the directory will be locked. Not the whole repo.
|
||||
|
||||
To unlock the project and workspace without completing an `apply` and merging, click the link
|
||||
at the bottom of the plan comment to discard the plan and delete the lock.
|
||||
Once a plan is discarded, you'll need to run `plan` again prior to running `apply`.
|
||||
Once a plan is discarded, you'll need to run `plan` again prior to running `apply` when you go back to that pull request.
|
||||
|
||||
## Approvals
|
||||
If you'd like to require pull/merge requests to be approved prior to a user running `atlantis apply` simply run Atlantis with the `--require-approval` flag.
|
||||
@@ -455,8 +457,8 @@ the assumed role specified in the `aws` provider and will have the session named
|
||||
A Terraform project. Multiple projects can be in a single GitHub repo.
|
||||
We identify a project by its repo **and** the path to the root of the project within that repo.
|
||||
|
||||
#### Environment
|
||||
A Terraform environment. See [terraform docs](https://www.terraform.io/docs/state/environments.html) for more information.
|
||||
#### Workspace/Environment
|
||||
A Terraform workspace. See [terraform docs](https://www.terraform.io/docs/state/workspaces.html) for more information.
|
||||
|
||||
## FAQ
|
||||
**Q: Does Atlantis affect Terraform [remote state](https://www.terraform.io/docs/state/remote.html)?**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
61
server/events/atlantis_workspace_locker.go
Normal file
61
server/events/atlantis_workspace_locker.go
Normal 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)
|
||||
}
|
||||
103
server/events/atlantis_workspace_locker_test.go
Normal file
103
server/events/atlantis_workspace_locker_test.go
Normal 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))
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]))
|
||||
123
server/events/mocks/mock_atlantis_workspace_locker.go
Normal file
123
server/events/mocks/mock_atlantis_workspace_locker.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)}}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user