Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ApplyJSON(), DestroyJSON(), PlanJSON() and RefreshJSON() functions #354

Merged
merged 8 commits into from
Jan 24, 2023
27 changes: 26 additions & 1 deletion tfexec/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tfexec
import (
"context"
"fmt"
"io"
"os/exec"
"strconv"
)
Expand Down Expand Up @@ -90,7 +91,7 @@ func (opt *ReattachOption) configureApply(conf *applyConfig) {
conf.reattachInfo = opt.info
}

// Apply represents the terraform apply subcommand.
// Apply represents the Terraform apply subcommand.
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
func (tf *Terraform) Apply(ctx context.Context, opts ...ApplyOption) error {
cmd, err := tf.applyCmd(ctx, opts...)
if err != nil {
Expand All @@ -99,6 +100,30 @@ func (tf *Terraform) Apply(ctx context.Context, opts ...ApplyOption) error {
return tf.runTerraformCmd(ctx, cmd)
}

// ApplyJSON represents the Terraform apply subcommand with the `-json` flag.
radeksimko marked this conversation as resolved.
Show resolved Hide resolved
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
func (tf *Terraform) ApplyJSON(ctx context.Context, w io.Writer, opts ...ApplyOption) error {
tf.SetStdout(w)
radeksimko marked this conversation as resolved.
Show resolved Hide resolved

cmd, err := tf.applyJSONCmd(ctx, opts...)
if err != nil {
return err
}

return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) applyJSONCmd(ctx context.Context, opts ...ApplyOption) (*exec.Cmd, error) {
cmd, err := tf.applyCmd(ctx, opts...)
if err != nil {
return nil, err
}

cmd.Args = append(cmd.Args[:3], cmd.Args[2:]...)
cmd.Args[2] = "-json"
radeksimko marked this conversation as resolved.
Show resolved Hide resolved

return cmd, nil
}

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

Expand Down
60 changes: 60 additions & 0 deletions tfexec/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,63 @@ func TestApplyCmd(t *testing.T) {
}, nil, applyCmd)
})
}

func TestApplyJSONCmd(t *testing.T) {
radeksimko marked this conversation as resolved.
Show resolved Hide resolved
td := t.TempDir()

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

// empty env, to avoid environ mismatch in testing
tf.SetEnv(map[string]string{})

t.Run("basic", func(t *testing.T) {
applyCmd, err := tf.applyJSONCmd(context.Background(),
Backup("testbackup"),
LockTimeout("200s"),
State("teststate"),
StateOut("teststateout"),
VarFile("foo.tfvars"),
VarFile("bar.tfvars"),
Lock(false),
Parallelism(99),
Refresh(false),
Replace("aws_instance.test"),
Replace("google_pubsub_topic.test"),
Target("target1"),
Target("target2"),
Var("var1=foo"),
Var("var2=bar"),
DirOrPlan("testfile"),
)
if err != nil {
t.Fatal(err)
}

assertCmd(t, []string{
"apply",
"-json",
"-no-color",
"-auto-approve",
"-input=false",
"-backup=testbackup",
"-lock-timeout=200s",
"-state=teststate",
"-state-out=teststateout",
"-var-file=foo.tfvars",
"-var-file=bar.tfvars",
"-lock=false",
"-parallelism=99",
"-refresh=false",
"-replace=aws_instance.test",
"-replace=google_pubsub_topic.test",
"-target=target1",
"-target=target2",
"-var", "var1=foo",
"-var", "var2=bar",
"testfile",
}, nil, applyCmd)
})
}
27 changes: 26 additions & 1 deletion tfexec/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tfexec
import (
"context"
"fmt"
"io"
"os/exec"
"strconv"
)
Expand Down Expand Up @@ -86,7 +87,7 @@ func (opt *ReattachOption) configureDestroy(conf *destroyConfig) {
conf.reattachInfo = opt.info
}

// Destroy represents the terraform destroy subcommand.
// Destroy represents the Terraform destroy subcommand.
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
func (tf *Terraform) Destroy(ctx context.Context, opts ...DestroyOption) error {
cmd, err := tf.destroyCmd(ctx, opts...)
if err != nil {
Expand All @@ -95,6 +96,30 @@ func (tf *Terraform) Destroy(ctx context.Context, opts ...DestroyOption) error {
return tf.runTerraformCmd(ctx, cmd)
}

// DestroyJSON represents the Terraform destroy subcommand with the `-json` flag.
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
func (tf *Terraform) DestroyJSON(ctx context.Context, w io.Writer, opts ...DestroyOption) error {
tf.SetStdout(w)
radeksimko marked this conversation as resolved.
Show resolved Hide resolved

cmd, err := tf.destroyJSONCmd(ctx, opts...)
if err != nil {
return err
}

return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) destroyJSONCmd(ctx context.Context, opts ...DestroyOption) (*exec.Cmd, error) {
cmd, err := tf.destroyCmd(ctx, opts...)
if err != nil {
return nil, err
}

cmd.Args = append(cmd.Args[:3], cmd.Args[2:]...)
cmd.Args[2] = "-json"
radeksimko marked this conversation as resolved.
Show resolved Hide resolved

return cmd, nil
}

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

Expand Down
59 changes: 59 additions & 0 deletions tfexec/destroy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,62 @@ func TestDestroyCmd(t *testing.T) {
}, nil, destroyCmd)
})
}

