Skip to content

Commit abfb5ba

Browse files
tfexec: add InitJSON (#478)
Signed-off-by: Bruno Schaatsbergen <[email protected]> Co-authored-by: Daniel Banck <[email protected]>
1 parent 840ecad commit abfb5ba

File tree

7 files changed

+204
-34
lines changed

7 files changed

+204
-34
lines changed

tfexec/apply_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func TestApplyJSONCmd(t *testing.T) {
154154
func TestApplyCmd_AllowDeferral(t *testing.T) {
155155
td := t.TempDir()
156156

157-
tf, err := NewTerraform(td, tfVersion(t, testutil.Alpha_v1_9))
157+
tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_Alpha_v1_9))
158158
if err != nil {
159159
t.Fatal(err)
160160
}

tfexec/init.go

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package tfexec
66
import (
77
"context"
88
"fmt"
9+
"io"
910
"os/exec"
1011
)
1112

@@ -99,6 +100,21 @@ func (opt *VerifyPluginsOption) configureInit(conf *initConfig) {
99100
conf.verifyPlugins = opt.verifyPlugins
100101
}
101102

103+
func (tf *Terraform) configureInitOptions(ctx context.Context, c *initConfig, opts ...InitOption) error {
104+
for _, o := range opts {
105+
switch o.(type) {
106+
case *LockOption, *LockTimeoutOption, *VerifyPluginsOption, *GetPluginsOption:
107+
err := tf.compatible(ctx, nil, tf0_15_0)
108+
if err != nil {
109+
return fmt.Errorf("-lock, -lock-timeout, -verify-plugins, and -get-plugins options are no longer available as of Terraform 0.15: %w", err)
110+
}
111+
}
112+
113+
o.configureInit(c)
114+
}
115+
return nil
116+
}
117+
102118
// Init represents the terraform init subcommand.
103119
func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error {
104120
cmd, err := tf.initCmd(ctx, opts...)
@@ -108,21 +124,71 @@ func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error {
108124
return tf.runTerraformCmd(ctx, cmd)
109125
}
110126

127+
// InitJSON represents the terraform init subcommand with the `-json` flag.
128+
// Using the `-json` flag will result in
129+
// [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui)
130+
// JSON being written to the supplied `io.Writer`.
131+
func (tf *Terraform) InitJSON(ctx context.Context, w io.Writer, opts ...InitOption) error {
132+
err := tf.compatible(ctx, tf1_9_0, nil)
133+
if err != nil {
134+
return fmt.Errorf("terraform init -json was added in 1.9.0: %w", err)
135+
}
136+
137+
tf.SetStdout(w)
138+
139+
cmd, err := tf.initJSONCmd(ctx, opts...)
140+
if err != nil {
141+
return err
142+
}
143+
144+
return tf.runTerraformCmd(ctx, cmd)
145+
}
146+
111147
func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) {
112148
c := defaultInitOptions
113149

114-
for _, o := range opts {
115-
switch o.(type) {
116-
case *LockOption, *LockTimeoutOption, *VerifyPluginsOption, *GetPluginsOption:
117-
err := tf.compatible(ctx, nil, tf0_15_0)
118-
if err != nil {
119-
return nil, fmt.Errorf("-lock, -lock-timeout, -verify-plugins, and -get-plugins options are no longer available as of Terraform 0.15: %w", err)
120-
}
121-
}
150+
err := tf.configureInitOptions(ctx, &c, opts...)
151+
if err != nil {
152+
return nil, err
153+
}
154+
155+
args, err := tf.buildInitArgs(ctx, c)
156+
if err != nil {
157+
return nil, err
158+
}
159+
160+
// Optional positional argument; must be last as flags precede positional arguments.
161+
if c.dir != "" {
162+
args = append(args, c.dir)
163+
}
164+
165+
return tf.buildInitCmd(ctx, c, args)
166+
}
167+
168+
func (tf *Terraform) initJSONCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) {
169+
c := defaultInitOptions
170+
171+
err := tf.configureInitOptions(ctx, &c, opts...)
172+
if err != nil {
173+
return nil, err
174+
}
122175

123-
o.configureInit(&c)
176+
args, err := tf.buildInitArgs(ctx, c)
177+
if err != nil {
178+
return nil, err
179+
}
180+
181+
args = append(args, "-json")
182+
183+
// Optional positional argument; must be last as flags precede positional arguments.
184+
if c.dir != "" {
185+
args = append(args, c.dir)
124186
}
125187

188+
return tf.buildInitCmd(ctx, c, args)
189+
}
190+
191+
func (tf *Terraform) buildInitArgs(ctx context.Context, c initConfig) ([]string, error) {
126192
args := []string{"init", "-no-color", "-input=false"}
127193

128194
// string opts: only pass if set
@@ -172,11 +238,10 @@ func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd
172238
}
173239
}
174240

