Skip to content

Commit 942e91c

Browse files
authored
Propagate auth env to experimental.python subprocess (#5074)
## Why When `bundle deploy` / `bundle validate` runs with a profile set (via `--profile`, `DATABRICKS_CONFIG_PROFILE`, or `workspace.profile` in `databricks.yml`) and the bundle uses `experimental.python`, the Python subprocess does not inherit `DATABRICKS_CONFIG_PROFILE`. The Databricks SDK inside Python then re-invokes the CLI via `databricks auth token --host <host>` without a profile, and fails with a multi-profile ambiguity error when `~/.databrickscfg` has several profiles sharing the same host. This is the remaining scenario on #4649. The Terraform path was fixed in #4624; the `experimental.python` path was missed because the Python mutator spawned its subprocess via `process.Background` without any auth env setup. ## Changes **Before:** `bundle/config/mutator/python/python_mutator.go` called `process.Background` with no environment propagation. The Python subprocess only saw whatever env vars the user happened to export; `DATABRICKS_CONFIG_PROFILE` from `--profile` or `databricks.yml` was silently dropped. **Now:** The mutator calls `b.AuthEnv(ctx)` (same call the Terraform path uses) and passes the resulting map through `process.WithEnvs(...)`. The Python SDK and any subprocesses it spawns now see the same auth configuration as the CLI itself. ## Test plan - [x] New unit test `TestPythonMutator_propagatesAuthEnv` asserts that `DATABRICKS_CONFIG_PROFILE` is present in the Python subprocess env when `workspace.profile` is set. - [x] Existing python mutator tests pass (`go test ./bundle/config/mutator/python/`). - [x] Full bundle test suite passes (`go test ./bundle/...`). - [x] `make checks` and `make lint` clean.
1 parent 4a58c0c commit 942e91c

8 files changed

Lines changed: 80 additions & 1 deletion

File tree

NEXT_CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
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).
88

99
### Bundles
1010

11+
* 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.
1112
* Fixed `--force-pull` on `bundle summary` and `bundle open` so the flag bypasses the local state cache and reads state from the workspace.
1213

1314
### 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)

0 commit comments

Comments
 (0)