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