175-
// optional positional argument
176-
if c.dir != "" {
177-
args = append(args, c.dir)
178-
}
241+
return args, nil
242+
}
179243

244+
func (tf *Terraform) buildInitCmd(ctx context.Context, c initConfig, args []string) (*exec.Cmd, error) {
180245
mergeEnv := map[string]string{}
181246
if c.reattachInfo != nil {
182247
reattachStr, err := c.reattachInfo.marshalString()

tfexec/init_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,57 @@ func TestInitCmd_v1(t *testing.T) {
127127
}, nil, initCmd)
128128
})
129129
}
130+
131+
func TestInitJSONCmd(t *testing.T) {
132+
td := t.TempDir()
133+
134+
tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1_9))
135+
if err != nil {
136+
t.Fatal(err)
137+
}
138+
139+
// empty env, to avoid environ mismatch in testing
140+
tf.SetEnv(map[string]string{})
141+
142+
t.Run("defaults", func(t *testing.T) {
143+
// defaults
144+
initCmd, err := tf.initJSONCmd(context.Background())
145+
if err != nil {
146+
t.Fatal(err)
147+
}
148+
149+
assertCmd(t, []string{
150+
"init",
151+
"-no-color",
152+
"-input=false",
153+
"-backend=true",
154+
"-get=true",
155+
"-upgrade=false",
156+
"-json",
157+
}, nil, initCmd)
158+
})
159+
160+
t.Run("override all defaults", func(t *testing.T) {
161+
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"))
162+
if err != nil {
163+
t.Fatal(err)
164+
}
165+
166+
assertCmd(t, []string{
167+
"init",
168+
"-no-color",
169+
"-input=false",
170+
"-from-module=testsource",
171+
"-backend=false",
172+
"-get=false",
173+
"-upgrade=true",
174+
"-reconfigure",
175+
"-backend-config=confpath1",
176+
"-backend-config=confpath2",
177+
"-plugin-dir=testdir1",
178+
"-plugin-dir=testdir2",
179+
"-json",
180+
"initdir",
181+
}, nil, initCmd)
182+
})
183+
}

tfexec/internal/e2etest/init_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ package e2etest
55

66
import (
77
"context"
8+
"io"
9+
"regexp"
810
"testing"
911

1012
"github.com/hashicorp/go-version"
1113

1214
"github.com/hashicorp/terraform-exec/tfexec"
15+
"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
1316
)
1417

1518
func TestInit(t *testing.T) {
@@ -20,3 +23,48 @@ func TestInit(t *testing.T) {
2023
}
2124
})
2225
}
26+
27+
func TestInitJSON_TF18AndEarlier(t *testing.T) {
28+
versions := []string{
29+
testutil.Latest011,
30+
testutil.Latest012,
31+
testutil.Latest013,
32+
testutil.Latest_v1_6,
33+
testutil.Latest_v1_7,
34+
testutil.Latest_v1_8,
35+
}
36+
37+
runTestWithVersions(t, versions, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
38+
err := tf.Init(context.Background())
39+
if err != nil {
40+
t.Fatalf("error running Init in test directory: %s", err)
41+
}
42+
43+
re := regexp.MustCompile("terraform init -json was added in 1.9.0")
44+
45+
err = tf.InitJSON(context.Background(), io.Discard)
46+
if err != nil && !re.MatchString(err.Error()) {
47+
t.Fatalf("error running Init: %s", err)
48+
}
49+
})
50+
}
51+
52+
func TestInitJSON_TF19AndLater(t *testing.T) {
53+
versions := []string{
54+
testutil.Latest_v1_9,
55+
testutil.Latest_Alpha_v1_9,
56+
testutil.Latest_Alpha_v1_10,
57+
}
58+
59+
runTestWithVersions(t, versions, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
60+
err := tf.Init(context.Background())
61+
if err != nil {
62+
t.Fatalf("error running Init in test directory: %s", err)
63+
}
64+
65+
err = tf.InitJSON(context.Background(), io.Discard)
66+
if err != nil {
67+
t.Fatalf("error running Init: %s", err)
68+
}
69+
})
70+
}

