From cb6d9140ab32daaeea200526386e612ce5a1e482 Mon Sep 17 00:00:00 2001 From: "pavlo.kozlov" Date: Mon, 26 Oct 2026 12:16:54 +0000 Subject: [PATCH 1/4] Report workspace_id in bundle debug list-targets `bundle debug list-targets` now reports each target's workspace ID next to its host. Both are normalized the same way authentication normalizes them: a ?o=, ?w= or ?workspace_id= query parameter in the host becomes the workspace ID, an explicit workspace.workspace_id takes precedence, and the query is removed from the host. Co-authored-by: Isaac --- .../bundle/debug/list-targets/databricks.yml | 11 ++++++++++ .../bundle/debug/list-targets/output.txt | 18 ++++++++++++++++ cmd/bundle/debug/list_targets.go | 21 +++++++++++++------ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/acceptance/bundle/debug/list-targets/databricks.yml b/acceptance/bundle/debug/list-targets/databricks.yml index 438256ff729..8194820880c 100644 --- a/acceptance/bundle/debug/list-targets/databricks.yml +++ b/acceptance/bundle/debug/list-targets/databricks.yml @@ -20,6 +20,17 @@ targets: mode: production workspace: host: https://prod.example.com + custom_host_query_id: + workspace: + host: https://custom.test/?o=1234 + custom_host_explicit_id: + workspace: + host: https://custom.test + workspace_id: 5678 + custom_host_explicit_id_over_query: + workspace: + host: https://custom.test/?o=1234 + workspace_id: "5678" uses_terraform: bundle: engine: terraform diff --git a/acceptance/bundle/debug/list-targets/output.txt b/acceptance/bundle/debug/list-targets/output.txt index abf957e8729..a3bf1d27634 100644 --- a/acceptance/bundle/debug/list-targets/output.txt +++ b/acceptance/bundle/debug/list-targets/output.txt @@ -1,5 +1,8 @@ >>> [CLI] bundle debug list-targets +custom_host_explicit_id https://custom.test workspace_id:5678 +custom_host_explicit_id_over_query https://custom.test workspace_id:5678 +custom_host_query_id https://custom.test workspace_id:1234 dev (default) development https://dev.example.com prod production https://prod.example.com staging @@ -10,6 +13,21 @@ with_vars ${var.target_mode} ${var.target_host} >>> [CLI] bundle debug list-targets --output json { "targets": [ + { + "name": "custom_host_explicit_id", + "host": "https://custom.test", + "workspace_id": "5678" + }, + { + "name": "custom_host_explicit_id_over_query", + "host": "https://custom.test", + "workspace_id": "5678" + }, + { + "name": "custom_host_query_id", + "host": "https://custom.test", + "workspace_id": "1234" + }, { "name": "dev", "default": true, diff --git a/cmd/bundle/debug/list_targets.go b/cmd/bundle/debug/list_targets.go index ebd038da11d..5cb9fe3d2f0 100644 --- a/cmd/bundle/debug/list_targets.go +++ b/cmd/bundle/debug/list_targets.go @@ -20,10 +20,11 @@ import ( ) type targetInfo struct { - Name string `json:"name"` - Default bool `json:"default,omitempty"` - Mode config.Mode `json:"mode,omitempty"` - Host string `json:"host,omitempty"` + Name string `json:"name"` + Default bool `json:"default,omitempty"` + Mode config.Mode `json:"mode,omitempty"` + Host string `json:"host,omitempty"` + WorkspaceID string `json:"workspace_id,omitempty"` // Engine is only reported for targets that use the removed Terraform engine. Engine engine.EngineType `json:"engine,omitempty"` @@ -53,7 +54,12 @@ func collectTargets(targets map[string]*config.Target, rootEngine engine.EngineT Mode: t.Mode, } if t.Workspace != nil { - info.Host = t.Workspace.Host + // Normalize a copy so the host and workspace ID match what + // authentication uses, e.g. ?o= in a custom host becomes the ID. + w := *t.Workspace + w.NormalizeHostURL() + info.Host = w.Host + info.WorkspaceID = w.WorkspaceID } targetEngine := rootEngine if t.Bundle != nil && t.Bundle.Engine != engine.EngineNotSet { @@ -73,7 +79,7 @@ func terraformOnly(e engine.EngineType) engine.EngineType { } // NewListTargetsCommand returns a command that lists all bundle targets -// with their name, default, mode, workspace host, and (if terraform) engine fields. +// with their name, default, mode, workspace host and ID, and (if terraform) engine fields. func NewListTargetsCommand() *cobra.Command { cmd := &cobra.Command{ Use: "list-targets", @@ -112,6 +118,9 @@ func NewListTargetsCommand() *cobra.Command { if t.Host != "" { parts = append(parts, t.Host) } + if t.WorkspaceID != "" { + parts = append(parts, "workspace_id:"+t.WorkspaceID) + } if t.Engine != "" { parts = append(parts, "engine:"+string(t.Engine)) } From 479aff050de5b74d9962fbb1bb91ad024dca306b Mon Sep 17 00:00:00 2001 From: "pavlo.kozlov" Date: Mon, 26 Oct 2026 12:41:33 +0000 Subject: [PATCH 2/4] Merge top-level workspace settings in bundle debug list-targets list-targets normalized only the target's own workspace section, so a target inherited neither the top-level host nor the top-level workspace_id. With a top-level workspace_id of 5678 and a target host of https://custom.test/?o=1234, it reported 1234 while authentication uses 5678. Merge the target's workspace over the top-level one, as MergeTargetOverrides does, before normalizing. Co-authored-by: Isaac --- .../databricks.yml | 17 +++++ .../list-targets-root-workspace/out.test.toml | 2 + .../list-targets-root-workspace/output.txt | 33 ++++++++++ .../debug/list-targets-root-workspace/script | 2 + bundle/config/root.go | 22 +++++++ bundle/config/root_test.go | 63 +++++++++++++++++++ cmd/bundle/debug/list_targets.go | 44 +++++++------ 7 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 acceptance/bundle/debug/list-targets-root-workspace/databricks.yml create mode 100644 acceptance/bundle/debug/list-targets-root-workspace/out.test.toml create mode 100644 acceptance/bundle/debug/list-targets-root-workspace/output.txt create mode 100644 acceptance/bundle/debug/list-targets-root-workspace/script diff --git a/acceptance/bundle/debug/list-targets-root-workspace/databricks.yml b/acceptance/bundle/debug/list-targets-root-workspace/databricks.yml new file mode 100644 index 00000000000..8732d647fbd --- /dev/null +++ b/acceptance/bundle/debug/list-targets-root-workspace/databricks.yml @@ -0,0 +1,17 @@ +bundle: + name: test-bundle + +workspace: + host: https://custom.test + workspace_id: "5678" + +targets: + inherited: + default: true + target_query_id: + workspace: + host: https://custom.test/?o=1234 + target_workspace_id: + workspace: + workspace_id: "9012" + null_target: diff --git a/acceptance/bundle/debug/list-targets-root-workspace/out.test.toml b/acceptance/bundle/debug/list-targets-root-workspace/out.test.toml new file mode 100644 index 00000000000..a927a5fbc06 --- /dev/null +++ b/acceptance/bundle/debug/list-targets-root-workspace/out.test.toml @@ -0,0 +1,2 @@ +Cloud = false +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/debug/list-targets-root-workspace/output.txt b/acceptance/bundle/debug/list-targets-root-workspace/output.txt new file mode 100644 index 00000000000..176eb4511b4 --- /dev/null +++ b/acceptance/bundle/debug/list-targets-root-workspace/output.txt @@ -0,0 +1,33 @@ + +>>> [CLI] bundle debug list-targets +inherited (default) https://custom.test workspace_id:5678 +null_target https://custom.test workspace_id:5678 +target_query_id https://custom.test workspace_id:5678 +target_workspace_id https://custom.test workspace_id:9012 + +>>> [CLI] bundle debug list-targets --output json +{ + "targets": [ + { + "name": "inherited", + "default": true, + "host": "https://custom.test", + "workspace_id": "5678" + }, + { + "name": "null_target", + "host": "https://custom.test", + "workspace_id": "5678" + }, + { + "name": "target_query_id", + "host": "https://custom.test", + "workspace_id": "5678" + }, + { + "name": "target_workspace_id", + "host": "https://custom.test", + "workspace_id": "9012" + } + ] +} \ No newline at end of file diff --git a/acceptance/bundle/debug/list-targets-root-workspace/script b/acceptance/bundle/debug/list-targets-root-workspace/script new file mode 100644 index 00000000000..fb699dee0cc --- /dev/null +++ b/acceptance/bundle/debug/list-targets-root-workspace/script @@ -0,0 +1,2 @@ +trace $CLI bundle debug list-targets +trace $CLI bundle debug list-targets --output json diff --git a/bundle/config/root.go b/bundle/config/root.go index a64b742630e..bbff6a7f04e 100644 --- a/bundle/config/root.go +++ b/bundle/config/root.go @@ -326,6 +326,28 @@ func mergeField(rv, ov dyn.Value, name string) (dyn.Value, error) { return dyn.SetByPath(rv, path, out) } +// TargetWorkspace returns the workspace settings of the named target as +// [Root.MergeTargetOverrides] merges them, without modifying the root. +func (r *Root) TargetWorkspace(name string) (Workspace, error) { + target, err := dyn.GetByPath(r.value, dyn.NewPath(dyn.Key("targets"), dyn.Key(name))) + if err != nil { + return Workspace{}, err + } + merged, err := mergeField(r.value, target, "workspace") + if err != nil { + return Workspace{}, fmt.Errorf("failed to merge target=%s field=workspace: %w", name, err) + } + + var w Workspace + // Neither the root nor the target sets a workspace section. + v := merged.Get("workspace") + if !v.IsValid() { + return w, nil + } + err = convert.ToTyped(&w, v) + return w, err +} + func (r *Root) MergeTargetOverrides(name string) error { root := r.value target, err := dyn.GetByPath(root, dyn.NewPath(dyn.Key("targets"), dyn.Key(name))) diff --git a/bundle/config/root_test.go b/bundle/config/root_test.go index f735c44405e..d654af12add 100644 --- a/bundle/config/root_test.go +++ b/bundle/config/root_test.go @@ -252,3 +252,66 @@ func TestIsFullVariableOverrideDef(t *testing.T) { assert.Equal(t, tc.expected, isFullVariableOverrideDef(tc.value), "test case %d", i) } } + +const targetWorkspaceConfig = ` +bundle: + name: test +workspace: + host: https://custom.test + workspace_id: "5678" +targets: + inherited: + host_override: + workspace: + host: https://custom.test/?o=1234 + id_override: + workspace: + workspace_id: "9012" +` + +func TestRootTargetWorkspace(t *testing.T) { + root, diags := LoadFromBytes("databricks.yml", []byte(targetWorkspaceConfig)) + require.Empty(t, diags) + + for _, tc := range []struct { + target string + host string + workspaceID string + }{ + {"inherited", "https://custom.test", "5678"}, + {"host_override", "https://custom.test/?o=1234", "5678"}, + {"id_override", "https://custom.test", "9012"}, + } { + t.Run(tc.target, func(t *testing.T) { + w, err := root.TargetWorkspace(tc.target) + require.NoError(t, err) + assert.Equal(t, tc.host, w.Host) + assert.Equal(t, tc.workspaceID, w.WorkspaceID) + + merged, diags := LoadFromBytes("databricks.yml", []byte(targetWorkspaceConfig)) + require.Empty(t, diags) + require.NoError(t, merged.MergeTargetOverrides(tc.target)) + assert.Equal(t, merged.Workspace, w) + }) + } + + assert.Equal(t, "5678", root.Workspace.WorkspaceID) + assert.Len(t, root.Targets, 3) +} + +func TestRootTargetWorkspaceWithoutWorkspace(t *testing.T) { + root, diags := LoadFromBytes("databricks.yml", []byte("targets:\n dev:\n")) + require.Empty(t, diags) + + w, err := root.TargetWorkspace("dev") + require.NoError(t, err) + assert.Equal(t, Workspace{}, w) +} + +func TestRootTargetWorkspaceUnknownTarget(t *testing.T) { + root, diags := LoadFromBytes("databricks.yml", []byte("targets:\n dev:\n")) + require.Empty(t, diags) + + _, err := root.TargetWorkspace("prod") + assert.Error(t, err) +} diff --git a/cmd/bundle/debug/list_targets.go b/cmd/bundle/debug/list_targets.go index 5cb9fe3d2f0..5f8e8e36975 100644 --- a/cmd/bundle/debug/list_targets.go +++ b/cmd/bundle/debug/list_targets.go @@ -35,32 +35,37 @@ type listTargetsOutput struct { } // collectTargets returns the targets sorted by name. The engine of a target is -// its own bundle.engine if set, otherwise the top-level bundle.engine. -func collectTargets(targets map[string]*config.Target, rootEngine engine.EngineType) []targetInfo { +// its own bundle.engine if set, otherwise the top-level bundle.engine. The host +// and workspace ID come from the target's workspace merged over the top-level +// one, normalized the same way authentication normalizes them. +func collectTargets(cfg *config.Root) ([]targetInfo, error) { + targets := cfg.Targets + rootEngine := cfg.Bundle.Engine names := slices.Sorted(maps.Keys(targets)) result := make([]targetInfo, 0, len(names)) for _, name := range names { + w, err := cfg.TargetWorkspace(name) + if err != nil { + return nil, err + } + w.NormalizeHostURL() + info := targetInfo{ + Name: name, + Host: w.Host, + WorkspaceID: w.WorkspaceID, + } + t := targets[name] // YAML decoding can leave a nil entry in the map when a target is // declared with a null value. Skip rather than dereference and panic. if t == nil { - result = append(result, targetInfo{Name: name, Engine: terraformOnly(rootEngine)}) + info.Engine = terraformOnly(rootEngine) + result = append(result, info) continue } - info := targetInfo{ - Name: name, - Default: t.Default, - Mode: t.Mode, - } - if t.Workspace != nil { - // Normalize a copy so the host and workspace ID match what - // authentication uses, e.g. ?o= in a custom host becomes the ID. - w := *t.Workspace - w.NormalizeHostURL() - info.Host = w.Host - info.WorkspaceID = w.WorkspaceID - } + info.Default = t.Default + info.Mode = t.Mode targetEngine := rootEngine if t.Bundle != nil && t.Bundle.Engine != engine.EngineNotSet { targetEngine = t.Bundle.Engine @@ -68,7 +73,7 @@ func collectTargets(targets map[string]*config.Target, rootEngine engine.EngineT info.Engine = terraformOnly(targetEngine) result = append(result, info) } - return result + return result, nil } func terraformOnly(e engine.EngineType) engine.EngineType { @@ -103,7 +108,10 @@ func NewListTargetsCommand() *cobra.Command { return root.ErrAlreadyPrinted } - targets := collectTargets(b.Config.Targets, b.Config.Bundle.Engine) + targets, err := collectTargets(&b.Config) + if err != nil { + return err + } switch root.OutputType(cmd) { case flags.OutputText: From 145a7703c8844dc8d06d1ce1637481f924362ea6 Mon Sep 17 00:00:00 2001 From: "pavlo.kozlov" Date: Mon, 26 Oct 2026 13:03:47 +0000 Subject: [PATCH 3/4] Replace TargetWorkspace unit tests with an acceptance test The value cases were already covered by the list-targets acceptance tests. Replace the check against MergeTargetOverrides with an acceptance test that compares the workspace ID list-targets reports with the one authentication uses for the same target. Co-authored-by: Isaac --- .../list_targets/databricks.yml | 14 +++++ .../list_targets/out.test.toml | 2 + .../list_targets/output.txt | 16 +++++ .../strict_profile_match/list_targets/script | 7 +++ bundle/config/root_test.go | 63 ------------------- 5 files changed, 39 insertions(+), 63 deletions(-) create mode 100644 acceptance/bundle/strict_profile_match/list_targets/databricks.yml create mode 100644 acceptance/bundle/strict_profile_match/list_targets/out.test.toml create mode 100644 acceptance/bundle/strict_profile_match/list_targets/output.txt create mode 100644 acceptance/bundle/strict_profile_match/list_targets/script diff --git a/acceptance/bundle/strict_profile_match/list_targets/databricks.yml b/acceptance/bundle/strict_profile_match/list_targets/databricks.yml new file mode 100644 index 00000000000..2d395eca457 --- /dev/null +++ b/acceptance/bundle/strict_profile_match/list_targets/databricks.yml @@ -0,0 +1,14 @@ +bundle: + name: strict-profile-match + +include: + - scripts.yml + +workspace: + workspace_id: "$WORKSPACE_ID" + +targets: + inherited_workspace_id: + default: true + workspace: + host: $CUSTOM_HOST/?o=2222222222 diff --git a/acceptance/bundle/strict_profile_match/list_targets/out.test.toml b/acceptance/bundle/strict_profile_match/list_targets/out.test.toml new file mode 100644 index 00000000000..a927a5fbc06 --- /dev/null +++ b/acceptance/bundle/strict_profile_match/list_targets/out.test.toml @@ -0,0 +1,2 @@ +Cloud = false +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/strict_profile_match/list_targets/output.txt b/acceptance/bundle/strict_profile_match/list_targets/output.txt new file mode 100644 index 00000000000..03af2d917f0 --- /dev/null +++ b/acceptance/bundle/strict_profile_match/list_targets/output.txt @@ -0,0 +1,16 @@ + +=== list-targets reports the inherited workspace ID +>>> [CLI] bundle debug list-targets -o json +{ + "targets": [ + { + "name": "inherited_workspace_id", + "default": true, + "host": "[CUSTOM_HOST]", + "workspace_id": "[WORKSPACE_ID]" + } + ] +} +=== Authentication uses the same workspace ID +>>> errcode [CLI] bundle run -t inherited_workspace_id show_auth +profile=workspace_custom_host host=[CUSTOM_HOST] workspace_id=[WORKSPACE_ID] diff --git a/acceptance/bundle/strict_profile_match/list_targets/script b/acceptance/bundle/strict_profile_match/list_targets/script new file mode 100644 index 00000000000..0dc0164c6dd --- /dev/null +++ b/acceptance/bundle/strict_profile_match/list_targets/script @@ -0,0 +1,7 @@ +# The top-level workspace_id takes precedence over ?o= in the target's host, so +# list-targets must report the workspace ID that authentication uses. +title "list-targets reports the inherited workspace ID" +trace $CLI bundle debug list-targets -o json + +title "Authentication uses the same workspace ID" +trace errcode $CLI bundle run -t inherited_workspace_id show_auth diff --git a/bundle/config/root_test.go b/bundle/config/root_test.go index d654af12add..f735c44405e 100644 --- a/bundle/config/root_test.go +++ b/bundle/config/root_test.go @@ -252,66 +252,3 @@ func TestIsFullVariableOverrideDef(t *testing.T) { assert.Equal(t, tc.expected, isFullVariableOverrideDef(tc.value), "test case %d", i) } } - -const targetWorkspaceConfig = ` -bundle: - name: test -workspace: - host: https://custom.test - workspace_id: "5678" -targets: - inherited: - host_override: - workspace: - host: https://custom.test/?o=1234 - id_override: - workspace: - workspace_id: "9012" -` - -func TestRootTargetWorkspace(t *testing.T) { - root, diags := LoadFromBytes("databricks.yml", []byte(targetWorkspaceConfig)) - require.Empty(t, diags) - - for _, tc := range []struct { - target string - host string - workspaceID string - }{ - {"inherited", "https://custom.test", "5678"}, - {"host_override", "https://custom.test/?o=1234", "5678"}, - {"id_override", "https://custom.test", "9012"}, - } { - t.Run(tc.target, func(t *testing.T) { - w, err := root.TargetWorkspace(tc.target) - require.NoError(t, err) - assert.Equal(t, tc.host, w.Host) - assert.Equal(t, tc.workspaceID, w.WorkspaceID) - - merged, diags := LoadFromBytes("databricks.yml", []byte(targetWorkspaceConfig)) - require.Empty(t, diags) - require.NoError(t, merged.MergeTargetOverrides(tc.target)) - assert.Equal(t, merged.Workspace, w) - }) - } - - assert.Equal(t, "5678", root.Workspace.WorkspaceID) - assert.Len(t, root.Targets, 3) -} - -func TestRootTargetWorkspaceWithoutWorkspace(t *testing.T) { - root, diags := LoadFromBytes("databricks.yml", []byte("targets:\n dev:\n")) - require.Empty(t, diags) - - w, err := root.TargetWorkspace("dev") - require.NoError(t, err) - assert.Equal(t, Workspace{}, w) -} - -func TestRootTargetWorkspaceUnknownTarget(t *testing.T) { - root, diags := LoadFromBytes("databricks.yml", []byte("targets:\n dev:\n")) - require.Empty(t, diags) - - _, err := root.TargetWorkspace("prod") - assert.Error(t, err) -} From 2493002f0359a2e70d57cf7deb8df5ed1c829c4b Mon Sep 17 00:00:00 2001 From: "pavlo.kozlov" Date: Tue, 27 Oct 2026 12:17:13 +0000 Subject: [PATCH 4/4] Use typed config to merge workspace settings in list-targets Read the host and workspace ID from the typed top-level and target workspace settings instead of merging dynamic values, and drop Root.TargetWorkspace. An explicit empty value in the target can't be told apart from an unset one, so it no longer clears the top-level value. Co-authored-by: Isaac --- bundle/config/root.go | 22 ---------------- cmd/bundle/debug/list_targets.go | 44 ++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/bundle/config/root.go b/bundle/config/root.go index bbff6a7f04e..a64b742630e 100644 --- a/bundle/config/root.go +++ b/bundle/config/root.go @@ -326,28 +326,6 @@ func mergeField(rv, ov dyn.Value, name string) (dyn.Value, error) { return dyn.SetByPath(rv, path, out) } -// TargetWorkspace returns the workspace settings of the named target as -// [Root.MergeTargetOverrides] merges them, without modifying the root. -func (r *Root) TargetWorkspace(name string) (Workspace, error) { - target, err := dyn.GetByPath(r.value, dyn.NewPath(dyn.Key("targets"), dyn.Key(name))) - if err != nil { - return Workspace{}, err - } - merged, err := mergeField(r.value, target, "workspace") - if err != nil { - return Workspace{}, fmt.Errorf("failed to merge target=%s field=workspace: %w", name, err) - } - - var w Workspace - // Neither the root nor the target sets a workspace section. - v := merged.Get("workspace") - if !v.IsValid() { - return w, nil - } - err = convert.ToTyped(&w, v) - return w, err -} - func (r *Root) MergeTargetOverrides(name string) error { root := r.value target, err := dyn.GetByPath(root, dyn.NewPath(dyn.Key("targets"), dyn.Key(name))) diff --git a/cmd/bundle/debug/list_targets.go b/cmd/bundle/debug/list_targets.go index 5f8e8e36975..613442569c3 100644 --- a/cmd/bundle/debug/list_targets.go +++ b/cmd/bundle/debug/list_targets.go @@ -1,6 +1,7 @@ package debug import ( + "cmp" "encoding/json" "fmt" "maps" @@ -35,28 +36,22 @@ type listTargetsOutput struct { } // collectTargets returns the targets sorted by name. The engine of a target is -// its own bundle.engine if set, otherwise the top-level bundle.engine. The host -// and workspace ID come from the target's workspace merged over the top-level -// one, normalized the same way authentication normalizes them. -func collectTargets(cfg *config.Root) ([]targetInfo, error) { +// its own bundle.engine if set, otherwise the top-level bundle.engine. +func collectTargets(cfg *config.Root) []targetInfo { targets := cfg.Targets rootEngine := cfg.Bundle.Engine names := slices.Sorted(maps.Keys(targets)) result := make([]targetInfo, 0, len(names)) for _, name := range names { - w, err := cfg.TargetWorkspace(name) - if err != nil { - return nil, err - } - w.NormalizeHostURL() + t := targets[name] + host, workspaceID := targetHostAndWorkspaceID(&cfg.Workspace, t) info := targetInfo{ Name: name, - Host: w.Host, - WorkspaceID: w.WorkspaceID, + Host: host, + WorkspaceID: workspaceID, } - t := targets[name] // YAML decoding can leave a nil entry in the map when a target is // declared with a null value. Skip rather than dereference and panic. if t == nil { @@ -73,7 +68,25 @@ func collectTargets(cfg *config.Root) ([]targetInfo, error) { info.Engine = terraformOnly(targetEngine) result = append(result, info) } - return result, nil + return result +} + +// targetHostAndWorkspaceID returns the host and workspace ID of target t, which +// may be nil, normalized the same way authentication normalizes them. As in +// MergeTargetOverrides, the target's values take precedence over the top-level +// ones, except that an explicit empty value cannot be told apart from an unset +// one, so it does not clear the top-level value. +func targetHostAndWorkspaceID(rootWorkspace *config.Workspace, t *config.Target) (string, string) { + w := config.Workspace{ + Host: rootWorkspace.Host, + WorkspaceID: rootWorkspace.WorkspaceID, + } + if t != nil && t.Workspace != nil { + w.Host = cmp.Or(t.Workspace.Host, w.Host) + w.WorkspaceID = cmp.Or(t.Workspace.WorkspaceID, w.WorkspaceID) + } + w.NormalizeHostURL() + return w.Host, w.WorkspaceID } func terraformOnly(e engine.EngineType) engine.EngineType { @@ -108,10 +121,7 @@ func NewListTargetsCommand() *cobra.Command { return root.ErrAlreadyPrinted } - targets, err := collectTargets(&b.Config) - if err != nil { - return err - } + targets := collectTargets(&b.Config) switch root.OutputType(cmd) { case flags.OutputText: