Skip to content

Commit e70a769

Browse files
authored
Merge branch 'main' into test/list-targets-nil-check
2 parents e8e7b52 + e4a77e1 commit e70a769

21 files changed

Lines changed: 528 additions & 148 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# NEXT CHANGELOG
22

3-
## Release v0.300.0
3+
## Release v0.299.2
44

55
### CLI
66

77
* `databricks auth describe` now reports where U2M (`databricks-cli`) tokens are stored: `plaintext` (`~/.databricks/token-cache.json`) or `secure` (OS keyring), and the source of the choice (env var, config setting, or default).
8+
* Marked the default profile in the interactive pickers shown by `databricks auth switch`, `databricks auth logout`, `databricks auth token`, and `databricks auth login`, and moved it to the top of the list. `databricks auth login` and `databricks auth logout` now offer the same selectors as `databricks auth token` and `databricks auth switch` respectively.
89

910
### Bundles
1011

12+
* Propagate authentication environment (including `DATABRICKS_CONFIG_PROFILE`) to the `experimental.python` subprocess so bundle validate/deploy no longer fails with a multi-profile host ambiguity error when several profiles in `~/.databrickscfg` share the same host.
1113
* Fixed `--force-pull` on `bundle summary` and `bundle open` so the flag bypasses the local state cache and reads state from the workspace.
1214

1315
### Dependency updates
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[my-profile]
2+
host = $DATABRICKS_HOST
3+
token = $DATABRICKS_TOKEN
4+
5+
[other-profile]
6+
host = $DATABRICKS_HOST
7+
token = other-token
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
bundle:
2+
name: my_project
3+
4+
sync: {paths: []} # don't need to copy files
5+
6+
python:
7+
mutators:
8+
- "mutators:capture_profile_env"
9+
10+
workspace:
11+
profile: my-profile
12+
13+
resources:
14+
jobs:
15+
my_job:
16+
name: "Job"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from databricks.bundles.jobs import Job
2+
from databricks.bundles.core import job_mutator, Bundle
3+
import os
4+
5+
6+
@job_mutator
7+
def capture_profile_env(bundle: Bundle, job: Job) -> Job:
8+
# The CLI must propagate DATABRICKS_CONFIG_PROFILE to the python subprocess
9+
# so the Databricks SDK can disambiguate when multiple profiles share a host.
10+
value = os.getenv("DATABRICKS_CONFIG_PROFILE", "<unset>")
11+
with open("captured_env.txt", "w") as f:
12+
f.write(value)
13+
return job

acceptance/bundle/python/propagates-auth-env/out.test.toml

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
>>> uv run [UV_ARGS] -q [CLI] bundle summary -o json
3+
{
4+
"profile": "my-profile"
5+
}
6+
7+
>>> cat captured_env.txt
8+
my-profile
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
# Two workspace profiles share the same host so picking one is meaningful.
3+
envsubst < .databrickscfg > out && mv out .databrickscfg
4+
export DATABRICKS_CONFIG_FILE=.databrickscfg
5+
unset DATABRICKS_HOST
6+
unset DATABRICKS_TOKEN
7+
unset DATABRICKS_CONFIG_PROFILE
8+
9+
trace uv run $UV_ARGS -q $CLI bundle summary -o json | jq '{profile: .workspace.profile}'
10+
11+
# The python mutator captures DATABRICKS_CONFIG_PROFILE from its subprocess env.
12+
# Without the fix, the CLI does not propagate the bundle's resolved profile,
13+
# so the SDK inside python re-invokes the CLI without a profile and fails on
14+
# multi-profile ambiguity.
15+
trace cat captured_env.txt
16+
echo ""
17+
18+
rm -fr .databricks __pycache__ captured_env.txt

bundle/config/mutator/python/python_mutator.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ type runPythonMutatorOpts struct {
104104
bundleRootPath string
105105
pythonPath string
106106
loadLocations bool
107+
authEnv map[string]string
107108
}
108109

109110
// getOpts adapts deprecated PyDABs and upcoming Python configuration
@@ -217,6 +218,15 @@ func (m *pythonMutator) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagno
217218
return diag.Errorf("Running Python code is not allowed when DATABRICKS_BUNDLE_RESTRICTED_CODE_EXECUTION is set")
218219
}
219220

