feat: Add support for `workspace list -json`
diff --git a/tfexec/internal/testutil/tfcache.go b/tfexec/internal/testutil/tfcache.go index 60dacd6..ec5ac21 100644 --- a/tfexec/internal/testutil/tfcache.go +++ b/tfexec/internal/testutil/tfcache.go
@@ -38,8 +38,10 @@ Latest_v1_11 = "1.11.4" Latest_v1_12 = "1.12.2" Latest_Alpha_v1_14 = "1.14.0-alpha20250903" + Latest_v1_15 = "1.15.1" + Latest_v1_16 = "1.16.0" - Latest_v1 = "1.14.0" + Latest_v1 = "1.16.1" ) const appendUserAgent = "tfexec-testutil"
diff --git a/tfexec/version.go b/tfexec/version.go index aa0926a..751c9b2 100644 --- a/tfexec/version.go +++ b/tfexec/version.go
@@ -39,6 +39,7 @@ tf1_10_0 = version.Must(version.NewVersion("1.10.0")) tf1_13_0 = version.Must(version.NewVersion("1.13.0")) tf1_14_0 = version.Must(version.NewVersion("1.14.0")) + tf1_16_0 = version.Must(version.NewVersion("1.16.0")) ) // Version returns structured output from the terraform version command including both the Terraform CLI version
diff --git a/tfexec/workspace_list.go b/tfexec/workspace_list.go index 6162fd8..a12751c 100644 --- a/tfexec/workspace_list.go +++ b/tfexec/workspace_list.go
@@ -5,8 +5,12 @@ import ( "context" + "fmt" + "io" "os/exec" "strings" + + tfjson "github.com/hashicorp/terraform-json" ) type workspaceListConfig struct { @@ -79,6 +83,55 @@ return workspaces, current } +// WorkspaceListJSON represents the terraform workspace list 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`. WorkspaceListJSON is likely to be +// removed in a future major version in favour of WorkspaceList returning JSON by default. +func (tf *Terraform) WorkspaceListJSON(ctx context.Context, w io.Writer, opts ...WorkspaceListOption) (*tfjson.WorkspaceListOutput, error) { + err := tf.compatible(ctx, tf1_16_0, nil) + if err != nil { + return nil, fmt.Errorf("terraform workspace list -json was added in 1.16.0: %w", err) + } + + tf.SetStdout(w) + + cmd, err := tf.workspaceListJSONCmd(ctx, opts...) + if err != nil { + return nil, err + } + + // Here we need the JSON representation of the workspace list JSON output. + var output tfjson.WorkspaceListOutput + + err = tf.runTerraformCmdJSON(ctx, cmd, &output) + if err != nil { + return nil, err + } + + return &output, nil +} + +func (tf *Terraform) workspaceListJSONCmd(ctx context.Context, opts ...WorkspaceListOption) (*exec.Cmd, error) { + c := defaultWorkspaceListOptions + + for _, o := range opts { + o.configureWorkspaceList(&c) + } + + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := c.reattachInfo.marshalString() + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + args := []string{"workspace", "list", "-json"} + return tf.buildWorkspaceListCmd(ctx, c, args) +} + func (tf *Terraform) buildWorkspaceListCmd(ctx context.Context, c workspaceListConfig, args []string) (*exec.Cmd, error) { mergeEnv := map[string]string{} if c.reattachInfo != nil {
diff --git a/tfexec/workspace_list_test.go b/tfexec/workspace_list_test.go index 917cea1..9862614 100644 --- a/tfexec/workspace_list_test.go +++ b/tfexec/workspace_list_test.go
@@ -59,6 +59,53 @@ }) } +func TestWorkspaceListCmdJSON(t *testing.T) { + tf, err := NewTerraform(t.TempDir(), 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) { + workspaceListCmd, err := tf.workspaceListJSONCmd(context.Background()) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "workspace", "list", + "-json", + }, nil, workspaceListCmd) + }) + + t.Run("reattach config", func(t *testing.T) { + workspaceListCmd, err := tf.workspaceListJSONCmd(context.Background(), Reattach(map[string]ReattachConfig{ + "registry.terraform.io/hashicorp/examplecloud": { + Protocol: "grpc", + ProtocolVersion: 6, + Pid: 1234, + Test: true, + Addr: ReattachConfigAddr{ + Network: "unix", + String: "/fake_folder/T/plugin123", + }, + }, + })) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "workspace", "list", + "-json", + }, map[string]string{ + "TF_REATTACH_PROVIDERS": `{"registry.terraform.io/hashicorp/examplecloud":{"Protocol":"grpc","ProtocolVersion":6,"Pid":1234,"Test":true,"Addr":{"Network":"unix","String":"/fake_folder/T/plugin123"}}}`, + }, workspaceListCmd) + }) +} + func TestParseWorkspaceListHumanOutput(t *testing.T) { for i, c := range []struct { expected []string