tfexec: add InitJSON (#478)
Signed-off-by: Bruno Schaatsbergen <git@bschaatsbergen.com>
Co-authored-by: Daniel Banck <dbanck@users.noreply.github.com>
diff --git a/tfexec/apply_test.go b/tfexec/apply_test.go
index fe0420a..d3c6543 100644
--- a/tfexec/apply_test.go
+++ b/tfexec/apply_test.go
@@ -154,7 +154,7 @@
func TestApplyCmd_AllowDeferral(t *testing.T) {
td := t.TempDir()
- tf, err := NewTerraform(td, tfVersion(t, testutil.Alpha_v1_9))
+ tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_Alpha_v1_9))
if err != nil {
t.Fatal(err)
}
diff --git a/tfexec/init.go b/tfexec/init.go
index c292fdc..ac5eea5 100644
--- a/tfexec/init.go
+++ b/tfexec/init.go
@@ -6,6 +6,7 @@
import (
"context"
"fmt"
+ "io"
"os/exec"
)
@@ -99,6 +100,21 @@
conf.verifyPlugins = opt.verifyPlugins
}
+func (tf *Terraform) configureInitOptions(ctx context.Context, c *initConfig, opts ...InitOption) error {
+ for _, o := range opts {
+ switch o.(type) {
+ case *LockOption, *LockTimeoutOption, *VerifyPluginsOption, *GetPluginsOption:
+ err := tf.compatible(ctx, nil, tf0_15_0)
+ if err != nil {
+ return fmt.Errorf("-lock, -lock-timeout, -verify-plugins, and -get-plugins options are no longer available as of Terraform 0.15: %w", err)
+ }
+ }
+
+ o.configureInit(c)
+ }
+ return nil
+}
+
// Init represents the terraform init subcommand.
func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error {
cmd, err := tf.initCmd(ctx, opts...)
@@ -108,21 +124,71 @@
return tf.runTerraformCmd(ctx, cmd)
}
+// InitJSON represents the terraform init subcommand with the `-json` flag.
+// 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) InitJSON(ctx context.Context, w io.Writer, opts ...InitOption) error {
+ err := tf.compatible(ctx, tf1_9_0, nil)
+ if err != nil {
+ return fmt.Errorf("terraform init -json was added in 1.9.0: %w", err)
+ }
+
+ tf.SetStdout(w)
+
+ cmd, err := tf.initJSONCmd(ctx, opts...)
+ if err != nil {
+ return err
+ }
+
+ return tf.runTerraformCmd(ctx, cmd)
+}
+
func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) {
c := defaultInitOptions
- for _, o := range opts {
- switch o.(type) {
- case *LockOption, *LockTimeoutOption, *VerifyPluginsOption, *GetPluginsOption:
- err := tf.compatible(ctx, nil, tf0_15_0)
- if err != nil {
- return nil, fmt.Errorf("-lock, -lock-timeout, -verify-plugins, and -get-plugins options are no longer available as of Terraform 0.15: %w", err)
- }
- }
-
- o.configureInit(&c)
+ err := tf.configureInitOptions(ctx, &c, opts...)
+ if err != nil {
+ return nil, err
}
+ args, err := tf.buildInitArgs(ctx, c)
+ if err != nil {
+ return nil, err
+ }
+
+ // Optional positional argument; must be last as flags precede positional arguments.
+ if c.dir != "" {
+ args = append(args, c.dir)
+ }
+
+ return tf.buildInitCmd(ctx, c, args)
+}
+
+func (tf *Terraform) initJSONCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) {
+ c := defaultInitOptions
+
+ err := tf.configureInitOptions(ctx, &c, opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ args, err := tf.buildInitArgs(ctx, c)
+ if err != nil {
+ return nil, err
+ }
+
+ args = append(args, "-json")
+
+ // Optional positional argument; must be last as flags precede positional arguments.
+ if c.dir != "" {
+ args = append(args, c.dir)
+ }
+
+ return tf.buildInitCmd(ctx, c, args)
+}
+
+func (tf *Terraform) buildInitArgs(ctx context.Context, c initConfig) ([]string, error) {
args := []string{"init", "-no-color", "-input=false"}
// string opts: only pass if set
@@ -172,11 +238,10 @@
}
}
- // optional positional argument
- if c.dir != "" {
- args = append(args, c.dir)
- }
+ return args, nil
+}
+func (tf *Terraform) buildInitCmd(ctx context.Context, c initConfig, args []string) (*exec.Cmd, error) {
mergeEnv := map[string]string{}
if c.reattachInfo != nil {
reattachStr, err := c.reattachInfo.marshalString()
diff --git a/tfexec/init_test.go b/tfexec/init_test.go
index 46b29cf..6f11bab 100644
--- a/tfexec/init_test.go
+++ b/tfexec/init_test.go
@@ -127,3 +127,57 @@
}, nil, initCmd)
})
}
+
+func TestInitJSONCmd(t *testing.T) {
+ td := t.TempDir()
+
+ tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1_9))
+ 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) {
+ // defaults
+ initCmd, err := tf.initJSONCmd(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertCmd(t, []string{
+ "init",
+ "-no-color",
+ "-input=false",
+ "-backend=true",
+ "-get=true",
+ "-upgrade=false",
+ "-json",
+ }, nil, initCmd)
+ })
+
+ t.Run("override all defaults", func(t *testing.T) {
+ initCmd, err := tf.initJSONCmd(context.Background(), Backend(false), BackendConfig("confpath1"), BackendConfig("confpath2"), FromModule("testsource"), Get(false), PluginDir("testdir1"), PluginDir("testdir2"), Reconfigure(true), Upgrade(true), Dir("initdir"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertCmd(t, []string{
+ "init",
+ "-no-color",
+ "-input=false",
+ "-from-module=testsource",
+ "-backend=false",
+ "-get=false",
+ "-upgrade=true",
+ "-reconfigure",
+ "-backend-config=confpath1",
+ "-backend-config=confpath2",
+ "-plugin-dir=testdir1",
+ "-plugin-dir=testdir2",
+ "-json",
+ "initdir",
+ }, nil, initCmd)
+ })
+}
diff --git a/tfexec/internal/e2etest/init_test.go b/tfexec/internal/e2etest/init_test.go
index 1bcc0f9..896e72f 100644
--- a/tfexec/internal/e2etest/init_test.go
+++ b/tfexec/internal/e2etest/init_test.go
@@ -5,11 +5,14 @@
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 TestInit(t *testing.T) {
@@ -20,3 +23,48 @@
}
})
}
+
+func TestInitJSON_TF18AndEarlier(t *testing.T) {
+ versions := []string{
+ testutil.Latest011,
+ testutil.Latest012,
+ testutil.Latest013,
+ testutil.Latest_v1_6,
+ testutil.Latest_v1_7,
+ testutil.Latest_v1_8,
+ }
+
+ runTestWithVersions(t, versions, "basic", 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 init -json was added in 1.9.0")
+
+ err = tf.InitJSON(context.Background(), io.Discard)
+ if err != nil && !re.MatchString(err.Error()) {
+ t.Fatalf("error running Init: %s", err)
+ }
+ })
+}
+
+func TestInitJSON_TF19AndLater(t *testing.T) {
+ versions := []string{
+ testutil.Latest_v1_9,
+ testutil.Latest_Alpha_v1_9,
+ testutil.Latest_Alpha_v1_10,
+ }
+
+ runTestWithVersions(t, versions, "basic", 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.InitJSON(context.Background(), io.Discard)
+ if err != nil {
+ t.Fatalf("error running Init: %s", err)
+ }
+ })
+}
diff --git a/tfexec/internal/testutil/tfcache.go b/tfexec/internal/testutil/tfcache.go
index 82d4ef0..2f79fc2 100644
--- a/tfexec/internal/testutil/tfcache.go
+++ b/tfexec/internal/testutil/tfcache.go
@@ -15,18 +15,21 @@
)
const (
- Latest011 = "0.11.15"
- Latest012 = "0.12.31"
- Latest013 = "0.13.7"
- Latest014 = "0.14.11"
- Latest015 = "0.15.5"
- Latest_v1 = "1.0.11"
- Latest_v1_1 = "1.1.9"
- Latest_v1_5 = "1.5.3"
- Latest_v1_6 = "1.6.0-alpha20230719"
-
- Beta_v1_8 = "1.8.0-beta1"
- Alpha_v1_9 = "1.9.0-alpha20240404"
+ Latest011 = "0.11.15"
+ Latest012 = "0.12.31"
+ Latest013 = "0.13.7"
+ Latest014 = "0.14.11"
+ Latest015 = "0.15.5"
+ Latest_v1 = "1.0.11"
+ Latest_v1_1 = "1.1.9"
+ Latest_v1_5 = "1.5.3"
+ Latest_v1_6 = "1.6.6"
+ Latest_v1_7 = "1.7.5"
+ Latest_v1_8 = "1.8.5"
+ Latest_Beta_v1_8 = "1.8.0-beta1"
+ Latest_v1_9 = "1.9.7"
+ Latest_Alpha_v1_9 = "1.9.0-alpha20240516"
+ Latest_Alpha_v1_10 = "1.10.0-alpha20240926"
)
const appendUserAgent = "tfexec-testutil"
diff --git a/tfexec/plan_test.go b/tfexec/plan_test.go
index e48dbe4..91c13f5 100644
--- a/tfexec/plan_test.go
+++ b/tfexec/plan_test.go
@@ -182,7 +182,7 @@
func TestPlanCmd_AllowDeferral(t *testing.T) {
td := t.TempDir()
- tf, err := NewTerraform(td, tfVersion(t, testutil.Alpha_v1_9))
+ tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_Alpha_v1_9))
if err != nil {
t.Fatal(err)
}
diff --git a/tfexec/version_test.go b/tfexec/version_test.go
index 4d7db85..cf4a774 100644
--- a/tfexec/version_test.go
+++ b/tfexec/version_test.go
@@ -300,16 +300,16 @@
tfVersion *version.Version
expectedError error
}{
- "experiments-enabled-in-1.9.0-alpha20240404": {
- tfVersion: version.Must(version.NewVersion(testutil.Alpha_v1_9)),
+ "experiments-enabled-in-alphas": {
+ tfVersion: version.Must(version.NewVersion(testutil.Latest_Alpha_v1_9)),
},
- "experiments-disabled-in-1.8.0-beta1": {
- tfVersion: version.Must(version.NewVersion(testutil.Beta_v1_8)),
- expectedError: errors.New("experiments are not enabled in version 1.8.0-beta1, as it's not an alpha or dev build"),
+ "experiments-disabled-in-betas": {
+ tfVersion: version.Must(version.NewVersion(testutil.Latest_Beta_v1_8)),
+ expectedError: fmt.Errorf("experiments are not enabled in version %s, as it's not an alpha or dev build", testutil.Latest_Beta_v1_8),
},
- "experiments-disabled-in-1.5.3": {
+ "experiments-disabled-in-stable": {
tfVersion: version.Must(version.NewVersion(testutil.Latest_v1_5)),
- expectedError: errors.New("experiments are not enabled in version 1.5.3, as it's not an alpha or dev build"),
+ expectedError: fmt.Errorf("experiments are not enabled in version %s, as it's not an alpha or dev build", testutil.Latest_v1_5),
},
}
for name, testCase := range testCases {