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

tfexec: add InitJSON #478

Merged
merged 21 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
820816d
tfexec: split init code out to support `InitJSON`
bschaatsbergen Oct 6, 2024
0bd7077
fix: missing closing parenthesis
bschaatsbergen Oct 6, 2024
90f2360
tfexec: add `InitJSON` and `initJSONCmd`
bschaatsbergen Oct 6, 2024
6af74c5
testutil: add latest 1.6, 1.7 and 1.8 releases
bschaatsbergen Oct 6, 2024
d7e2db6
e2etest: add `InitJSON` tests
bschaatsbergen Oct 6, 2024
6761406
testutil: fix 1.10 alpha prelease version
bschaatsbergen Oct 6, 2024
160e8e8
refactor: reuse latest version constants
bschaatsbergen Oct 6, 2024
1589e33
tfexec: add `initJSONCmd` tests
bschaatsbergen Oct 6, 2024
1b71d09
tfexec: move optional positional argument to init cmd; as flags prece…
bschaatsbergen Oct 6, 2024
073ec1e
Merge branch 'main' into f/add-initjson
bschaatsbergen Oct 7, 2024
3c17379
Merge branch 'main' into f/add-initjson
bschaatsbergen Oct 21, 2024
74d8c31
Merge branch 'main' into f/add-initjson
bschaatsbergen Oct 28, 2024
e49b28f
Merge branch 'main' into f/add-initjson
bschaatsbergen Nov 8, 2024
63e7f56
Merge branch 'main' into f/add-initjson
bschaatsbergen Nov 27, 2024
f0f6fe3
Merge branch 'main' into f/add-initjson
bschaatsbergen Dec 6, 2024
758f95f
Merge branch 'main' into f/add-initjson
bschaatsbergen Dec 16, 2024
d7a13e6
Merge branch 'main' into f/add-initjson
bschaatsbergen Jan 16, 2025
32585e8
tfexec: apply suggestion from @dbanck
bschaatsbergen Jan 20, 2025
84c22d7
tfexec: apply suggestion from @dbanck
bschaatsbergen Jan 20, 2025
3a908ab
tfexec: apply suggestion from @dbanck
bschaatsbergen Jan 20, 2025
bc12a17
Merge branch 'main' into f/add-initjson
bschaatsbergen Jan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tfexec/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func TestApplyJSONCmd(t *testing.T) {
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)
}
Expand Down
91 changes: 78 additions & 13 deletions tfexec/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package tfexec
import (
"context"
"fmt"
"io"
"os/exec"
)

Expand Down Expand Up @@ -99,6 +100,21 @@ func (opt *VerifyPluginsOption) configureInit(conf *initConfig) {
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...)
Expand All @@ -108,21 +124,71 @@ func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error {
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)
}
}
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
}

o.configureInit(&c)
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
Expand Down Expand Up @@ -172,11 +238,10 @@ func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd
}
}

// 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()
Expand Down
54 changes: 54 additions & 0 deletions tfexec/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,57 @@ func TestInitCmd_v1(t *testing.T) {
}, 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)
})
}
48 changes: 48 additions & 0 deletions tfexec/internal/e2etest/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,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 TestInit(t *testing.T) {
Expand All @@ -20,3 +23,48 @@ func TestInit(t *testing.T) {
}
})
}

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)
}
})
}
27 changes: 15 additions & 12 deletions tfexec/internal/testutil/tfcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,21 @@ import (
)

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"
Expand Down
2 changes: 1 addition & 1 deletion tfexec/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func TestPlanJSONCmd(t *testing.T) {
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)
}
Expand Down
14 changes: 7 additions & 7 deletions tfexec/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,16 +300,16 @@ func TestExperimentsEnabled(t *testing.T) {
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 {
Expand Down
Loading