Jacob McCann
8 years ago
4 changed files with 287 additions and 166 deletions
@ -1,188 +1,115 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"github.com/aws/aws-sdk-go/aws/credentials" |
|||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds" |
|||
"github.com/aws/aws-sdk-go/aws/session" |
|||
"github.com/aws/aws-sdk-go/service/sts" |
|||
"github.com/drone/drone-plugin-go/plugin" |
|||
"io/ioutil" |
|||
"encoding/json" |
|||
"os" |
|||
"os/exec" |
|||
"strings" |
|||
"time" |
|||
) |
|||
|
|||
var ( |
|||
buildCommit string |
|||
"github.com/joho/godotenv" |
|||
"github.com/Sirupsen/logrus" |
|||
"github.com/urfave/cli" |
|||
) |
|||
|
|||
type terraform struct { |
|||
Remote remote `json:"remote"` |
|||
Plan bool `json:"plan"` |
|||
Vars map[string]string `json:"vars"` |
|||
Cacert string `json:"ca_cert"` |
|||
Sensitive bool `json:"sensitive"` |
|||
RoleARN string `json:"role_arn_to_assume"` |
|||
RootDir string `json:"root_dir"` |
|||
Parallelism int `json:"parallelism"` |
|||
} |
|||
|
|||
type remote struct { |
|||
Backend string `json:"backend"` |
|||
Config map[string]string `json:"config"` |
|||
} |
|||
var version string // build number set at compile-time
|
|||
|
|||
func main() { |
|||
fmt.Printf("Drone Terraform Plugin built from %s\n", buildCommit) |
|||
|
|||
workspace := plugin.Workspace{} |
|||
vargs := terraform{} |
|||
|
|||
plugin.Param("workspace", &workspace) |
|||
plugin.Param("vargs", &vargs) |
|||
plugin.MustParse() |
|||
|
|||
if vargs.RoleARN != "" { |
|||
assumeRole(vargs.RoleARN) |
|||
app := cli.NewApp() |
|||
app.Name = "terraform plugin" |
|||
app.Usage = "terraform plugin" |
|||
app.Action = run |
|||
app.Version = version |
|||
app.Flags = []cli.Flag{ |
|||
|
|||
//
|
|||
// plugin args
|
|||
//
|
|||
|
|||
cli.BoolFlag{ |
|||
Name: "terraform.plan", |
|||
Usage: "calculates a plan but does NOT apply it", |
|||
EnvVar: "PLUGIN_PLAN", |
|||
}, |
|||
cli.StringFlag{ |
|||
Name: "terraform.remote", |
|||
Usage: "contains the configuration for the Terraform remote state tracking", |
|||
EnvVar: "PLUGIN_REMOTE", |
|||
}, |
|||
cli.StringFlag{ |
|||
Name: "terraform.vars", |
|||
Usage: "a map of variables to pass to the Terraform `plan` and `apply` commands. Each value is passed as a `<key>=<value>` option", |
|||
EnvVar: "PLUGIN_VARS", |
|||
}, |
|||
cli.StringFlag{ |
|||
Name: "terraform.secrets", |
|||
Usage: "a map of secrets to pass to the Terraform `plan` and `apply` commands. Each value is passed as a `<key>=<ENV>` option", |
|||
EnvVar: "PLUGIN_SECRETS", |
|||
}, |
|||
cli.StringFlag{ |
|||
Name: "terraform.ca_cert", |
|||
Usage: "ca cert to add to your environment to allow terraform to use internal/private resources", |
|||
EnvVar: "PLUGIN_CA_CERT", |
|||
}, |
|||
cli.BoolFlag{ |
|||
Name: "terraform.sensitive", |
|||
Usage: "whether or not to suppress terraform commands to stdout", |
|||
EnvVar: "PLUGIN_SENSITIVE", |
|||
}, |
|||
cli.StringFlag{ |
|||
Name: "terraform.role_arn_to_assume", |
|||
Usage: "A role to assume before running the terraform commands", |
|||
EnvVar: "PLUGIN_ROLE_ARN_TO_ASSUME", |
|||
}, |
|||
cli.StringFlag{ |
|||
Name: "terraform.root_dir", |
|||
Usage: "The root directory where the terraform files live. When unset, the top level directory will be assumed", |
|||
EnvVar: "PLUGIN_ROOT_DIR", |
|||
}, |
|||
cli.IntFlag{ |
|||
Name: "terraform.parallelism", |
|||
Usage: "The number of concurrent operations as Terraform walks its graph", |
|||
EnvVar: "PLUGIN_PARALLELISM", |
|||
}, |
|||
|
|||
cli.StringFlag{ |
|||
Name: "env-file", |
|||
Usage: "source env file", |
|||
}, |
|||
} |
|||
|
|||
var commands []*exec.Cmd |
|||
remote := vargs.Remote |
|||
if vargs.Cacert != "" { |
|||
commands = append(commands, installCaCert(vargs.Cacert)) |
|||
} |
|||
if remote.Backend != "" { |
|||
commands = append(commands, deleteCache()) |
|||
commands = append(commands, remoteConfigCommand(remote)) |
|||
if err := app.Run(os.Args); err != nil { |
|||
logrus.Fatal(err) |
|||
} |
|||
commands = append(commands, getModules()) |
|||
commands = append(commands, planCommand(vargs.Vars, vargs.Parallelism)) |
|||
if !vargs.Plan { |
|||
commands = append(commands, applyCommand(vargs.Parallelism)) |
|||
} |
|||
commands = append(commands, deleteCache()) |
|||
|
|||
for _, c := range commands { |
|||
c.Env = os.Environ() |
|||
c.Dir = workspace.Path |
|||
if c.Dir == "" { |
|||
wd, err := os.Getwd() |
|||
if err == nil { |
|||
c.Dir = wd |
|||
} |
|||
} |
|||
if vargs.RootDir != "" { |
|||
c.Dir = c.Dir + "/" + vargs.RootDir |
|||
} |
|||
c.Stdout = os.Stdout |
|||
c.Stderr = os.Stderr |
|||
if !vargs.Sensitive { |
|||
trace(c) |
|||
} |
|||
|
|||
err := c.Run() |
|||
if err != nil { |
|||
fmt.Println("Error!") |
|||
fmt.Println(err) |
|||
os.Exit(1) |
|||
} |
|||
fmt.Println("Command completed successfully") |
|||
} |
|||
|
|||
} |
|||
|
|||
func installCaCert(cacert string) *exec.Cmd { |
|||
ioutil.WriteFile("/usr/local/share/ca-certificates/ca_cert.crt", []byte(cacert), 0644) |
|||
return exec.Command( |
|||
"update-ca-certificates", |
|||
) |
|||
} |
|||
|
|||
func deleteCache() *exec.Cmd { |
|||
return exec.Command( |
|||
"rm", |
|||
"-rf", |
|||
".terraform", |
|||
) |
|||
} |
|||
|
|||
func remoteConfigCommand(config remote) *exec.Cmd { |
|||
args := []string{ |
|||
"remote", |
|||
"config", |
|||
fmt.Sprintf("-backend=%s", config.Backend), |
|||
func run(c *cli.Context) error { |
|||
if c.String("env-file") != "" { |
|||
_ = godotenv.Load(c.String("env-file")) |
|||
} |
|||
for k, v := range config.Config { |
|||
args = append(args, fmt.Sprintf("-backend-config=%s=%s", k, v)) |
|||
} |
|||
return exec.Command( |
|||
"terraform", |
|||
args..., |
|||
) |
|||
} |
|||
|
|||
func getModules() *exec.Cmd { |
|||
return exec.Command( |
|||
"terraform", |
|||
"get", |
|||
) |
|||
} |
|||
|
|||
func planCommand(variables map[string]string, parallelism int) *exec.Cmd { |
|||
args := []string{ |
|||
"plan", |
|||
"-out=plan.tfout", |
|||
} |
|||
for k, v := range variables { |
|||
args = append(args, "-var") |
|||
args = append(args, fmt.Sprintf("%s=%s", k, v)) |
|||
} |
|||
if parallelism > 0 { |
|||
args = append(args, fmt.Sprintf("-parallelism=%d", parallelism)) |
|||
} |
|||
return exec.Command( |
|||
"terraform", |
|||
args..., |
|||
) |
|||
} |
|||
remote := Remote{} |
|||
json.Unmarshal([]byte(c.String("terraform.remote")), &remote) |
|||
|
|||
func applyCommand(parallelism int) *exec.Cmd { |
|||
args := []string{ |
|||
"apply", |
|||
var vars map[string]string |
|||
if err := json.Unmarshal([]byte(c.String("terraform.vars")), &vars); err != nil { |
|||
panic(err) |
|||
} |
|||
if parallelism > 0 { |
|||
args = append(args, fmt.Sprintf("-parallelism=%d", parallelism)) |
|||
var secrets map[string]string |
|||
if err := json.Unmarshal([]byte(c.String("terraform.secrets")), &secrets); err != nil { |
|||
panic(err) |
|||
} |
|||
args = append(args, "plan.tfout") |
|||
return exec.Command( |
|||
"terraform", |
|||
args..., |
|||
) |
|||
} |
|||
|
|||
func assumeRole(roleArn string) { |
|||
client := sts.New(session.New()) |
|||
duration := time.Hour * 1 |
|||
stsProvider := &stscreds.AssumeRoleProvider{ |
|||
Client: client, |
|||
Duration: duration, |
|||
RoleARN: roleArn, |
|||
RoleSessionName: "drone", |
|||
plugin := Plugin{ |
|||
Config: Config{ |
|||
Remote: remote, |
|||
Plan: c.Bool("terraform.plan"), |
|||
Vars: vars, |
|||
Secrets: secrets, |
|||
Cacert: c.String("terraform.ca_cert"), |
|||
Sensitive: c.Bool("terraform.sensitive"), |
|||
RoleARN: c.String("terraform.role_arn_to_assume"), |
|||
RootDir: c.String("terraform.root_dir"), |
|||
Parallelism: c.Int("terraform.parallelism"), |
|||
}, |
|||
} |
|||
|
|||
value, err := credentials.NewCredentials(stsProvider).Get() |
|||
if err != nil { |
|||
fmt.Println("Error assuming role!") |
|||
fmt.Println(err) |
|||
os.Exit(1) |
|||
} |
|||
os.Setenv("AWS_ACCESS_KEY_ID", value.AccessKeyID) |
|||
os.Setenv("AWS_SECRET_ACCESS_KEY", value.SecretAccessKey) |
|||
os.Setenv("AWS_SESSION_TOKEN", value.SessionToken) |
|||
} |
|||
|
|||
func trace(cmd *exec.Cmd) { |
|||
fmt.Println("$", strings.Join(cmd.Args, " ")) |
|||
return plugin.Exec() |
|||
} |
|||
|
@ -0,0 +1,190 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"github.com/aws/aws-sdk-go/aws/credentials" |
|||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds" |
|||
"github.com/aws/aws-sdk-go/aws/session" |
|||
"github.com/aws/aws-sdk-go/service/sts" |
|||
"io/ioutil" |
|||
"os" |
|||
"os/exec" |
|||
"strings" |
|||
"time" |
|||
) |
|||
|
|||
var ( |
|||
buildCommit string |
|||
) |
|||
|
|||
type ( |
|||
Config struct { |
|||
Remote Remote |
|||
Plan bool |
|||
Vars map[string]string |
|||
Secrets map[string]string |
|||
Cacert string |
|||
Sensitive bool |
|||
RoleARN string |
|||
RootDir string |
|||
Parallelism int |
|||
} |
|||
|
|||
Remote struct { |
|||
Backend string `json:"backend"` |
|||
Config map[string]string `json:"config"` |
|||
} |
|||
|
|||
Plugin struct { |
|||
Config Config |
|||
} |
|||
) |
|||
|
|||
func (p Plugin) Exec() error { |
|||
fmt.Printf("Drone Terraform Plugin built from %s\n", buildCommit) |
|||
|
|||
if p.Config.RoleARN != "" { |
|||
assumeRole(p.Config.RoleARN) |
|||
} |
|||
|
|||
var commands []*exec.Cmd |
|||
remote := p.Config.Remote |
|||
if p.Config.Cacert != "" { |
|||
commands = append(commands, installCaCert(p.Config.Cacert)) |
|||
} |
|||
if remote.Backend != "" { |
|||
commands = append(commands, deleteCache()) |
|||
commands = append(commands, remoteConfigCommand(remote)) |
|||
} |
|||
commands = append(commands, getModules()) |
|||
commands = append(commands, planCommand(p.Config.Vars, p.Config.Secrets, p.Config.Parallelism)) |
|||
if !p.Config.Plan { |
|||
commands = append(commands, applyCommand(p.Config.Parallelism)) |
|||
} |
|||
commands = append(commands, deleteCache()) |
|||
|
|||
for _, c := range commands { |
|||
if c.Dir == "" { |
|||
wd, err := os.Getwd() |
|||
if err == nil { |
|||
c.Dir = wd |
|||
} |
|||
} |
|||
if p.Config.RootDir != "" { |
|||
c.Dir = c.Dir + "/" + p.Config.RootDir |
|||
} |
|||
c.Stdout = os.Stdout |
|||
c.Stderr = os.Stderr |
|||
if !p.Config.Sensitive { |
|||
trace(c) |
|||
} |
|||
|
|||
err := c.Run() |
|||
if err != nil { |
|||
fmt.Println("Error!") |
|||
fmt.Println(err) |
|||
os.Exit(1) |
|||
} |
|||
fmt.Println("Command completed successfully") |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func installCaCert(cacert string) *exec.Cmd { |
|||
ioutil.WriteFile("/usr/local/share/ca-certificates/ca_cert.crt", []byte(cacert), 0644) |
|||
return exec.Command( |
|||
"update-ca-certificates", |
|||
) |
|||
} |
|||
|
|||
func deleteCache() *exec.Cmd { |
|||
return exec.Command( |
|||
"rm", |
|||
"-rf", |
|||
".terraform", |
|||
) |
|||
} |
|||
|
|||
func remoteConfigCommand(config Remote) *exec.Cmd { |
|||
args := []string{ |
|||
"remote", |
|||
"config", |
|||
fmt.Sprintf("-backend=%s", config.Backend), |
|||
} |
|||
for k, v := range config.Config { |
|||
args = append(args, fmt.Sprintf("-backend-config=%s=%s", k, v)) |
|||
} |
|||
return exec.Command( |
|||
"terraform", |
|||
args..., |
|||
) |
|||
} |
|||
|
|||
func getModules() *exec.Cmd { |
|||
return exec.Command( |
|||
"terraform", |
|||
"get", |
|||
) |
|||
} |
|||
|
|||
func planCommand(variables map[string]string, secrets map[string]string, parallelism int) *exec.Cmd { |
|||
args := []string{ |
|||
"plan", |
|||
"-out=plan.tfout", |
|||
} |
|||
for k, v := range variables { |
|||
args = append(args, "-var") |
|||
args = append(args, fmt.Sprintf("%s=%s", k, v)) |
|||
} |
|||
for k, v := range secrets { |
|||
args = append(args, "-var") |
|||
args = append(args, fmt.Sprintf("%s=%s", k, os.Getenv(v))) |
|||
} |
|||
if parallelism > 0 { |
|||
args = append(args, fmt.Sprintf("-parallelism=%d", parallelism)) |
|||
} |
|||
return exec.Command( |
|||
"terraform", |
|||
args..., |
|||
) |
|||
} |
|||
|
|||
func applyCommand(parallelism int) *exec.Cmd { |
|||
args := []string{ |
|||
"apply", |
|||
} |
|||
if parallelism > 0 { |
|||
args = append(args, fmt.Sprintf("-parallelism=%d", parallelism)) |
|||
} |
|||
args = append(args, "plan.tfout") |
|||
return exec.Command( |
|||
"terraform", |
|||
args..., |
|||
) |
|||
} |
|||
|
|||
func assumeRole(roleArn string) { |
|||
client := sts.New(session.New()) |
|||
duration := time.Hour * 1 |
|||
stsProvider := &stscreds.AssumeRoleProvider{ |
|||
Client: client, |
|||
Duration: duration, |
|||
RoleARN: roleArn, |
|||
RoleSessionName: "drone", |
|||
} |
|||
|
|||
value, err := credentials.NewCredentials(stsProvider).Get() |
|||
if err != nil { |
|||
fmt.Println("Error assuming role!") |
|||
fmt.Println(err) |
|||
os.Exit(1) |
|||
} |
|||
os.Setenv("AWS_ACCESS_KEY_ID", value.AccessKeyID) |
|||
os.Setenv("AWS_SECRET_ACCESS_KEY", value.SecretAccessKey) |
|||
os.Setenv("AWS_SESSION_TOKEN", value.SessionToken) |
|||
} |
|||
|
|||
func trace(cmd *exec.Cmd) { |
|||
fmt.Println("$", strings.Join(cmd.Args, " ")) |
|||
} |
Loading…
Reference in new issue