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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.18.0 (unreleased)

ENHANCEMENTS:

- tfexec: Add `(Terraform).ApplyJSON()`, `(Terraform).DestroyJSON()`, `(Terraform).PlanJSON()` and `(Terraform).RefreshJSON()` methods ([#354](https://github.com/hashicorp/terraform-exec/pull/354))

# 0.17.3 (August 31, 2022)

Please note that terraform-exec now requires Go 1.18.
Expand Down
35 changes: 34 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,38 @@ 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
// Using the `-json` flag will result in
// [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui)
// JSON being written to the supplied `io.Writer`.
func (tf *Terraform) ApplyJSON(ctx context.Context, w io.Writer, opts ...ApplyOption) error {
err := tf.compatible(ctx, tf0_15_3, nil)
if err != nil {
return fmt.Errorf("terraform apply -json was added in 0.15.3: %w", err)
}

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)
})
}
35 changes: 34 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,38 @@ 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
// Using the `-json` flag will result in
// [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui)
// JSON being written to the supplied `io.Writer`.
func (tf *Terraform) DestroyJSON(ctx context.Context, w io.Writer, opts ...DestroyOption) error {
err := tf.compatible(ctx, tf0_15_3, nil)
if err != nil {
return fmt.Errorf("terraform apply -json was added in 0.15.3: %w", err)
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
}

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/internal/e2etest/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package e2etest

import (
"context"
"io"
"regexp"
"testing"

"github.com/hashicorp/go-version"

"github.com/hashicorp/terraform-exec/tfexec"
"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
)

func TestApply(t *testing.T) {
Expand All @@ -22,3 +25,37 @@ func TestApply(t *testing.T) {
}
})
}

func TestApplyJSON_TF014AndEarlier(t *testing.T) {
versions := []string{testutil.Latest011, testutil.Latest012, testutil.Latest013, testutil.Latest014}

runTestWithVersions(t, "basic", versions, func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
err := tf.Init(context.Background())
if err != nil {
t.Fatalf("error running Init in test directory: %s", err)
}

re := regexp.MustCompile("terraform apply -json was added in 0.15.3")

err = tf.ApplyJSON(context.Background(), io.Discard)
if err != nil && !re.MatchString(err.Error()) {
t.Fatalf("error running Apply: %s", err)
}
})
}

func TestApplyJSON_TF015AndLater(t *testing.T) {
versions := []string{testutil.Latest015, testutil.Latest_v1, testutil.Latest_v1_1}

runTestWithVersions(t, "basic", versions, func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
err := tf.Init(context.Background())
if err != nil {
t.Fatalf("error running Init in test directory: %s", err)
}

err = tf.ApplyJSON(context.Background(), io.Discard)
if err != nil {
t.Fatalf("error running Apply: %s", err)
}
})
}
37 changes: 37 additions & 0 deletions tfexec/internal/e2etest/destroy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package e2etest

import (
"context"
"io"
"regexp"
"testing"

"github.com/hashicorp/go-version"

"github.com/hashicorp/terraform-exec/tfexec"
"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
)

func TestDestroy(t *testing.T) {
Expand All @@ -27,3 +30,37 @@ func TestDestroy(t *testing.T) {
}
})
}

func TestDestroyJSON_TF014AndEarlier(t *testing.T) {
versions := []string{testutil.Latest011, testutil.Latest012, testutil.Latest013, testutil.Latest014}

runTestWithVersions(t, "basic", versions, func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
err := tf.Init(context.Background())
if err != nil {
t.Fatalf("error running Init in test directory: %s", err)
}

re := regexp.MustCompile("terraform apply -json was added in 0.15.3")
bendbennett marked this conversation as resolved.
Show resolved Hide resolved

err = tf.DestroyJSON(context.Background(), io.Discard)
if err != nil && !re.MatchString(err.Error()) {
t.Fatalf("error running Apply: %s", err)
}
})
}

func TestDestroyJSON_TF015AndLater(t *testing.T) {
versions := []string{testutil.Latest015, testutil.Latest_v1, testutil.Latest_v1_1}

runTestWithVersions(t, "basic", versions, func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
err := tf.Init(context.Background())
if err != nil {
t.Fatalf("error running Init in test directory: %s", err)
}

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