tfexec/internal/testutil/tfcache.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,21 @@ import (
1515
)
1616

1717
const (
18-
Latest011 = "0.11.15"
19-
Latest012 = "0.12.31"
20-
Latest013 = "0.13.7"
21-
Latest014 = "0.14.11"
22-
Latest015 = "0.15.5"
23-
Latest_v1 = "1.0.11"
24-
Latest_v1_1 = "1.1.9"
25-
Latest_v1_5 = "1.5.3"
26-
Latest_v1_6 = "1.6.0-alpha20230719"
27-
28-
Beta_v1_8 = "1.8.0-beta1"
29-
Alpha_v1_9 = "1.9.0-alpha20240404"
18+
Latest011 = "0.11.15"
19+
Latest012 = "0.12.31"
20+
Latest013 = "0.13.7"
21+
Latest014 = "0.14.11"
22+
Latest015 = "0.15.5"
23+
Latest_v1 = "1.0.11"
24+
Latest_v1_1 = "1.1.9"
25+
Latest_v1_5 = "1.5.3"
26+
Latest_v1_6 = "1.6.6"
27+
Latest_v1_7 = "1.7.5"
28+
Latest_v1_8 = "1.8.5"
29+
Latest_Beta_v1_8 = "1.8.0-beta1"
30+
Latest_v1_9 = "1.9.7"
31+
Latest_Alpha_v1_9 = "1.9.0-alpha20240516"
32+
Latest_Alpha_v1_10 = "1.10.0-alpha20240926"
3033
)
3134

3235
const appendUserAgent = "tfexec-testutil"

tfexec/plan_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ func TestPlanJSONCmd(t *testing.T) {
182182
func TestPlanCmd_AllowDeferral(t *testing.T) {
183183
td := t.TempDir()
184184

185-
tf, err := NewTerraform(td, tfVersion(t, testutil.Alpha_v1_9))
185+
tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_Alpha_v1_9))
186186
if err != nil {
187187
t.Fatal(err)
188188
}

tfexec/version_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -300,16 +300,16 @@ func TestExperimentsEnabled(t *testing.T) {
300300
tfVersion *version.Version
301301
expectedError error
302302
}{
303-
"experiments-enabled-in-1.9.0-alpha20240404": {
304-
tfVersion: version.Must(version.NewVersion(testutil.Alpha_v1_9)),
303+
"experiments-enabled-in-alphas": {
304+
tfVersion: version.Must(version.NewVersion(testutil.Latest_Alpha_v1_9)),
305305
},
306-
"experiments-disabled-in-1.8.0-beta1": {
307-
tfVersion: version.Must(version.NewVersion(testutil.Beta_v1_8)),
308-
expectedError: errors.New("experiments are not enabled in version 1.8.0-beta1, as it's not an alpha or dev build"),
306+
"experiments-disabled-in-betas": {
307+
tfVersion: version.Must(version.NewVersion(testutil.Latest_Beta_v1_8)),
308+
expectedError: fmt.Errorf("experiments are not enabled in version %s, as it's not an alpha or dev build", testutil.Latest_Beta_v1_8),
309309
},
310-
"experiments-disabled-in-1.5.3": {
310+
"experiments-disabled-in-stable": {
311311
tfVersion: version.Must(version.NewVersion(testutil.Latest_v1_5)),
312-
expectedError: errors.New("experiments are not enabled in version 1.5.3, as it's not an alpha or dev build"),
312+
expectedError: fmt.Errorf("experiments are not enabled in version %s, as it's not an alpha or dev build", testutil.Latest_v1_5),
313313
},
314314
}
315315
for name, testCase := range testCases {

0 commit comments

Comments
 (0)