From 433f8875c3115e2b1645d7cc080cc1d93c43f95e Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Tue, 1 Aug 2017 22:37:20 -0700 Subject: [PATCH] Make bootstrap re-runnable. README edits --- Makefile | 3 +++ README.md | 30 ++++++++++-------------- bootstrap/bootstrap.go | 15 ++++++------ bootstrap/github.go | 17 ++++++++++++-- cmd/server.go | 4 ++-- server/apply_executor.go | 36 +++++++++++++++++------------ server/github_comment_renderer.go | 4 ++-- server/plan_executor.go | 38 ++++++++++++++++++------------- server/server.go | 12 +++++++--- 9 files changed, 93 insertions(+), 66 deletions(-) diff --git a/Makefile b/Makefile index 4fdabf08c..8a6b9cb8d 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,9 @@ test-coverage: dist: ## Package up everything in static/ using go-bindata-assetfs so it can be served by a single binary go-bindata-assetfs -pkg server static/... && mv bindata_assetfs.go server +release: ## Create packages for a release + gox -os="darwin linux" -arch="amd64" + vendor-status: @govendor status diff --git a/README.md b/README.md index 224e7e647..576244935 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,24 @@ # atlantis ![atlantis-logo](./docs/atlantis-logo.png) +A unified workflow for collaborating on Terraform through GitHub. + [![CircleCI](https://circleci.com/gh/hootsuite/atlantis/tree/master.svg?style=shield&circle-token=08bf5b34233b0e168a9dd73e01cafdcf7dc4bf16)](https://circleci.com/gh/hootsuite/atlantis/tree/master) -A unified workflow for collaborating on Terraform through GitHub. +* [Features](#features) +* [Getting Started](#getting-started) +* [Production-Ready Deployment](#production-ready-deployment) +* [Configuration](#configuration) +* [AWS Credentials](#aws-credentials) +* [Environments](#environments) +* [Locking](#locking) +* [Glossary](#glossary) + * [Project](#project) + * [Environment](#environment) ## Features ➜ Collaborate on Terraform with your team - Run terraform `plan` and `apply` **from GitHub pull requests** so everyone can review the output -![atlantis plan](https://github.com/hootsuite/atlantis/raw/master/docs/atlantis-plan.gif) - **Lock environments** until pull requests are merged to prevent concurrent modification and confusion ➜ Developers can write Terraform safely @@ -19,22 +29,6 @@ A unified workflow for collaborating on Terraform through GitHub. - 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 - Support **multiple versions of Terraform** with a simple project config file -* [Getting Started](#getting-started) -* [Production-Ready Deployment](#production-ready-deployment) - * [Install Terraform](#install-terraform) - * [Hosting Atlantis](#hosting-atlantis) - * [Add GitHub Webhook](#add-github-webhook) - * [Create a GitHub Token](#create-a-github-token) - * [Start Atlantis](#start-atlantis) - * [Testing Out Atlantis](#testing-out-atlantis) -* [Configuration](#configuration) -* [AWS Credentials](#aws-credentials) - * [Assume Role Session Names](#assume-role-session-names) -* [Environments](#environments) -* [Locking](#locking) -* [Glossary](#glossary) - * [Project](#project) - * [Environment](#environment) ## Getting Started Download from https://github.com/hootsuite/atlantis/releases diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index f86bacfbf..dee87cd92 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -36,7 +36,7 @@ var pullRequestBody = "In this pull request we will learn how to use atlantis. T "* Start by typing `atlantis help` in the comments.\n" + "* Next, lets plan by typing `atlantis plan` in the comments. That will run a `terraform plan`.\n" + "* Now lets apply that plan. Type `atlantis apply` in the comments. This will run a `terraform apply`.\n" + - "\nThank you for using atlantis. For more info on running atlantis in production please follow: https://atlantis.run/link/to/doc" + "\nThank you for trying out atlantis. For more info on running atlantis in production see https://github.com/hootsuite/atlantis" func Start() error { s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) @@ -133,11 +133,16 @@ Follow these instructions to create a token (we don't store any tokens): time.Sleep(2 * time.Second) s.Stop() colorstring.Println("\n[green]=> started tunnel!") + tunnelURL, err := getTunnelAddr() + if err != nil { + return errors.Wrapf(err, "getting tunnel url") + } + s.Stop() // start atlantis server colorstring.Printf("[white]=> starting atlantis server ") s.Start() - atlantisCmd, err := executeCmd("./atlantis", []string{"server", "--gh-user", githubUsername, "--gh-token", githubToken, "--data-dir", "/tmp/atlantis/data"}) + atlantisCmd, err := executeCmd(os.Args[0], []string{"server", "--gh-user", githubUsername, "--gh-token", githubToken, "--data-dir", "/tmp/atlantis/data", "--atlantis-url", tunnelURL}) if err != nil { return errors.Wrapf(err, "creating atlantis server") } @@ -148,12 +153,6 @@ Follow these instructions to create a token (we don't store any tokens): }() // if this function returns atlantis server should be stopped defer atlantisCmd.Process.Kill() - - tunnelURL, err := getTunnelAddr() - if err != nil { - return errors.Wrapf(err, "getting tunnel url") - } - s.Stop() colorstring.Printf("\n[green]=> atlantis server is now securely exposed at [bold][underline]%s", tunnelURL) fmt.Println("") diff --git a/bootstrap/github.go b/bootstrap/github.go index 3f1a676cf..fd7f21518 100644 --- a/bootstrap/github.go +++ b/bootstrap/github.go @@ -8,7 +8,6 @@ import ( "github.com/google/go-github/github" ) -var githubHostname = "http://api.github.com" var githubUsername string var githubToken string @@ -61,8 +60,22 @@ func (g *Client) CreateWebhook(ownerName string, repoName string, hookURL string return nil } -// CreatePullRequest creates a github pull request with custom title and description +// CreatePullRequest creates a github pull request with custom title and description. +// It first checks if there's already a pull request open for this branch func (g *Client) CreatePullRequest(ownerName string, repoName string, head string, base string) (string, error) { + + // first check if the pull request already exists + pulls, _, err := g.client.PullRequests.List(g.ctx, ownerName, repoName, nil) + if err != nil { + return "", err + } + for _, pull := range pulls { + if pull.Head.GetRef() == head && pull.Base.GetRef() == base { + return pull.GetHTMLURL(), nil + } + } + + // if not, create it newPullRequest := &github.NewPullRequest{ Title: github.String("Welcome to Atlantis!"), Head: github.String(head), diff --git a/cmd/server.go b/cmd/server.go index bbcd099e2..7f9207907 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -33,7 +33,7 @@ const ( var stringFlags = []stringFlag{ { name: atlantisURLFlag, - description: "Url that Atlantis can be reached at. Defaults to http://$(hostname):$port where $port comes from the port flag.", + description: "Url that Atlantis can be reached at. Defaults to http://$(hostname):$port where $port is from --" + portFlag + ".", }, { name: awsAssumeRoleFlag, @@ -41,7 +41,7 @@ var stringFlags = []stringFlag{ }, { name: awsRegionFlag, - description: "The Amazon region to connect to for API actions.", + description: "Amazon region to use for assume role. If not setting --" + awsAssumeRoleFlag + " then ignore.", value: "us-east-1", }, { diff --git a/server/apply_executor.go b/server/apply_executor.go index fa2a442af..ba932c53b 100644 --- a/server/apply_executor.go +++ b/server/apply_executor.go @@ -125,22 +125,28 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P applyExtraArgs = config.GetExtraArguments(ctx.Command.Name.String()) } - awsSession, err := a.awsConfig.CreateSession(ctx.User.Username) - if err != nil { - return ProjectResult{Error: err} - } - creds, err := awsSession.Config.Credentials.Get() - if err != nil { - err = errors.Wrap(err, "getting aws credentials") - ctx.Log.Err(err.Error()) - return ProjectResult{Error: err} - } - ctx.Log.Info("created aws session") + // todo: de-duplicate this section between plan and apply + var credsEnvVars []string + // If awsConfig is nil we know that we're not using assume role and so + // don't need to do an AWS calls ourselves + if a.awsConfig != nil { + awsSession, err := a.awsConfig.CreateSession(ctx.User.Username) + if err != nil { + return ProjectResult{Error: err} + } + creds, err := awsSession.Config.Credentials.Get() + if err != nil { + err = errors.Wrap(err, "getting aws credentials") + ctx.Log.Err(err.Error()) + return ProjectResult{Error: err} + } + ctx.Log.Info("created aws session") - credsEnvVars := []string{ - fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", creds.AccessKeyID), - fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", creds.SecretAccessKey), - fmt.Sprintf("AWS_SESSION_TOKEN=%s", creds.SessionToken), + credsEnvVars = []string{ + fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", creds.AccessKeyID), + fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", creds.SecretAccessKey), + fmt.Sprintf("AWS_SESSION_TOKEN=%s", creds.SessionToken), + } } // check if terraform version is >= 0.9.0 diff --git a/server/github_comment_renderer.go b/server/github_comment_renderer.go index 77a1f8c4e..8ca39d5f3 100644 --- a/server/github_comment_renderer.go +++ b/server/github_comment_renderer.go @@ -80,10 +80,10 @@ func (g *GithubCommentRenderer) renderProjectResults(pathResults []ProjectResult if result.Error != nil { results[result.Path] = g.renderTemplate(errTmpl, struct { Command string - Error string + Error string }{ Command: common.Command, - Error: result.Error.Error(), + Error: result.Error.Error(), }) } else if result.Failure != "" { results[result.Path] = g.renderTemplate(failureTmpl, struct { diff --git a/server/plan_executor.go b/server/plan_executor.go index 8a9bc2108..e9e834c1c 100644 --- a/server/plan_executor.go +++ b/server/plan_executor.go @@ -119,23 +119,29 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models. planExtraArgs = config.GetExtraArguments(ctx.Command.Name.String()) } - awsSession, err := p.awsConfig.CreateSession(ctx.User.Username) - if err != nil { - ctx.Log.Err(err.Error()) - return ProjectResult{Error: err} - } - creds, err := awsSession.Config.Credentials.Get() - if err != nil { - err = errors.Wrap(err, "getting aws credentials") - ctx.Log.Err(err.Error()) - return ProjectResult{Error: err} - } - ctx.Log.Info("created aws session") + // todo: de-duplicate this section between plan and apply + var credsEnvVars []string + // If awsConfig is nil we know that we're not using assume role and so + // don't need to do an AWS calls ourselves + if p.awsConfig != nil { + awsSession, err := p.awsConfig.CreateSession(ctx.User.Username) + if err != nil { + ctx.Log.Err(err.Error()) + return ProjectResult{Error: err} + } + creds, err := awsSession.Config.Credentials.Get() + if err != nil { + err = errors.Wrap(err, "getting aws credentials") + ctx.Log.Err(err.Error()) + return ProjectResult{Error: err} + } + ctx.Log.Info("created aws session") - credsEnvVars := []string{ - fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", creds.AccessKeyID), - fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", creds.SecretAccessKey), - fmt.Sprintf("AWS_SESSION_TOKEN=%s", creds.SessionToken), + credsEnvVars = []string{ + fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", creds.AccessKeyID), + fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", creds.SecretAccessKey), + fmt.Sprintf("AWS_SESSION_TOKEN=%s", creds.SessionToken), + } } // check if terraform version is >= 0.9.0 diff --git a/server/server.go b/server/server.go index 84dbaec48..f94eb192e 100644 --- a/server/server.go +++ b/server/server.go @@ -87,9 +87,15 @@ func NewServer(config ServerConfig) (*Server, error) { return nil, errors.Wrap(err, "initializing terraform") } githubComments := &GithubCommentRenderer{} - awsConfig := &aws.Config{ - Region: config.AWSRegion, - RoleARN: config.AssumeRole, + + // a nil awsConfig indicates that we won't be doing any AWS + // config in Atlantis + var awsConfig *aws.Config + if config.AssumeRole != "" { + awsConfig = &aws.Config{ + Region: config.AWSRegion, + RoleARN: config.AssumeRole, + } } boltdb, err := boltdb.New(config.DataDir)