Skip to content

Commit

Permalink
Split commands out to individual files
Browse files Browse the repository at this point in the history
  • Loading branch information
paultyng committed Jul 13, 2020
1 parent f6537f2 commit 9bfde3e
Show file tree
Hide file tree
Showing 19 changed files with 1,270 additions and 1,136 deletions.
146 changes: 146 additions & 0 deletions tfexec/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package tfexec

import (
"context"
"fmt"
"os/exec"
"strconv"
"strings"
)

type applyConfig struct {
backup string
dirOrPlan string
lock bool

// LockTimeout must be a string with time unit, e.g. '10s'
lockTimeout string
parallelism int
refresh bool
state string
stateOut string
targets []string

// Vars: each var must be supplied as a single string, e.g. 'foo=bar'
vars []string
varFile string
}

var defaultApplyOptions = applyConfig{
lock: true,
parallelism: 10,
refresh: true,
}

type ApplyOption interface {
configureApply(*applyConfig)
}

func (opt *ParallelismOption) configureApply(conf *applyConfig) {
conf.parallelism = opt.parallelism
}

func (opt *BackupOption) configureApply(conf *applyConfig) {
conf.backup = opt.path
}

func (opt *TargetOption) configureApply(conf *applyConfig) {
conf.targets = append(conf.targets, opt.target)
}

func (opt *LockTimeoutOption) configureApply(conf *applyConfig) {
conf.lockTimeout = opt.timeout
}

func (opt *StateOption) configureApply(conf *applyConfig) {
conf.state = opt.path
}

func (opt *StateOutOption) configureApply(conf *applyConfig) {
conf.stateOut = opt.path
}

func (opt *VarFileOption) configureApply(conf *applyConfig) {
conf.varFile = opt.path
}

func (opt *LockOption) configureApply(conf *applyConfig) {
conf.lock = opt.lock
}

func (opt *RefreshOption) configureApply(conf *applyConfig) {
conf.refresh = opt.refresh
}

func (opt *VarOption) configureApply(conf *applyConfig) {
conf.vars = append(conf.vars, opt.assignment)
}

func (opt *DirOrPlanOption) configureApply(conf *applyConfig) {
conf.dirOrPlan = opt.path
}

func (tf *Terraform) Apply(ctx context.Context, opts ...ApplyOption) error {
applyCmd := tf.ApplyCmd(ctx, opts...)

var errBuf strings.Builder
applyCmd.Stderr = &errBuf

err := applyCmd.Run()
if err != nil {
return parseError(errBuf.String())
}

return nil
}

func (tf *Terraform) ApplyCmd(ctx context.Context, opts ...ApplyOption) *exec.Cmd {
c := defaultApplyOptions

for _, o := range opts {
o.configureApply(&c)
}

args := []string{"apply", "-no-color", "-auto-approve", "-input=false"}

// string opts: only pass if set
if c.backup != "" {
args = append(args, "-backup="+c.backup)
}
if c.lockTimeout != "" {
args = append(args, "-lock-timeout="+c.lockTimeout)
}
if c.state != "" {
args = append(args, "-state="+c.state)
}
if c.stateOut != "" {
args = append(args, "-state-out="+c.stateOut)
}
if c.varFile != "" {
args = append(args, "-var-file="+c.varFile)
}

// boolean and numerical opts: always pass
args = append(args, "-lock="+strconv.FormatBool(c.lock))
args = append(args, "-parallelism="+fmt.Sprint(c.parallelism))
args = append(args, "-refresh="+strconv.FormatBool(c.refresh))

// string slice opts: split into separate args
if c.targets != nil {
for _, ta := range c.targets {
args = append(args, "-target="+ta)
}
}
if c.vars != nil {
for _, v := range c.vars {
args = append(args, "-var '"+v+"'")
}
}

// string argument: pass if set
if c.dirOrPlan != "" {
args = append(args, c.dirOrPlan)
}

return tf.buildTerraformCmd(ctx, args...)
}
54 changes: 54 additions & 0 deletions tfexec/apply_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package tfexec

import (
"context"
"os"
"path/filepath"
"strings"
"testing"
)

func TestApply(t *testing.T) {
td := testTempDir(t)
defer os.RemoveAll(td)

tf, err := NewTerraform(td, tfPath)
if err != nil {
t.Fatal(err)
}

err = copyFile(filepath.Join(testFixtureDir, testConfigFileName), filepath.Join(td, testConfigFileName))
if err != nil {
t.Fatalf("error copying config file into test dir: %s", err)
}

err = tf.Init(context.Background())
if err != nil {
t.Fatalf("error running Init in test directory: %s", err)
}

err = tf.Apply(context.Background())
if err != nil {
t.Fatalf("error running Apply: %s", err)
}
}

func TestApplyCmd(t *testing.T) {
td := testTempDir(t)
defer os.RemoveAll(td)

tf, err := NewTerraform(td, tfPath)
if err != nil {
t.Fatal(err)
}

applyCmd := tf.ApplyCmd(context.Background(), Backup("testbackup"), LockTimeout("200s"), State("teststate"), StateOut("teststateout"), VarFile("testvarfile"), Lock(false), Parallelism(99), Refresh(false), Target("target1"), Target("target2"), Var("var1=foo"), Var("var2=bar"), DirOrPlan("testfile"))

actual := strings.TrimPrefix(cmdString(applyCmd), applyCmd.Path+" ")

expected := "apply -no-color -auto-approve -input=false -backup=testbackup -lock-timeout=200s -state=teststate -state-out=teststateout -var-file=testvarfile -lock=false -parallelism=99 -refresh=false -target=target1 -target=target2 -var 'var1=foo' -var 'var2=bar' testfile"

if actual != expected {
t.Fatalf("expected arguments of ApplyCmd:\n%s\n actual arguments:\n%s\n", expected, actual)
}
}
137 changes: 137 additions & 0 deletions tfexec/destroy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package tfexec

