Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to local locking via bolt DB #1

Merged
merged 8 commits into from
May 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,21 @@
# atlantis
A [terraform](https://www.terraform.io/) collaboration tool that enables you to collaborate on infrastructure safely and securely.

## Locking
When a **Run** is triggered, the set of infrastructure that is being modified is locked against any modification or planning until the **Run** has been
completed by an `apply` and the pull request has been merged

```
{
"data_dir": "/var/lib/atlantis",
"locking": {
"backend": "file"
}
}

{
"locking": {
"backend": "dynamodb"
}
}
```
58 changes: 27 additions & 31 deletions apply_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/google/go-github/github"
"github.com/hootsuite/atlantis/locking"
"time"
)

type ApplyExecutor struct {
Expand Down Expand Up @@ -60,7 +61,6 @@ func (a *ApplyExecutor) execute(ctx *ExecutionContext, prCtx *PullRequestContext
}

func (a *ApplyExecutor) setupAndApply(ctx *ExecutionContext, prCtx *PullRequestContext) ExecutionResult {
stashCtx := a.stashContext(ctx)
a.github.UpdateStatus(prCtx, PendingStatus, "Applying...")

if a.requireApproval {
Expand All @@ -78,8 +78,6 @@ func (a *ApplyExecutor) setupAndApply(ctx *ExecutionContext, prCtx *PullRequestC
}
}

//runLog = append(runLog, "-> Confirmed pull request was plus one'd")

planPaths, err := a.downloadPlans(ctx.repoOwner, ctx.repoName, ctx.pullNum, ctx.command.environment, a.scratchDir, a.awsConfig, a.s3Bucket)
if err != nil {
errMsg := fmt.Sprintf("failed to download plans: %v", err)
Expand All @@ -99,15 +97,15 @@ func (a *ApplyExecutor) setupAndApply(ctx *ExecutionContext, prCtx *PullRequestC
//runLog = append(runLog, fmt.Sprintf("-> Downloaded plans: %v", planPaths))
applyOutputs := []PathResult{}
for _, planPath := range planPaths {
output := a.apply(ctx, stashCtx, planPath)
output := a.apply(ctx, prCtx, planPath)
output.Path = planPath
applyOutputs = append(applyOutputs, output)
}
a.updateGithubStatus(prCtx, applyOutputs)
return ExecutionResult{PathResults: applyOutputs}
}

func (a *ApplyExecutor) apply(ctx *ExecutionContext, stashCtx *StashPullRequestContext, planPath string) PathResult {
func (a *ApplyExecutor) apply(ctx *ExecutionContext, prCtx *PullRequestContext, planPath string) PathResult {
//runLog = append(runLog, fmt.Sprintf("-> Running apply %s", planPath))
planName := path.Base(planPath)
planSubDir := a.determinePlanSubDir(planName, ctx.pullNum)
Expand Down Expand Up @@ -164,18 +162,33 @@ func (a *ApplyExecutor) apply(ctx *ExecutionContext, stashCtx *StashPullRequestC
}

if remoteStatePath != "" {
//runLog = append(runLog, "-> Remote state configured")
// now lock the state prior to applying
stashLockResponse := a.stash.LockState(ctx.log, stashCtx, remoteStatePath)
if !stashLockResponse.Success {
msg := fmt.Sprintf("failed to lock state: %s", stashLockResponse.Message)
ctx.log.Err(msg)
tfEnv := ctx.command.environment
if tfEnv == "" {
tfEnv = "default"
}
run := locking.Run{
RepoOwner: prCtx.owner,
RepoName: prCtx.repoName,
Path: execPath.Relative,
Env: tfEnv,
PullID: prCtx.number,
User: prCtx.terraformApplier,
Timestamp: time.Now(),
}

lockAttempt, err := a.lockManager.TryLock(run)
if err != nil {
return PathResult{
Status: "error",
Result: GeneralError{errors.New(msg)},
Result: GeneralError{fmt.Errorf("failed to acquire lock: %s", err)},
}
}
if lockAttempt.LockAcquired != true && lockAttempt.LockingRun.PullID != prCtx.number {
return PathResult{
Status: "error",
Result: GeneralError{fmt.Errorf("failed to acquire lock: lock held by pull request #%d", lockAttempt.LockingRun.PullID)},
}
}
//runLog = append(runLog, "-> Stash lock aquired")
}

// need to get auth data from assumed role
Expand Down Expand Up @@ -226,23 +239,6 @@ func (a *ApplyExecutor) apply(ctx *ExecutionContext, stashCtx *StashPullRequestC
}
}

func (a *ApplyExecutor) validatePlusOne(prSubmitter string) func(*github.IssueComment) bool {
return func(c *github.IssueComment) bool {
if c == nil || c.Body == nil {
return false
}
body := *c.Body
if !(strings.Contains(body, ":+1:") || strings.Contains(body, "+1") || strings.ContainsRune(body, '\U0001f44d')) {
return false
}
if c.User == nil || c.User.Login == nil {
return false
}
// the plus-oner can't be the user that submitted the PR or ourselves (otherwise our comment telling the user they're missing a +1 would count as approval)
return *c.User.Login != prSubmitter && *c.User.Login != a.atlantisGithubUser
}
}

func (a *ApplyExecutor) downloadPlans(repoOwner string, repoName string, pullNum int, env string, outputDir string, awsConfig *AWSConfig, s3Bucket string) (planPaths []string, err error) {
awsSession, err := awsConfig.CreateAWSSession()
if err != nil {
Expand Down
29 changes: 15 additions & 14 deletions atlantis_config_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,34 @@ import (
"io/ioutil"
"os"
"testing"
. "github.com/hootsuite/atlantis/testing_util"
)

var tempConfigFile = "/tmp/" + AtlantisConfigFile

func TestConfigFileExists_invalid_path(t *testing.T) {
var c Config
equals(t, c.Exists("/invalid/path"), false)
Equals(t, c.Exists("/invalid/path"), false)
}

func TestConfigFileExists_valid_path(t *testing.T) {
var c Config
var str = `
---
---
terraform_version: "0.0.1"
pre_apply:
commands:
pre_apply:
commands:
- "echo"
- "date"
pre_plan:
commands:
pre_plan:
commands:
- "echo"
- "date"
stash_path: "file/path"
`
writeAtlantisConfigFile([]byte(str))
defer os.Remove(tempConfigFile)
equals(t, c.Exists("/tmp"), true)
Equals(t, c.Exists("/tmp"), true)
}

func TestConfigFileRead_invalid_config(t *testing.T) {
Expand All @@ -39,28 +40,28 @@ func TestConfigFileRead_invalid_config(t *testing.T) {
writeAtlantisConfigFile(str)
defer os.Remove(tempConfigFile)
err := c.Read("/tmp")
assert(t, err != nil, "expect an error")
Assert(t, err != nil, "expect an error")
}

func TestConfigFileRead_valid_config(t *testing.T) {
var c Config
var str = `
---
---
terraform_version: "0.0.1"
pre_apply:
commands:
pre_apply:
commands:
- "echo"
- "date"
pre_plan:
commands:
pre_plan:
commands:
- "echo"
- "date"
stash_path: "file/path"
`
writeAtlantisConfigFile([]byte(str))
defer os.Remove(tempConfigFile)
err := c.Read("/tmp")
assert(t, err == nil, "should be valid yaml")
Assert(t, err == nil, "should be valid json")
}

func writeAtlantisConfigFile(s []byte) error {
Expand Down
18 changes: 5 additions & 13 deletions base_executor.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package main

import "path/filepath"
import (
"path/filepath"
"github.com/hootsuite/atlantis/locking"
)

type BaseExecutor struct {
github *GithubClient
awsConfig *AWSConfig
scratchDir string
s3Bucket string
sshKey string
stash *StashPRClient
ghComments *GithubCommentRenderer
terraform *TerraformClient
githubCommentRenderer *GithubCommentRenderer
lockManager locking.LockManager
}

type PullRequestContext struct {
Expand Down Expand Up @@ -75,17 +78,6 @@ func (b *BaseExecutor) worstResult(results []PathResult) string {
return worst
}

func (b *BaseExecutor) stashContext(ctx *ExecutionContext) *StashPullRequestContext {
return &StashPullRequestContext{
owner: ctx.repoOwner,
repoName: ctx.repoName,
number: ctx.pullNum,
pullRequestLink: ctx.pullLink,
terraformApplier: ctx.requesterUsername,
terraformApplierEmail: ctx.requesterEmail,
}
}

func (b *BaseExecutor) Exec(f func(*ExecutionContext, *PullRequestContext) ExecutionResult, ctx *ExecutionContext, github *GithubClient) {
prCtx := b.githubContext(ctx)
result := f(ctx, prCtx)
Expand Down
Loading