func TestDestroyJSONCmd(t *testing.T) {
td := t.TempDir()

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

// empty env, to avoid environ mismatch in testing
tf.SetEnv(map[string]string{})

t.Run("defaults", func(t *testing.T) {
destroyCmd, err := tf.destroyJSONCmd(context.Background())
if err != nil {
t.Fatal(err)
}

assertCmd(t, []string{
"destroy",
"-json",
"-no-color",
"-auto-approve",
"-input=false",
"-lock-timeout=0s",
"-lock=true",
"-parallelism=10",
"-refresh=true",
}, nil, destroyCmd)
})

t.Run("override all defaults", func(t *testing.T) {
destroyCmd, err := tf.destroyJSONCmd(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"), Dir("destroydir"))
if err != nil {
t.Fatal(err)
}

assertCmd(t, []string{
"destroy",
"-json",
"-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",
"destroydir",
}, nil, destroyCmd)
})
}
37 changes: 37 additions & 0 deletions tfexec/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tfexec
import (
"context"
"fmt"
"io"
"os/exec"
"strconv"
)
Expand Down Expand Up @@ -108,6 +109,42 @@ func (tf *Terraform) Plan(ctx context.Context, opts ...PlanOption) (bool, error)
return false, err
}

// PlanJSON executes `terraform plan` with the specified options as well as the
// `-json` flag and waits for it to complete.
//
// The returned boolean is false when the plan diff is empty (no changes) and
// true when the plan diff is non-empty (changes present).
//
// The returned error is nil if `terraform plan` has been executed and exits
// with either 0 or 2.
func (tf *Terraform) PlanJSON(ctx context.Context, w io.Writer, opts ...PlanOption) (bool, error) {
tf.SetStdout(w)
radeksimko marked this conversation as resolved.
Show resolved Hide resolved

cmd, err := tf.planJSONCmd(ctx, opts...)
if err != nil {
return false, err
}

err = tf.runTerraformCmd(ctx, cmd)
if err != nil && cmd.ProcessState.ExitCode() == 2 {
return true, nil
}

return false, err
}

func (tf *Terraform) planJSONCmd(ctx context.Context, opts ...PlanOption) (*exec.Cmd, error) {
cmd, err := tf.planCmd(ctx, opts...)
if err != nil {
return nil, err
}

cmd.Args = append(cmd.Args[:3], cmd.Args[2:]...)
cmd.Args[2] = "-json"

return cmd, nil
}

func (tf *Terraform) planCmd(ctx context.Context, opts ...PlanOption) (*exec.Cmd, error) {
c := defaultPlanOptions

Expand Down
76 changes: 76 additions & 0 deletions tfexec/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,79 @@ func TestPlanCmd(t *testing.T) {
}, nil, planCmd)
})
}

func TestPlanJSONCmd(t *testing.T) {
td := t.TempDir()

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

// empty env, to avoid environ mismatch in testing
tf.SetEnv(map[string]string{})

t.Run("defaults", func(t *testing.T) {
planCmd, err := tf.planJSONCmd(context.Background())
if err != nil {
t.Fatal(err)
}

assertCmd(t, []string{
"plan",
"-json",
"-no-color",
"-input=false",
"-detailed-exitcode",
"-lock-timeout=0s",
"-lock=true",
"-parallelism=10",
"-refresh=true",
}, nil, planCmd)
})

t.Run("override all defaults", func(t *testing.T) {
planCmd, err := tf.planJSONCmd(context.Background(),
Destroy(true),
Lock(false),
LockTimeout("22s"),
Out("whale"),
Parallelism(42),
Refresh(false),
Replace("ford.prefect"),
Replace("arthur.dent"),
State("marvin"),
Target("zaphod"),
Target("beeblebrox"),
Var("android=paranoid"),
Var("brain_size=planet"),
VarFile("trillian"),
Dir("earth"))
if err != nil {
t.Fatal(err)
}

assertCmd(t, []string{
"plan",
"-json",
"-no-color",
"-input=false",
"-detailed-exitcode",
"-lock-timeout=22s",
"-out=whale",
"-state=marvin",
"-var-file=trillian",
"-lock=false",
"-parallelism=42",
"-refresh=false",
"-replace=ford.prefect",
"-replace=arthur.dent",
"-destroy",
"-target=zaphod",
"-target=beeblebrox",
"-var", "android=paranoid",
"-var", "brain_size=planet",
"earth",
}, nil, planCmd)
})
}
Loading