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/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/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/cmd/bundle/debug/list_targets.go b/cmd/bundle/debug/list_targets.go index ebd038da11d..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" @@ -20,10 +21,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"` @@ -35,26 +37,30 @@ 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 { +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 { t := targets[name] + host, workspaceID := targetHostAndWorkspaceID(&cfg.Workspace, t) + info := targetInfo{ + Name: name, + Host: host, + WorkspaceID: workspaceID, + } + // 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 { - info.Host = t.Workspace.Host - } + info.Default = t.Default + info.Mode = t.Mode targetEngine := rootEngine if t.Bundle != nil && t.Bundle.Engine != engine.EngineNotSet { targetEngine = t.Bundle.Engine @@ -65,6 +71,24 @@ func collectTargets(targets map[string]*config.Target, rootEngine engine.EngineT 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 { if e == engine.EngineTerraform { return e @@ -73,7 +97,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", @@ -97,7 +121,7 @@ func NewListTargetsCommand() *cobra.Command { return root.ErrAlreadyPrinted } - targets := collectTargets(b.Config.Targets, b.Config.Bundle.Engine) + targets := collectTargets(&b.Config) switch root.OutputType(cmd) { case flags.OutputText: @@ -112,6 +136,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)) }