Skip to content

Commit

Permalink
fixed preapply commands and added postapply/postplan (runatlantis#102)
Browse files Browse the repository at this point in the history
* fixed preapply commands and added postapply

* removed duplicate code and pass in stage name. Added post_plan. Updated docs
  • Loading branch information
mootpt authored and anubhavmishra committed Aug 10, 2017
1 parent afb34df commit 6f583aa
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 40 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ Now when Atlantis executes it will use the `terraform{version}` executable.
## Project-Specific Customization
An `atlantis.yaml` config file in your project root (which is not necessarily the repo root) can be used to customize
- what commands Atlantis runs **before** `plan` and `apply` with `pre_plan` and `pre_apply`
- what commands Atlantis runs **after** `plan` and `apply` with `post_plan` and `post_apply`
- additional arguments to be supplied to specific terraform commands with `extra_arguments`
- what version of Terraform to use (see [Terraform Versions](#terraform-versions))

Expand All @@ -158,16 +159,22 @@ terraform_version: 0.8.8 # optional version
pre_plan:
commands:
- "curl http://example.com"
post_plan:
commands:
- "curl http://example.com"
pre_apply:
commands:
- "curl http://example.com"
post_apply:
commands:
- "curl http://example.com"
extra_arguments:
- command: plan
arguments:
- "-tfvars=myvars.tfvars"
```
When running the `pre_plan` and `pre_apply` commands the following environment variables are available
When running the `pre_plan`, `post_plan`, `pre_apply`, and `post_apply` commands the following environment variables are available
- `ENVIRONMENT`: if an environment argument is supplied to `atlantis plan` or `atlantis apply` this will
be the value of that argument. Else it will be `default`
- `ATLANTIS_TERRAFORM_VERSION`: local version of `terraform` or the version from `terraform_version` if specified, ex. `0.10.0`
Expand Down
25 changes: 13 additions & 12 deletions prerun/pre_run.go → run/run.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Package prerun handles running commands prior to the
// Package run handles running commands prior and following the
// regular Atlantis commands.
package prerun
package run

import (
"bufio"
Expand All @@ -17,28 +17,29 @@ import (

const inlineShebang = "#!/bin/sh -e"

type PreRun struct{}
type Run struct{}

// Execute runs the commands by writing them as a script to disk
// and then executing the script.
func (p *PreRun) Execute(
func (p *Run) Execute(
log *logging.SimpleLogger,
commands []string,
path string,
environment string,
terraformVersion *version.Version) (string, error) {
terraformVersion *version.Version,
stage string) (string, error) {
// we create a script from the commands provided
if len(commands) == 0 {
return "", errors.New("prerun commands cannot be empty")
return "", errors.Errorf("%s commands cannot be empty", stage)
}

s, err := createScript(commands)
s, err := createScript(commands, stage)
if err != nil {
return "", err
}
defer os.Remove(s)

log.Info("running prerun commands: %v", commands)
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
Expand All @@ -49,10 +50,10 @@ func (p *PreRun) Execute(
return execute(s)
}

func createScript(cmds []string) (string, error) {
func createScript(cmds []string, stage string) (string, error) {
tmp, err := ioutil.TempFile("/tmp", "atlantis-temp-script")
if err != nil {
return "", errors.Wrap(err, "preparing pre run shell script")
return "", errors.Wrapf(err, "preparing %s shell script", stage)
}

scriptName := tmp.Name()
Expand All @@ -62,7 +63,7 @@ func createScript(cmds []string) (string, error) {
writer.WriteString(fmt.Sprintf("%s\n", inlineShebang))
cmdsJoined := strings.Join(cmds, "\n")
if _, err := writer.WriteString(cmdsJoined); err != nil {
return "", errors.Wrap(err, "preparing pre run")
return "", errors.Wrapf(err, "preparing %s", stage)
}

if err := writer.Flush(); err != nil {
Expand All @@ -71,7 +72,7 @@ func createScript(cmds []string) (string, error) {
tmp.Close()

if err := os.Chmod(scriptName, 0755); err != nil {
return "", errors.Wrap(err, "making pre run script executable")
return "", errors.Wrapf(err, "making %s script executable", stage)
}

return scriptName, nil
Expand Down
20 changes: 10 additions & 10 deletions prerun/pre_run_test.go → run/run_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package prerun
package run

import (
"log"
Expand All @@ -11,33 +11,33 @@ import (
)

var logger = logging.NewSimpleLogger("", log.New(os.Stderr, "", log.LstdFlags), false, logging.Debug)
var preRun = &PreRun{}
var run = &Run{}

func TestPreRunCreateScript_valid(t *testing.T) {
func TestRunCreateScript_valid(t *testing.T) {
cmds := []string{"echo", "date"}
scriptName, err := createScript(cmds)
scriptName, err := createScript(cmds, "post_apply")
Assert(t, scriptName != "", "there should be a script name")
Assert(t, err == nil, "there should not be an error")
}

func TestPreRunExecuteScript_invalid(t *testing.T) {
func TestRunExecuteScript_invalid(t *testing.T) {
cmds := []string{"invalid", "command"}
scriptName, _ := createScript(cmds)
scriptName, _ := createScript(cmds, "post_apply")
_, err := execute(scriptName)
Assert(t, err != nil, "there should be an error")
}

func TestPreRunExecuteScript_valid(t *testing.T) {
func TestRunExecuteScript_valid(t *testing.T) {
cmds := []string{"echo", "date"}
scriptName, _ := createScript(cmds)
scriptName, _ := createScript(cmds, "post_apply")
output, err := execute(scriptName)
Assert(t, err == nil, "there should not be an error")
Assert(t, output != "", "there should be output")
}

func TestPreRun_valid(t *testing.T) {
func TestRun_valid(t *testing.T) {
cmds := []string{"echo", "date"}
version, _ := version.NewVersion("0.8.8")
_, err := preRun.Execute(logger, cmds, "/tmp/atlantis", "staging", version)
_, err := run.Execute(logger, cmds, "/tmp/atlantis", "staging", version, "post_apply")
Ok(t, err)
}
21 changes: 15 additions & 6 deletions server/apply_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/hootsuite/atlantis/github"
"github.com/hootsuite/atlantis/locking"
"github.com/hootsuite/atlantis/models"
"github.com/hootsuite/atlantis/prerun"
"github.com/hootsuite/atlantis/run"
"github.com/hootsuite/atlantis/terraform"
)

Expand All @@ -25,7 +25,7 @@ type ApplyExecutor struct {
githubCommentRenderer *GithubCommentRenderer
lockingClient *locking.Client
requireApproval bool
preRun *prerun.PreRun
run *run.Run
configReader *ConfigReader
concurrentRunLocker *ConcurrentRunLocker
workspace *Workspace
Expand Down Expand Up @@ -117,7 +117,7 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P
var applyExtraArgs []string
var config ProjectConfig
if a.configReader.Exists(absolutePath) {
config, err := a.configReader.Read(absolutePath)
config, err = a.configReader.Read(absolutePath)
if err != nil {
return ProjectResult{Error: err}
}
Expand All @@ -132,6 +132,7 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P
if a.awsConfig != nil {
awsSession, err := a.awsConfig.CreateSession(ctx.User.Username)
if err != nil {
ctx.Log.Err(err.Error())
return ProjectResult{Error: err}
}
creds, err := awsSession.Config.Credentials.Get()
Expand All @@ -156,7 +157,7 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P
}
constraints, _ := version.NewConstraint(">= 0.9.0")
if constraints.Check(terraformVersion) {
ctx.Log.Info("determined that we are running terraform with version >= 0.9.0")
ctx.Log.Info("determined that we are running terraform with version >= 0.9.0. Running version %s", terraformVersion)
_, err := a.terraform.RunInitAndEnv(ctx.Log, absolutePath, tfEnv, config.GetExtraArguments("init"), credsEnvVars, terraformVersion)
if err != nil {
return ProjectResult{Error: err}
Expand All @@ -165,9 +166,9 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P

// if there are pre apply commands then run them
if len(config.PreApply.Commands) > 0 {
_, err := a.preRun.Execute(ctx.Log, config.PreApply.Commands, absolutePath, ctx.Command.Environment, config.TerraformVersion)
_, err := a.run.Execute(ctx.Log, config.PreApply.Commands, absolutePath, tfEnv, terraformVersion, "pre_apply")
if err != nil {
return ProjectResult{Error: err}
return ProjectResult{Error: errors.Wrap(err, "running pre apply commands")}
}
}

Expand All @@ -178,6 +179,14 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P
}
ctx.Log.Info("apply succeeded")

// if there are post apply commands then run them
if len(config.PostApply.Commands) > 0 {
_, err := a.run.Execute(ctx.Log, config.PostApply.Commands, absolutePath, tfEnv, terraformVersion, "post_apply")
if err != nil {
return ProjectResult{Error: errors.Wrap(err, "running post apply commands")}
}
}

return ProjectResult{ApplySuccess: output}
}

Expand Down
18 changes: 13 additions & 5 deletions server/plan_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/hootsuite/atlantis/github"
"github.com/hootsuite/atlantis/locking"
"github.com/hootsuite/atlantis/models"
"github.com/hootsuite/atlantis/prerun"
"github.com/hootsuite/atlantis/run"
"github.com/hootsuite/atlantis/terraform"
"github.com/pkg/errors"
)
Expand All @@ -29,7 +29,7 @@ type PlanExecutor struct {
lockingClient *locking.Client
// LockURL is a function that given a lock id will return a url for lock view
LockURL func(id string) (url string)
preRun *prerun.PreRun
run *run.Run
configReader *ConfigReader
concurrentRunLocker *ConcurrentRunLocker
workspace *Workspace
Expand Down Expand Up @@ -151,13 +151,13 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models.
}
constraints, _ := version.NewConstraint(">= 0.9.0")
if constraints.Check(terraformVersion) {
ctx.Log.Info("determined that we are running terraform with version >= 0.9.0")
ctx.Log.Info("determined that we are running terraform with version >= 0.9.0. Running version %s", terraformVersion)
_, err := p.terraform.RunInitAndEnv(ctx.Log, absolutePath, tfEnv, config.GetExtraArguments("init"), credsEnvVars, terraformVersion)
if err != nil {
return ProjectResult{Error: err}
}
} else {
ctx.Log.Info("determined that we are running terraform with version < 0.9.0")
ctx.Log.Info("determined that we are running terraform with version < 0.9.0. Running version %s", terraformVersion)
terraformGetCmd := append([]string{"get", "-no-color"}, config.GetExtraArguments("get")...)
_, err := p.terraform.RunCommandWithVersion(ctx.Log, absolutePath, terraformGetCmd, nil, terraformVersion)
if err != nil {
Expand All @@ -167,7 +167,7 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models.

// if there are pre plan commands then run them
if len(config.PrePlan.Commands) > 0 {
_, err := p.preRun.Execute(ctx.Log, config.PrePlan.Commands, absolutePath, tfEnv, terraformVersion)
_, err := p.run.Execute(ctx.Log, config.PrePlan.Commands, absolutePath, tfEnv, terraformVersion, "pre_plan")
if err != nil {
return ProjectResult{Error: errors.Wrap(err, "running pre plan commands")}
}
Expand All @@ -192,6 +192,14 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models.
}
ctx.Log.Info("plan succeeded")

// if there are post plan commands then run them
if len(config.PostPlan.Commands) > 0 {
_, err := p.run.Execute(ctx.Log, config.PostPlan.Commands, absolutePath, tfEnv, terraformVersion, "post_plan")
if err != nil {
return ProjectResult{Error: errors.Wrap(err, "running post plan commands")}
}
}

return ProjectResult{
PlanSuccess: &PlanSuccess{
TerraformOutput: output,
Expand Down
18 changes: 16 additions & 2 deletions server/project_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,34 @@ type PrePlan struct {
Commands []string `yaml:"commands"`
}

type PostPlan struct {
Commands []string `yaml:"commands"`
}

type PreApply struct {
Commands []string `yaml:"commands"`
}

type PostApply struct {
Commands []string `yaml:"commands"`
}

type ConfigReader struct{}

type ProjectConfigYaml struct {
PrePlan PrePlan `yaml:"pre_plan"`
PostPlan PostPlan `yaml:"post_plan"`
PreApply PreApply `yaml:"pre_apply"`
PostApply PostApply `yaml:"post_apply"`
TerraformVersion string `yaml:"terraform_version"`
ExtraArguments []CommandExtraArguments `yaml:"extra_arguments"`
}

type ProjectConfig struct {
PrePlan PrePlan
PreApply PreApply
PrePlan PrePlan
PostPlan PostPlan
PreApply PreApply
PostApply PostApply
// TerraformVersion is the version specified in the config file or nil if version wasn't specified
TerraformVersion *version.Version
ExtraArguments []CommandExtraArguments
Expand Down Expand Up @@ -70,8 +82,10 @@ func (c *ConfigReader) Read(execPath string) (ProjectConfig, error) {
return ProjectConfig{
TerraformVersion: v,
ExtraArguments: pcYaml.ExtraArguments,
PostApply: pcYaml.PostApply,
PreApply: pcYaml.PreApply,
PrePlan: pcYaml.PrePlan,
PostPlan: pcYaml.PostPlan,
}, nil
}

Expand Down
4 changes: 4 additions & 0 deletions server/project_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ var tempConfigFile = "/tmp/" + ProjectConfigFile
var projectConfigFileStr = `
---
terraform_version: "0.0.1"
post_apply:
commands:
- "echo"
- "date"
pre_apply:
commands:
- "echo"
Expand Down
8 changes: 4 additions & 4 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"github.com/hootsuite/atlantis/locking/boltdb"
"github.com/hootsuite/atlantis/logging"
"github.com/hootsuite/atlantis/models"
"github.com/hootsuite/atlantis/prerun"
"github.com/hootsuite/atlantis/run"
"github.com/hootsuite/atlantis/terraform"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
Expand Down Expand Up @@ -103,7 +103,7 @@ func NewServer(config ServerConfig) (*Server, error) {
return nil, err
}
lockingClient := locking.NewClient(boltdb)
preRun := &prerun.PreRun{}
run := &run.Run{}
configReader := &ConfigReader{}
concurrentRunLocker := NewConcurrentRunLocker()
workspace := &Workspace{
Expand All @@ -117,7 +117,7 @@ func NewServer(config ServerConfig) (*Server, error) {
githubCommentRenderer: githubComments,
lockingClient: lockingClient,
requireApproval: config.RequireApproval,
preRun: preRun,
run: run,
configReader: configReader,
concurrentRunLocker: concurrentRunLocker,
workspace: workspace,
Expand All @@ -129,7 +129,7 @@ func NewServer(config ServerConfig) (*Server, error) {
terraform: terraformClient,
githubCommentRenderer: githubComments,
lockingClient: lockingClient,
preRun: preRun,
run: run,
configReader: configReader,
concurrentRunLocker: concurrentRunLocker,
workspace: workspace,
Expand Down

0 comments on commit 6f583aa

Please sign in to comment.