|
|
@ -5,6 +5,8 @@ import ( |
|
|
|
"io/ioutil" |
|
|
|
"os" |
|
|
|
"os/exec" |
|
|
|
"os/user" |
|
|
|
"path/filepath" |
|
|
|
"regexp" |
|
|
|
"strings" |
|
|
|
"time" |
|
|
@ -19,7 +21,7 @@ import ( |
|
|
|
type ( |
|
|
|
// Config holds input parameters for the plugin
|
|
|
|
Config struct { |
|
|
|
Plan bool |
|
|
|
Actions []string |
|
|
|
Vars map[string]string |
|
|
|
Secrets map[string]string |
|
|
|
InitOptions InitOptions |
|
|
@ -30,7 +32,12 @@ type ( |
|
|
|
Parallelism int |
|
|
|
Targets []string |
|
|
|
VarFiles []string |
|
|
|
Destroy bool |
|
|
|
} |
|
|
|
|
|
|
|
Netrc struct { |
|
|
|
Machine string |
|
|
|
Login string |
|
|
|
Password string |
|
|
|
} |
|
|
|
|
|
|
|
// InitOptions include options for the Terraform's init command
|
|
|
@ -43,6 +50,7 @@ type ( |
|
|
|
// Plugin represents the plugin instance to be executed
|
|
|
|
Plugin struct { |
|
|
|
Config Config |
|
|
|
Netrc Netrc |
|
|
|
Terraform Terraform |
|
|
|
} |
|
|
|
) |
|
|
@ -62,6 +70,12 @@ func (p Plugin) Exec() error { |
|
|
|
assumeRole(p.Config.RoleARN) |
|
|
|
} |
|
|
|
|
|
|
|
// writing the .netrc file with Github credentials in it.
|
|
|
|
err := writeNetrc(p.Netrc.Machine, p.Netrc.Login, p.Netrc.Password) |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
} |
|
|
|
|
|
|
|
var commands []*exec.Cmd |
|
|
|
|
|
|
|
commands = append(commands, exec.Command("terraform", "version")) |
|
|
@ -73,15 +87,27 @@ func (p Plugin) Exec() error { |
|
|
|
} |
|
|
|
|
|
|
|
commands = append(commands, deleteCache()) |
|
|
|
|
|
|
|
commands = append(commands, initCommand(p.Config.InitOptions)) |
|
|
|
|
|
|
|
commands = append(commands, getModules()) |
|
|
|
commands = append(commands, validateCommand(p.Config)) |
|
|
|
commands = append(commands, planCommand(p.Config)) |
|
|
|
if !p.Config.Plan { |
|
|
|
commands = append(commands, terraformCommand(p.Config)) |
|
|
|
|
|
|
|
// Add commands listed from Actions
|
|
|
|
for _, action := range p.Config.Actions { |
|
|
|
switch action { |
|
|
|
case "validate": |
|
|
|
commands = append(commands, tfValidate(p.Config)) |
|
|
|
case "plan": |
|
|
|
commands = append(commands, tfPlan(p.Config, false)) |
|
|
|
case "plan-destroy": |
|
|
|
commands = append(commands, tfPlan(p.Config, true)) |
|
|
|
case "apply": |
|
|
|
commands = append(commands, tfApply(p.Config)) |
|
|
|
case "destroy": |
|
|
|
commands = append(commands, tfDestroy(p.Config)) |
|
|
|
default: |
|
|
|
return fmt.Errorf("valid actions are: validate, plan, apply, plan-destroy, destroy. You provided %s", action) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
commands = append(commands, deleteCache()) |
|
|
|
|
|
|
|
for _, c := range commands { |
|
|
@ -112,13 +138,7 @@ func (p Plugin) Exec() error { |
|
|
|
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", |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// CopyTfEnv creates copies of TF_VAR_ to lowercase
|
|
|
|
func CopyTfEnv() { |
|
|
|
tfVar := regexp.MustCompile(`^TF_VAR_.*$`) |
|
|
|
for _, e := range os.Environ() { |
|
|
@ -130,6 +150,27 @@ func CopyTfEnv() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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 { |
|
|
|
logrus.WithFields(logrus.Fields{ |
|
|
|
"error": err, |
|
|
|
}).Fatal("Error assuming role!") |
|
|
|
} |
|
|
|
os.Setenv("AWS_ACCESS_KEY_ID", value.AccessKeyID) |
|
|
|
os.Setenv("AWS_SECRET_ACCESS_KEY", value.SecretAccessKey) |
|
|
|
os.Setenv("AWS_SESSION_TOKEN", value.SessionToken) |
|
|
|
} |
|
|
|
|
|
|
|
func deleteCache() *exec.Cmd { |
|
|
|
return exec.Command( |
|
|
|
"rm", |
|
|
@ -138,6 +179,13 @@ func deleteCache() *exec.Cmd { |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
func getModules() *exec.Cmd { |
|
|
|
return exec.Command( |
|
|
|
"terraform", |
|
|
|
"get", |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
func initCommand(config InitOptions) *exec.Cmd { |
|
|
|
args := []string{ |
|
|
|
"init", |
|
|
@ -166,50 +214,24 @@ func initCommand(config InitOptions) *exec.Cmd { |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
func getModules() *exec.Cmd { |
|
|
|
func installCaCert(cacert string) *exec.Cmd { |
|
|
|
ioutil.WriteFile("/usr/local/share/ca-certificates/ca_cert.crt", []byte(cacert), 0644) |
|
|
|
return exec.Command( |
|
|
|
"terraform", |
|
|
|
"get", |
|
|
|
"update-ca-certificates", |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
func validateCommand(config Config) *exec.Cmd { |
|
|
|
args := []string{ |
|
|
|
"validate", |
|
|
|
} |
|
|
|
for _, v := range config.VarFiles { |
|
|
|
args = append(args, "-var-file", fmt.Sprintf("%s", v)) |
|
|
|
} |
|
|
|
for k, v := range config.Vars { |
|
|
|
args = append(args, "-var") |
|
|
|
args = append(args, fmt.Sprintf("%s=%s", k, v)) |
|
|
|
} |
|
|
|
return exec.Command( |
|
|
|
"terraform", |
|
|
|
args..., |
|
|
|
) |
|
|
|
func trace(cmd *exec.Cmd) { |
|
|
|
fmt.Println("$", strings.Join(cmd.Args, " ")) |
|
|
|
} |
|
|
|
|
|
|
|
func planCommand(config Config) *exec.Cmd { |
|
|
|
func tfApply(config Config) *exec.Cmd { |
|
|
|
args := []string{ |
|
|
|
"plan", |
|
|
|
} |
|
|
|
if config.Destroy { |
|
|
|
args = append(args, "-destroy") |
|
|
|
} else { |
|
|
|
args = append(args, "-out=plan.tfout") |
|
|
|
"apply", |
|
|
|
} |
|
|
|
|
|
|
|
for _, v := range config.Targets { |
|
|
|
args = append(args, "--target", fmt.Sprintf("%s", v)) |
|
|
|
} |
|
|
|
for _, v := range config.VarFiles { |
|
|
|
args = append(args, "-var-file", fmt.Sprintf("%s", v)) |
|
|
|
} |
|
|
|
for k, v := range config.Vars { |
|
|
|
args = append(args, "-var") |
|
|
|
args = append(args, fmt.Sprintf("%s=%s", k, v)) |
|
|
|
} |
|
|
|
if config.Parallelism > 0 { |
|
|
|
args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism)) |
|
|
|
} |
|
|
@ -219,26 +241,19 @@ func planCommand(config Config) *exec.Cmd { |
|
|
|
if config.InitOptions.LockTimeout != "" { |
|
|
|
args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout)) |
|
|
|
} |
|
|
|
args = append(args, "plan.tfout") |
|
|
|
return exec.Command( |
|
|
|
"terraform", |
|
|
|
args..., |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
func terraformCommand(config Config) *exec.Cmd { |
|
|
|
if config.Destroy { |
|
|
|
return destroyCommand(config) |
|
|
|
} |
|
|
|
|
|
|
|
return applyCommand(config) |
|
|
|
} |
|
|
|
|
|
|
|
func applyCommand(config Config) *exec.Cmd { |
|
|
|
func tfDestroy(config Config) *exec.Cmd { |
|
|
|
args := []string{ |
|
|
|
"apply", |
|
|
|
"destroy", |
|
|
|
} |
|
|
|
for _, v := range config.Targets { |
|
|
|
args = append(args, "--target", fmt.Sprintf("%s", v)) |
|
|
|
args = append(args, fmt.Sprintf("-target=%s", v)) |
|
|
|
} |
|
|
|
if config.Parallelism > 0 { |
|
|
|
args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism)) |
|
|
@ -249,19 +264,33 @@ func applyCommand(config Config) *exec.Cmd { |
|
|
|
if config.InitOptions.LockTimeout != "" { |
|
|
|
args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout)) |
|
|
|
} |
|
|
|
args = append(args, "plan.tfout") |
|
|
|
args = append(args, "-force") |
|
|
|
return exec.Command( |
|
|
|
"terraform", |
|
|
|
args..., |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
func destroyCommand(config Config) *exec.Cmd { |
|
|
|
func tfPlan(config Config, destroy bool) *exec.Cmd { |
|
|
|
args := []string{ |
|
|
|
"destroy", |
|
|
|
"plan", |
|
|
|
} |
|
|
|
|
|
|
|
if destroy { |
|
|
|
args = append(args, "-destroy") |
|
|
|
} else { |
|
|
|
args = append(args, "-out=plan.tfout") |
|
|
|
} |
|
|
|
|
|
|
|
for _, v := range config.Targets { |
|
|
|
args = append(args, fmt.Sprintf("-target=%s", v)) |
|
|
|
args = append(args, "--target", fmt.Sprintf("%s", v)) |
|
|
|
} |
|
|
|
for _, v := range config.VarFiles { |
|
|
|
args = append(args, "-var-file", fmt.Sprintf("%s", v)) |
|
|
|
} |
|
|
|
for k, v := range config.Vars { |
|
|
|
args = append(args, "-var") |
|
|
|
args = append(args, fmt.Sprintf("%s=%s", k, v)) |
|
|
|
} |
|
|
|
if config.Parallelism > 0 { |
|
|
|
args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism)) |
|
|
@ -272,34 +301,54 @@ func destroyCommand(config Config) *exec.Cmd { |
|
|
|
if config.InitOptions.LockTimeout != "" { |
|
|
|
args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout)) |
|
|
|
} |
|
|
|
args = append(args, "-force") |
|
|
|
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", |
|
|
|
func tfValidate(config Config) *exec.Cmd { |
|
|
|
args := []string{ |
|
|
|
"validate", |
|
|
|
} |
|
|
|
|
|
|
|
value, err := credentials.NewCredentials(stsProvider).Get() |
|
|
|
if err != nil { |
|
|
|
logrus.WithFields(logrus.Fields{ |
|
|
|
"error": err, |
|
|
|
}).Fatal("Error assuming role!") |
|
|
|
for _, v := range config.VarFiles { |
|
|
|
args = append(args, "-var-file", fmt.Sprintf("%s", v)) |
|
|
|
} |
|
|
|
os.Setenv("AWS_ACCESS_KEY_ID", value.AccessKeyID) |
|
|
|
os.Setenv("AWS_SECRET_ACCESS_KEY", value.SecretAccessKey) |
|
|
|
os.Setenv("AWS_SESSION_TOKEN", value.SessionToken) |
|
|
|
for k, v := range config.Vars { |
|
|
|
args = append(args, "-var") |
|
|
|
args = append(args, fmt.Sprintf("%s=%s", k, v)) |
|
|
|
} |
|
|
|
return exec.Command( |
|
|
|
"terraform", |
|
|
|
args..., |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
func trace(cmd *exec.Cmd) { |
|
|
|
fmt.Println("$", strings.Join(cmd.Args, " ")) |
|
|
|
// helper function to write a netrc file.
|
|
|
|
// The following code comes from the official Git plugin for Drone:
|
|
|
|
// https://github.com/drone-plugins/drone-git/blob/8386effd2fe8c8695cf979427f8e1762bd805192/utils.go#L43-L68
|
|
|
|
func writeNetrc(machine, login, password string) error { |
|
|
|
if machine == "" { |
|
|
|
return nil |
|
|
|
} |
|
|
|
out := fmt.Sprintf( |
|
|
|
netrcFile, |
|
|
|
machine, |
|
|
|
login, |
|
|
|
password, |
|
|
|
) |
|
|
|
|
|
|
|
home := "/root" |
|
|
|
u, err := user.Current() |
|
|
|
if err == nil { |
|
|
|
home = u.HomeDir |
|
|
|
} |
|
|
|
path := filepath.Join(home, ".netrc") |
|
|
|
return ioutil.WriteFile(path, []byte(out), 0600) |
|
|
|
} |
|
|
|
|
|
|
|
const netrcFile = ` |
|
|
|
machine %s |
|
|
|
login %s |
|
|
|
password %s |
|
|
|
` |
|
|
|