221+
// Propagate auth env so the Databricks SDK in the Python subprocess uses the
222+
// same credentials as the CLI. In particular this carries DATABRICKS_CONFIG_PROFILE,
223+
// which lets the CLI disambiguate profiles sharing the same host when the SDK
224+
// re-invokes `databricks auth token --host <host>`.
225+
authEnv, err := b.AuthEnv(ctx)
226+
if err != nil {
227+
return diag.FromErr(err)
228+
}
229+
220230
// mutateDiags is used because Mutate returns 'error' instead of 'diag.Diagnostics'
221231
var mutateDiags diag.Diagnostics
222232
var result applyPythonOutputResult
@@ -238,6 +248,7 @@ func (m *pythonMutator) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagno
238248
bundleRootPath: b.BundleRootPath,
239249
pythonPath: pythonPath,
240250
loadLocations: opts.loadLocations,
251+
authEnv: authEnv,
241252
})
242253
mutateDiags = diags
243254
if diags.HasError() {
@@ -364,6 +375,7 @@ func (m *pythonMutator) runPythonMutator(ctx context.Context, root dyn.Value, op
364375
process.WithDir(opts.bundleRootPath),
365376
process.WithStderrWriter(stderrWriter),
366377
process.WithStdoutWriter(stdoutWriter),
378+
process.WithEnvs(opts.authEnv),
367379
)
368380
if processErr != nil {
369381
logger.Debugf(ctx, "python mutator process failed: %s", processErr)

cmd/auth/login.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,43 @@ a new profile is created.
180180
}
181181
}
182182

183+
// When interactive and nothing was specified, show a picker that lets
184+
// the user re-login to an existing profile, create a new one, or enter
185+
// a host URL. With no profiles configured the picker still shows the
186+
// two action entries so the user can choose between web-based discovery
187+
// (Create a new profile) and a manual host URL.
188+
if profileName == "" && authArguments.Host == "" && len(args) == 0 && cmdio.IsPromptSupported(ctx) {
189+
allProfiles, err := profile.DefaultProfiler.LoadProfiles(ctx, profile.MatchAllProfiles)
190+
if err != nil && !errors.Is(err, profile.ErrNoConfiguration) {
191+
return err
192+
}
193+
label := "Select a profile"
194+
if len(allProfiles) == 0 {
195+
label = "How would you like to log in?"
196+
}
197+
currentDefault, _ := databrickscfg.GetDefaultProfile(ctx, env.Get(ctx, "DATABRICKS_CONFIG_FILE"))
198+
result, selected, err := pickAuthProfile(ctx, allProfiles, profilePickerOptions{
199+
Label: label,
200+
Default: currentDefault,
201+
IncludeExtras: true,
202+
})
203+
if err != nil {
204+
return err
205+
}
206+
switch result {
207+
case profilePickerProfile:
208+
profileName = selected
209+
case profilePickerEnterHost:
210+
host, err := promptForHost(ctx)
211+
if err != nil {
212+
return err
213+
}
214+
authArguments.Host = host
215+
case profilePickerCreateNew:
216+
// Fall through to the profile name prompt below.
217+
}
218+
}
219+
183220
// If the user has not specified a profile name, prompt for one.
184221
if profileName == "" {
185222
var err error

cmd/auth/logout.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,19 @@ to specify it explicitly.
119119
if err != nil {
120120
return err
121121
}
122-
selected, err := profile.SelectProfile(ctx, profile.SelectConfig{
123-
Label: "Select a profile to log out of",
124-
Profiles: allProfiles,
125-
StartInSearchMode: len(allProfiles) > 5,
126-
ActiveTemplate: `▸ {{.PaddedName | bold}}{{if .AccountID}} (account: {{.AccountID}}){{else}} ({{.Host}}){{end}}`,
127-
InactiveTemplate: ` {{.PaddedName}}{{if .AccountID}} (account: {{.AccountID | faint}}){{else}} ({{.Host | faint}}){{end}}`,
128-
SelectedTemplate: `{{ "Selected profile" | faint }}: {{ .Name | bold }}`,
122+
currentDefault, _ := databrickscfg.GetDefaultProfile(ctx, env.Get(ctx, "DATABRICKS_CONFIG_FILE"))
123+
result, selected, err := pickAuthProfile(ctx, allProfiles, profilePickerOptions{
124+
Label: "Select a profile to log out of",
125+
SelectedNoun: "Selected profile",
126+
Default: currentDefault,
129127
})
130128
if err != nil {
131129
return err
132130
}
131+
// Without IncludeExtras, the picker only returns profile selections.
132+
if result != profilePickerProfile {
133+
return fmt.Errorf("unexpected picker result: %v", result)
134+
}
133135
profileName = selected
134136
}
135137

0 commit comments

Comments
 (0)