mirror of
https://github.com/thegeeklab/wp-opentofu.git
synced 2024-11-12 17:10:39 +00:00
99c5f3f0b1
https://www.terraform.io/upgrade-guides/0-15.html#other-minor-command-line-behavior-changes > The -lock and -lock-timeout options are no longer available for the terraform init command. Locking applies to operations that can potentially change remote objects, to help ensure that two concurrent Terraform processes don't try to run conflicting operations, but terraform init does not interact with any providers in order to possibly effect such changes. > These options didn't do anything in the terraform init command before, and so you can remove them from any automated calls with no change in behavior.
416 lines
9.5 KiB
Go
416 lines
9.5 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
|
|
DisableRefresh bool
|
|
}
|
|
|
|
// 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 != "" && !credsSet() {
|
|
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
|
|
}
|
|
|
|
terraformDataDir := ".terraform"
|
|
if p.Config.TerraformDataDir != "" {
|
|
terraformDataDir = p.Config.TerraformDataDir
|
|
os.Setenv("TF_DATA_DIR", p.Config.TerraformDataDir)
|
|
}
|
|
|
|
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(terraformDataDir))
|
|
commands = append(commands, initCommand(p.Config.InitOptions))
|
|
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())
|
|
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(terraformDataDir))
|
|
|
|
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 credsSet() bool {
|
|
awsTokens := []string{"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"}
|
|
for _, token := range awsTokens {
|
|
if os.Getenv(token) == "" {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
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(terraformDataDir string) *exec.Cmd {
|
|
return exec.Command(
|
|
"rm",
|
|
"-rf",
|
|
terraformDataDir,
|
|
)
|
|
}
|
|
|
|
func getModules() *exec.Cmd {
|
|
return exec.Command(
|
|
"terraform",
|
|
"get",
|
|
)
|
|
}
|
|
|
|
func initCommand(config InitOptions) *exec.Cmd {
|
|
args := []string{
|
|
"init",
|
|
}
|
|
|
|
for _, v := range config.BackendConfig {
|
|
args = append(args, fmt.Sprintf("-backend-config=%s", v))
|
|
}
|
|
|
|
// Fail Terraform execution on prompt
|
|
args = append(args, "-input=false")
|
|
|
|
return exec.Command(
|
|
"terraform",
|
|
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))
|
|
}
|
|
if config.DisableRefresh {
|
|
args = append(args, "-refresh=false")
|
|
}
|
|
args = append(args, getTfoutPath())
|
|
|
|
return exec.Command(
|
|
"terraform",
|
|
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, "-auto-approve")
|
|
return exec.Command(
|
|
"terraform",
|
|
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()))
|
|
}
|
|
|
|
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))
|
|
}
|
|
if config.DisableRefresh {
|
|
args = append(args, "-refresh=false")
|
|
}
|
|
return exec.Command(
|
|
"terraform",
|
|
args...,
|
|
)
|
|
}
|
|
|
|
func tfValidate() *exec.Cmd {
|
|
return exec.Command(
|
|
"terraform",
|
|
"validate",
|
|
)
|
|
}
|
|
|
|
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 exec.Command(
|
|
"terraform",
|
|
args...,
|
|
)
|
|
}
|
|
|
|
func getTfoutPath() string {
|
|
terraformDataDir := os.Getenv("TF_DATA_DIR")
|
|
if terraformDataDir == ".terraform" || terraformDataDir == "" {
|
|
return "plan.tfout"
|
|
}
|
|
|
|
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
|
|
`
|