From e728d2548bb041358b08b0fe43c2fc7d96fbf5f2 Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Thu, 27 Jul 2017 13:51:48 -0700 Subject: [PATCH] Add Getting Started instructions. Rename gh-password to gh-token --- README.md | 109 +++++++++++++++++++++++++++++++++++++++-- bootstrap/bootstrap.go | 8 +-- bootstrap/github.go | 2 +- circle.yml | 2 +- cmd/server.go | 14 +++--- e2e/main.go | 10 ++-- server/server.go | 4 +- 7 files changed, 125 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 5a1abac94..7fa7fb184 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,108 @@ A unified workflow for collaborating on Terraform through GitHub. ## Features -- +➜ Collaborate on Terraform with your team +- Run terraform `plan` and `apply` **from GitHub pull requests** so everyone can review the output +- todo: gif +- **Lock environments** 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 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 +- Support **multiple versions of Terraform** with a simple project config file ## Getting Started -// todo: atlantis bootstrap workflow +Atlantis runs as a server that receives GitHub webhooks. Once it's running and hooked up with GitHub, you can interact with it directly through GitHub comments. + +### First Download Atlantis +Download from https://github.com/hootsuite/atlantis/releases + +### Start with `atlantis bootstrap` (recommended) +Run `atlantis bootstrap` to get started quickly with Atlantis. + +If you want to manually run through all the steps that `bootstrap` performs, keep reading. + +### Start Manually +To manually get started with Atlantis, you'll need to +- install `terraform` into your `$PATH` + - download from https://www.terraform.io/downloads.html + - `unzip path/to/terraform_*.zip -d /usr/local/bin` + - check that it's installed by running `terraform version` +- Atlantis needs to be reachable on an IP address or hostname that github.com can access. By default, Atlantis runs on port `4141` (this can be changed with the `--port` flag). You can install `ngrok` to make exposing Atlantis easy for testing purposes + - download from https://ngrok.com/download + - `unzip path/to/ngrok*.zip -d /usr/local/bin` + - start ngrok with `ngrok http 4141` +- Create a GitHub personal access token for Atlantis to use GitHub's API + - follow [https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/#creating-a-token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/#creating-a-token) + - copy the access token to your clipboard +- now you're ready to start Atlantis! Run: +``` +$ atlantis server --atlantis-url $URL --gh-username $USERNAME --gh-token $TOKEN +2049/10/6 00:00:00 [WARN] server: Atlantis started - listening on port 4141 +``` + +- where `$URL` is the URL that Atlantis can be reached at. If using `ngrok` it will be something like `https://68da2fdd.ngrok.io` +- where `$USERNAME` is your GitHub username +- where `$TOKEN` is the access token you created + +Now that Atlantis is running, it's time to test it out. You'll need to set up a pull request first + +- Fork https://github.com/hootsuite/atlantis-example to your user +- Add Atlantis as a webhook to the forked repo + - navigate to `{your-repo-url}/settings/hooks/new`, ex. https://github.com/hootsuite/atlantis-example/settings/hooks/new + - set **Payload URL** to `$URL/events` where `$URL` is what you used above, ex. `https://68da2fdd.ngrok.io/events`. **Be sure to add `/events` to the end** + - set **Content type** to `application/json` + - leave **Secret** blank + - select **Let me select individual events** + - check the boxes + - **Pull request review** + - **Push** + - **Issue comment** + - **Pull request** + - leave **Active** checked + - click **Add webhook** +- Now that Atlantis can receive events you should be able to comment on a pull request to trigger Atlantis. Let's create a pull request + - Navigate to `{your-repo-url}/branches`, ex. https://github.com/hootsuite/atlantis-example/branches + - click the **New pull request** button next to the `example` branch + - Change the `base` to `{your-repo}/master` + - click **Create pull request** +- Finally we're ready to talk to Atlantis! + - Create a comment `atlantis help` to see what commands you can run from the pull request + - `atlantis plan` will run `terraform plan` behind the scenes. You should see the output commented back on the pull request. You should also see some logs show up where you're running `atlantis server` + - You could also type `atlantis apply` but since you may not have AWS credentials set up this probably won't work TODO: VERIFY THIS + +You're done! If you're ready to set up Atlantis for a production deployment, see [Production-Ready Deployment](#Production-Ready+Deployment) + + +## Production-Ready Deployment ## Configuration -Atlantis configuration can be specified via cli flags or a yaml config file. +Atlantis configuration can be specified via command line flags or a YAML config file. + +``` +$ atlantis server --help +... +Usage: + atlantis server [flags] + +Flags: + --atlantis-url string Url that Atlantis can be reached at. Defaults to http://$(hostname):$port where $port comes from the port flag. + --aws-assume-role-arn string ARN of the role to assume when running Terraform against AWS. If not using assume role, no need to set. + --aws-region string The Amazon region to connect to for API actions. (default "us-east-1") + --config string Path to config file. + --data-dir string Path to directory to store Atlantis data. (default "~/.atlantis") + --gh-hostname string Hostname of your Github Enterprise installation. If using github.com, no need to set. (default "github.com") + --gh-password string [REQUIRED] GitHub password of API user. Can also be specified via the ATLANTIS_GH_PASSWORD environment variable. + --gh-user string [REQUIRED] GitHub username of API user. + -h, --help help for server + --log-level string Log level. Either debug, info, warn, or error. (default "warn") + --port int Port to bind to. (default 4141) + --require-approval Require pull requests to be "Approved" before allowing the apply command to be run. (default true) +``` + The `gh-password` flag can also be specified via an `ATLANTIS_GH_PASSWORD` environment variable. Config file values are overridden by environment variables which in turn are overridden by flags. @@ -35,7 +130,10 @@ to assume a role (specified by the `--aws-assume-role-arn` flag) and **dynamical name the session** with the GitHub username of whoever is running `atlantis apply`. To take advantage of this feature, simply set the `--aws-assume-role-arn` flag. -## Project Locking +## Environments + + +## Locking When `plan` is run, the project and environment are Locked until an `apply` succeeds and the pull 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. @@ -43,9 +141,12 @@ users from seeing a `plan` that will be invalid if another pull request is merge To unlock the project and environment without completing an `apply`, click the link at the bottom of each plan to discard the plan and delete the lock. +## `atlantis.yaml` Config File + ## Glossary #### Project A Terraform project. Multiple projects can be in a single GitHub repo. #### Environment A Terraform environment. See [terraform docs](https://www.terraform.io/docs/state/environments.html) for more information. + diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index c18a3cbc0..bda81ee8b 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -56,14 +56,14 @@ Follow these instructions to create a token (we don't store any tokens): - add "repo" scope - copy the access token `) - // read github password, check for error later + // read github token, check for error later colorstring.Print("[white][bold]GitHub access token (will be hidden): ") - githubPassword, _ = readPassword() + githubToken, _ = readPassword() // create github client tp := github.BasicAuthTransport{ Username: strings.TrimSpace(githubUsername), - Password: strings.TrimSpace(githubPassword), + Password: strings.TrimSpace(githubToken), } githubClient := &Client{client: github.NewClient(tp.Client()), ctx: context.Background()} @@ -137,7 +137,7 @@ Follow these instructions to create a token (we don't store any tokens): // start atlantis server colorstring.Printf("[white]=> starting atlantis server ") s.Start() - atlantisCmd, err := executeCmd("./atlantis", []string{"server", "--gh-user", githubUsername, "--gh-password", githubPassword, "--data-dir", "/tmp/atlantis/data"}) + atlantisCmd, err := executeCmd("./atlantis", []string{"server", "--gh-user", githubUsername, "--gh-token", githubToken, "--data-dir", "/tmp/atlantis/data"}) if err != nil { return errors.Wrapf(err, "creating atlantis server") } diff --git a/bootstrap/github.go b/bootstrap/github.go index ed29fdf20..3f1a676cf 100644 --- a/bootstrap/github.go +++ b/bootstrap/github.go @@ -10,7 +10,7 @@ import ( var githubHostname = "http://api.github.com" var githubUsername string -var githubPassword string +var githubToken string // Client used for github interactions type Client struct { diff --git a/circle.yml b/circle.yml index a7aa63ce3..f7cd7c0f7 100644 --- a/circle.yml +++ b/circle.yml @@ -26,7 +26,7 @@ test: # Run e2e tests - cd "${WORKDIR}" && ./scripts/e2e-deps.sh # Start atlantis server - - cd "${WORKDIR}/e2e" && ./atlantis server --gh-user="$GITHUB_USERNAME" --gh-password="$GITHUB_PASSWORD" --data-dir="/tmp" --require-approval=false --log-level="debug" &> /tmp/atlantis-server.log: + - cd "${WORKDIR}/e2e" && ./atlantis server --gh-user="$GITHUB_USERNAME" --gh-token="$GITHUB_TOKEN" --data-dir="/tmp" --require-approval=false --log-level="debug" &> /tmp/atlantis-server.log: background: true - sleep 2 - cd "${WORKDIR}/e2e" && ./ngrok http 4141: diff --git a/cmd/server.go b/cmd/server.go index ff83cbee2..981dba03d 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -23,7 +23,7 @@ const ( configFlag = "config" dataDirFlag = "data-dir" ghHostnameFlag = "gh-hostname" - ghPasswordFlag = "gh-password" + ghTokenFlag = "gh-token" ghUserFlag = "gh-user" logLevelFlag = "log-level" portFlag = "port" @@ -59,9 +59,9 @@ var stringFlags = []stringFlag{ value: "github.com", }, { - name: ghPasswordFlag, - description: "[REQUIRED] GitHub password of API user. Can also be specified via the ATLANTIS_GH_PASSWORD environment variable.", - env: "ATLANTIS_GH_PASSWORD", + name: ghTokenFlag, + description: "[REQUIRED] GitHub token of API user. Can also be specified via the ATLANTIS_GH_TOKEN environment variable.", + env: "ATLANTIS_GH_TOKEN", }, { name: ghUserFlag, @@ -70,7 +70,7 @@ var stringFlags = []stringFlag{ { name: logLevelFlag, description: "Log level. Either debug, info, warn, or error.", - value: "warn", + value: "info", }, } var boolFlags = []boolFlag{ @@ -186,8 +186,8 @@ func validate(config server.ServerConfig) error { if config.GithubUser == "" { return fmt.Errorf("--%s must be set", ghUserFlag) } - if config.GithubPassword == "" { - return fmt.Errorf("--%s must be set", ghPasswordFlag) + if config.GithubToken == "" { + return fmt.Errorf("--%s must be set", ghTokenFlag) } return nil } diff --git a/e2e/main.go b/e2e/main.go index c1b4f0502..07c640313 100644 --- a/e2e/main.go +++ b/e2e/main.go @@ -30,9 +30,9 @@ func main() { if githubUsername == "" { log.Fatalf("GITHUB_USERNAME cannot be empty") } - githubPassword := os.Getenv("GITHUB_PASSWORD") - if githubPassword == "" { - log.Fatalf("GITHUB_PASSWORD cannot be empty") + githubToken := os.Getenv("GITHUB_TOKEN") + if githubToken == "" { + log.Fatalf("GITHUB_TOKEN cannot be empty") } atlantisURL := os.Getenv("ATLANTIS_URL") if atlantisURL == "" { @@ -49,7 +49,7 @@ func main() { repoName = "atlantis-tests" } // using https to clone the repo - repoUrl := fmt.Sprintf("https://%s:%s@github.com/%s/%s.git", githubUsername, githubPassword, ownerName, repoName) + repoUrl := fmt.Sprintf("https://%s:%s@github.com/%s/%s.git", githubUsername, githubToken, ownerName, repoName) cloneDirRoot := os.Getenv("CLONE_DIR") if cloneDirRoot == "" { cloneDirRoot = "/tmp/atlantis-tests" @@ -65,7 +65,7 @@ func main() { // create github client tp := github.BasicAuthTransport{ Username: strings.TrimSpace(githubUsername), - Password: strings.TrimSpace(githubPassword), + Password: strings.TrimSpace(githubToken), } ghClient := github.NewClient(tp.Client()) diff --git a/server/server.go b/server/server.go index ccf442000..84dbaec48 100644 --- a/server/server.go +++ b/server/server.go @@ -50,7 +50,7 @@ type ServerConfig struct { AtlantisURL string `mapstructure:"atlantis-url"` DataDir string `mapstructure:"data-dir"` GithubHostname string `mapstructure:"gh-hostname"` - GithubPassword string `mapstructure:"gh-password"` + GithubToken string `mapstructure:"gh-token"` GithubUser string `mapstructure:"gh-user"` LogLevel string `mapstructure:"log-level"` Port int `mapstructure:"port"` @@ -77,7 +77,7 @@ func NewServer(config ServerConfig) (*Server, error) { config.DataDir = expanded } - githubClient, err := github.NewClient(config.GithubHostname, config.GithubUser, config.GithubPassword) + githubClient, err := github.NewClient(config.GithubHostname, config.GithubUser, config.GithubToken) if err != nil { return nil, err }