import (
"context"
"fmt"
"os/exec"
"strconv"
"strings"
)

type destroyConfig struct {
backup string
lock bool

// LockTimeout must be a string with time unit, e.g. '10s'
lockTimeout string
parallelism int
refresh bool
state string
stateOut string
targets []string

// Vars: each var must be supplied as a single string, e.g. 'foo=bar'
vars []string
varFile string
}

var defaultDestroyOptions = destroyConfig{
lock: true,
lockTimeout: "0s",
parallelism: 10,
refresh: true,
}

type DestroyOption interface {
configureDestroy(*destroyConfig)
}

func (opt *ParallelismOption) configureDestroy(conf *destroyConfig) {
conf.parallelism = opt.parallelism
}

func (opt *BackupOption) configureDestroy(conf *destroyConfig) {
conf.backup = opt.path
}

func (opt *TargetOption) configureDestroy(conf *destroyConfig) {
conf.targets = append(conf.targets, opt.target)
}

func (opt *LockTimeoutOption) configureDestroy(conf *destroyConfig) {
conf.lockTimeout = opt.timeout
}

func (opt *StateOption) configureDestroy(conf *destroyConfig) {
conf.state = opt.path
}

func (opt *StateOutOption) configureDestroy(conf *destroyConfig) {
conf.stateOut = opt.path
}

func (opt *VarFileOption) configureDestroy(conf *destroyConfig) {
conf.varFile = opt.path
}

func (opt *LockOption) configureDestroy(conf *destroyConfig) {
conf.lock = opt.lock
}

func (opt *RefreshOption) configureDestroy(conf *destroyConfig) {
conf.refresh = opt.refresh
}

func (opt *VarOption) configureDestroy(conf *destroyConfig) {
conf.vars = append(conf.vars, opt.assignment)
}

func (tf *Terraform) Destroy(ctx context.Context, opts ...DestroyOption) error {
destroyCmd := tf.DestroyCmd(ctx, opts...)

var errBuf strings.Builder
destroyCmd.Stderr = &errBuf

err := destroyCmd.Run()
if err != nil {
return parseError(errBuf.String())
}

return nil
}

func (tf *Terraform) DestroyCmd(ctx context.Context, opts ...DestroyOption) *exec.Cmd {
c := defaultDestroyOptions

for _, o := range opts {
o.configureDestroy(&c)
}

args := []string{"destroy", "-no-color", "-auto-approve"}

// string opts: only pass if set
if c.backup != "" {
args = append(args, "-backup="+c.backup)
}
if c.lockTimeout != "" {
args = append(args, "-lock-timeout="+c.lockTimeout)
}
if c.state != "" {
args = append(args, "-state="+c.state)
}
if c.stateOut != "" {
args = append(args, "-state-out="+c.stateOut)
}
if c.varFile != "" {
args = append(args, "-var-file="+c.varFile)
}

// boolean and numerical opts: always pass
args = append(args, "-lock="+strconv.FormatBool(c.lock))
args = append(args, "-parallelism="+fmt.Sprint(c.parallelism))
args = append(args, "-refresh="+strconv.FormatBool(c.refresh))

// string slice opts: split into separate args
if c.targets != nil {
for _, ta := range c.targets {
args = append(args, "-target="+ta)
}
}
if c.vars != nil {
for _, v := range c.vars {
args = append(args, "-var '"+v+"'")
}
}

return tf.buildTerraformCmd(ctx, args...)
}
40 changes: 40 additions & 0 deletions tfexec/destroy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package tfexec

import (
"context"
"os"
"strings"
"testing"
)

func TestDestroyCmd(t *testing.T) {
td := testTempDir(t)
defer os.RemoveAll(td)

tf, err := NewTerraform(td, tfPath)
if err != nil {
t.Fatal(err)
}

// defaults
destroyCmd := tf.DestroyCmd(context.Background())

actual := strings.TrimPrefix(cmdString(destroyCmd), destroyCmd.Path+" ")

expected := "destroy -no-color -auto-approve -lock-timeout=0s -lock=true -parallelism=10 -refresh=true"

if actual != expected {
t.Fatalf("expected default arguments of DestroyCmd:\n%s\n actual arguments:\n%s\n", expected, actual)
}

// override all defaults
destroyCmd = tf.DestroyCmd(context.Background(), Backup("testbackup"), LockTimeout("200s"), State("teststate"), StateOut("teststateout"), VarFile("testvarfile"), Lock(false), Parallelism(99), Refresh(false), Target("target1"), Target("target2"), Var("var1=foo"), Var("var2=bar"))

actual = strings.TrimPrefix(cmdString(destroyCmd), destroyCmd.Path+" ")

expected = "destroy -no-color -auto-approve -backup=testbackup -lock-timeout=200s -state=teststate -state-out=teststateout -var-file=testvarfile -lock=false -parallelism=99 -refresh=false -target=target1 -target=target2 -var 'var1=foo' -var 'var2=bar'"

if actual != expected {
t.Fatalf("expected arguments of DestroyCmd:\n%s\n actual arguments:\n%s\n", expected, actual)
}
}
Loading

0 comments on commit 9bfde3e

Please sign in to comment.