Skip to content

Commit

Permalink
Merge pull request runatlantis#87 from hootsuite/readme
Browse files Browse the repository at this point in the history
Add Getting Started instructions. Rename gh-password to gh-token
  • Loading branch information
lkysow authored Jul 30, 2017
2 parents 4b3aaa2 + 509c511 commit 60d7db9
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 26 deletions.
109 changes: 105 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -35,17 +130,23 @@ 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.

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.

8 changes: 4 additions & 4 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
Expand Down Expand Up @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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_PASSWORD" --data-dir="/tmp" --require-approval=false --log-level="debug" &> /tmp/atlantis-server.log:
background: true
- sleep 2
- cd "${WORKDIR}/e2e" && ./ngrok http 4141:
Expand Down
14 changes: 7 additions & 7 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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{
Expand Down Expand Up @@ -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
}
Expand Down
11 changes: 8 additions & 3 deletions e2e/e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ func (t *E2ETester) Start() (*E2EResult, error) {

state := "not started"
// waiting for atlantis run and finish
for i := 0; i < 20 && checkStatus(state); i++ {
maxLoops := 20
i := 0
for ; i < maxLoops && checkStatus(state); i++ {
time.Sleep(2 * time.Second)
state, _ = getAtlantisStatus(t, branchName)
if state == "" {
Expand All @@ -134,12 +136,15 @@ func (t *E2ETester) Start() (*E2EResult, error) {
}
log.Printf("atlantis run is in %s state", state)
}
if i == maxLoops {
state = "timed out"
}

log.Printf("atlantis run finished with %s status", state)
log.Printf("atlantis run finished with status %q", state)
e2eResult.testResult = state
// check if atlantis run was a success
if state != "success" {
return e2eResult, fmt.Errorf("atlantis run project type %q failed with %s status", t.projectType.Name, state)
return e2eResult, fmt.Errorf("atlantis run project type %q failed with %q status", t.projectType.Name, state)
}

return e2eResult, nil
Expand Down
8 changes: 4 additions & 4 deletions e2e/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func main() {
if githubUsername == "" {
log.Fatalf("GITHUB_USERNAME cannot be empty")
}
githubPassword := os.Getenv("GITHUB_PASSWORD")
if githubPassword == "" {
githubToken := os.Getenv("GITHUB_PASSWORD")
if githubToken == "" {
log.Fatalf("GITHUB_PASSWORD cannot be empty")
}
atlantisURL := os.Getenv("ATLANTIS_URL")
Expand All @@ -49,7 +49,7 @@ func main() {
repoName = "atlantis-tests"
}
// using https to clone the repo
repoUrl := fmt.Sprintf("https://%s:%[email protected]/%s/%s.git", githubUsername, githubPassword, ownerName, repoName)
repoUrl := fmt.Sprintf("https://%s:%[email protected]/%s/%s.git", githubUsername, githubToken, ownerName, repoName)
cloneDirRoot := os.Getenv("CLONE_DIR")
if cloneDirRoot == "" {
cloneDirRoot = "/tmp/atlantis-tests"
Expand All @@ -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())

Expand Down
4 changes: 2 additions & 2 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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
}
Expand Down

0 comments on commit 60d7db9

Please sign in to comment.