mirror of
https://github.com/thegeeklab/wp-opentofu.git
synced 2024-11-09 18:00:40 +00:00
13a6625b51
When using Command.Env, if you send nil as a value, it will use all of the environment variables from os.Environ() when executing the command. In order to not break the current tests and be coherent with the current relationship, we are appending the created environment variables with os.Environ() only when we have at least 1 environment variable to override.
444 lines
10 KiB
Go
444 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"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"
|
|
)
|
|
|
|
type (
|
|
// Config holds input parameters for the plugin
|
|
Config struct {
|
|
Actions []string
|
|
Vars map[string]string
|
|
Secrets map[string]string
|
|
InitOptions InitOptions
|
|
FmtOptions FmtOptions
|
|
Cacert string
|
|
Sensitive bool
|
|
RoleARN string
|
|
RootDir string
|
|
Parallelism int
|
|
Targets []string
|
|
VarFiles []string
|
|
TerraformDataDir string
|
|
}
|
|
|
|
// Netrc is credentials for cloning
|
|
Netrc struct {
|
|
Machine string
|
|
Login string
|
|
Password string
|
|
}
|
|
|
|
// InitOptions include options for the Terraform's init command
|
|
InitOptions struct {
|
|
BackendConfig []string `json:"backend-config"`
|
|
Lock *bool `json:"lock"`
|
|
LockTimeout string `json:"lock-timeout"`
|
|
}
|
|
|
|
// FmtOptions fmt options for the Terraform's fmt command
|
|
FmtOptions struct {
|
|
List *bool `json:"list"`
|
|
Write *bool `json:"write"`
|
|
Diff *bool `json:"diff"`
|
|
Check *bool `json:"check"`
|
|
}
|
|
|
|
// Plugin represents the plugin instance to be executed
|
|
Plugin struct {
|
|
Config Config
|
|
Netrc Netrc
|
|
Terraform Terraform
|
|
}
|
|
)
|
|
|
|
// Exec executes the plugin
|
|
func (p Plugin) Exec() error {
|
|
// Install specified version of terraform
|
|
if p.Terraform.Version != "" {
|
|
err := installTerraform(p.Terraform.Version)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if p.Config.RoleARN != "" {
|
|
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"))
|
|
|
|
CopyTfEnv()
|
|
|
|
if p.Config.Cacert != "" {
|
|
commands = append(commands, installCaCert(p.Config.Cacert))
|
|
}
|
|
|
|
commands = append(commands, deleteCache(p.Config))
|
|
commands = append(commands, initCommand(p.Config))
|
|
commands = append(commands, getModules())
|
|
|
|
// Add commands listed from Actions
|
|
for _, action := range p.Config.Actions {
|
|
switch action {
|
|
case "fmt":
|
|
commands = append(commands, tfFmt(p.Config))
|
|
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: fmt, validate, plan, apply, plan-destroy, destroy. You provided %s", action)
|
|
}
|
|
}
|
|
|
|
commands = append(commands, deleteCache(p.Config))
|
|
|
|
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 {
|
|
logrus.WithFields(logrus.Fields{
|
|
"error": err,
|
|
}).Fatal("Failed to execute a command")
|
|
}
|
|
logrus.Debug("Command completed successfully")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CopyTfEnv creates copies of TF_VAR_ to lowercase
|
|
func CopyTfEnv() {
|
|
tfVar := regexp.MustCompile(`^TF_VAR_.*$`)
|
|
for _, e := range os.Environ() {
|
|
pair := strings.SplitN(e, "=", 2)
|
|
if tfVar.MatchString(pair[0]) {
|
|
name := strings.Split(pair[0], "TF_VAR_")
|
|
os.Setenv(fmt.Sprintf("TF_VAR_%s", strings.ToLower(name[1])), pair[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
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(config Config) *exec.Cmd {
|
|
terraformDataDir := getTerraformDataDir(config)
|
|
return exec.Command(
|
|
"rm",
|
|
"-rf",
|
|
terraformDataDir,
|
|
)
|
|
}
|
|
|
|
func getModules() *exec.Cmd {
|
|
return exec.Command(
|
|
"terraform",
|
|
"get",
|
|
)
|
|
}
|
|
|
|
func initCommand(config Config) *exec.Cmd {
|
|
args := []string{
|
|
"init",
|
|
}
|
|
|
|
for _, v := range config.InitOptions.BackendConfig {
|
|
args = append(args, fmt.Sprintf("-backend-config=%s", v))
|
|
}
|
|
|
|
// True is default in TF
|
|
if config.InitOptions.Lock != nil {
|
|
args = append(args, fmt.Sprintf("-lock=%t", *config.InitOptions.Lock))
|
|
}
|
|
|
|
// "0s" is default in TF
|
|
if config.InitOptions.LockTimeout != "" {
|
|
args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout))
|
|
}
|
|
|
|
// Fail Terraform execution on prompt
|
|
args = append(args, "-input=false")
|
|
|
|
return createTerraformCommand(
|
|
config,
|
|
args...,
|
|
)
|
|
}
|
|
|
|
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 trace(cmd *exec.Cmd) {
|
|
fmt.Println("$", strings.Join(cmd.Args, " "))
|
|
}
|
|
|
|
func tfApply(config Config) *exec.Cmd {
|
|
args := []string{
|
|
"apply",
|
|
}
|
|
for _, v := range config.Targets {
|
|
args = append(args, "--target", fmt.Sprintf("%s", v))
|
|
}
|
|
if config.Parallelism > 0 {
|
|
args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism))
|
|
}
|
|
if config.InitOptions.Lock != nil {
|
|
args = append(args, fmt.Sprintf("-lock=%t", *config.InitOptions.Lock))
|
|
}
|
|
if config.InitOptions.LockTimeout != "" {
|
|
args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout))
|
|
}
|
|
args = append(args, getTfoutPath(config))
|
|
|
|
return createTerraformCommand(
|
|
config,
|
|
args...,
|
|
)
|
|
}
|
|
|
|
func tfDestroy(config Config) *exec.Cmd {
|
|
args := []string{
|
|
"destroy",
|
|
}
|
|
for _, v := range config.Targets {
|
|
args = append(args, fmt.Sprintf("-target=%s", v))
|
|
}
|
|
args = append(args, varFiles(config.VarFiles)...)
|
|
args = append(args, vars(config.Vars)...)
|
|
if config.Parallelism > 0 {
|
|
args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism))
|
|
}
|
|
if config.InitOptions.Lock != nil {
|
|
args = append(args, fmt.Sprintf("-lock=%t", *config.InitOptions.Lock))
|
|
}
|
|
if config.InitOptions.LockTimeout != "" {
|
|
args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout))
|
|
}
|
|
args = append(args, "-force")
|
|
return createTerraformCommand(
|
|
config,
|
|
args...,
|
|
)
|
|
}
|
|
|
|
func tfPlan(config Config, destroy bool) *exec.Cmd {
|
|
args := []string{
|
|
"plan",
|
|
}
|
|
|
|
if destroy {
|
|
args = append(args, "-destroy")
|
|
} else {
|
|
args = append(args, fmt.Sprintf("-out=%s", getTfoutPath(config)))
|
|
}
|
|
|
|
for _, v := range config.Targets {
|
|
args = append(args, "--target", fmt.Sprintf("%s", v))
|
|
}
|
|
args = append(args, varFiles(config.VarFiles)...)
|
|
args = append(args, vars(config.Vars)...)
|
|
if config.Parallelism > 0 {
|
|
args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism))
|
|
}
|
|
if config.InitOptions.Lock != nil {
|
|
args = append(args, fmt.Sprintf("-lock=%t", *config.InitOptions.Lock))
|
|
}
|
|
if config.InitOptions.LockTimeout != "" {
|
|
args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout))
|
|
}
|
|
return createTerraformCommand(
|
|
config,
|
|
args...,
|
|
)
|
|
}
|
|
|
|
func tfValidate(config Config) *exec.Cmd {
|
|
args := []string{
|
|
"validate",
|
|
}
|
|
for _, v := range config.VarFiles {
|
|
args = append(args, fmt.Sprintf("-var-file=%s", v))
|
|
}
|
|
for k, v := range config.Vars {
|
|
args = append(args, "-var", fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
return createTerraformCommand(
|
|
config,
|
|
args...,
|
|
)
|
|
}
|
|
|
|
func tfFmt(config Config) *exec.Cmd {
|
|
args := []string{
|
|
"fmt",
|
|
}
|
|
if config.FmtOptions.List != nil {
|
|
args = append(args, fmt.Sprintf("-list=%t", *config.FmtOptions.List))
|
|
}
|
|
if config.FmtOptions.Write != nil {
|
|
args = append(args, fmt.Sprintf("-write=%t", *config.FmtOptions.Write))
|
|
}
|
|
if config.FmtOptions.Diff != nil {
|
|
args = append(args, fmt.Sprintf("-diff=%t", *config.FmtOptions.Diff))
|
|
}
|
|
if config.FmtOptions.Check != nil {
|
|
args = append(args, fmt.Sprintf("-check=%t", *config.FmtOptions.Check))
|
|
}
|
|
return createTerraformCommand(
|
|
config,
|
|
args...,
|
|
)
|
|
}
|
|
|
|
func getTerraformDataDir(config Config) string {
|
|
// Override terraform data dir
|
|
var terraformDataDir string
|
|
if config.TerraformDataDir != "" {
|
|
terraformDataDir = config.TerraformDataDir
|
|
} else if os.Getenv("TF_DATA_DIR") != "" {
|
|
terraformDataDir = os.Getenv("TF_DATA_DIR")
|
|
} else {
|
|
terraformDataDir = ".terraform"
|
|
}
|
|
return terraformDataDir
|
|
}
|
|
|
|
func createEnvironmentVariables(config Config) []string {
|
|
var environmentVariables []string = []string{}
|
|
|
|
terraformDataDir := getTerraformDataDir(config)
|
|
if terraformDataDir != ".terraform" {
|
|
environmentVariables = append(environmentVariables, fmt.Sprintf("TF_DATA_DIR=%s", terraformDataDir))
|
|
}
|
|
return environmentVariables
|
|
}
|
|
|
|
func createTerraformCommand(config Config, args ...string) *exec.Cmd {
|
|
command := exec.Command("terraform", args...)
|
|
environmentVariables := createEnvironmentVariables(config)
|
|
if len(environmentVariables) > 0 {
|
|
command.Env = append(os.Environ(), environmentVariables...)
|
|
}
|
|
return command
|
|
}
|
|
func getTfoutPath(config Config) string {
|
|
terraformDataDir := getTerraformDataDir(config)
|
|
if terraformDataDir == ".terraform" {
|
|
return "plan.tfout"
|
|
} else {
|
|
return fmt.Sprintf("%s.plan.tfout", terraformDataDir)
|
|
}
|
|
}
|
|
|
|
func vars(vs map[string]string) []string {
|
|
var args []string
|
|
for k, v := range vs {
|
|
args = append(args, "-var", fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
return args
|
|
}
|
|
|
|
func varFiles(vfs []string) []string {
|
|
var args []string
|
|
for _, v := range vfs {
|
|
args = append(args, fmt.Sprintf("-var-file=%s", v))
|
|
}
|
|
return 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
|
|
`
|