Skip to content

[VPEX][2/8] Add local-env compute-target resolution - #5824

Merged
rugpanov merged 9 commits into
mainfrom
dbconnect/02-target
Jul 7, 2026
Merged

[VPEX][2/8] Add local-env compute-target resolution#5824
rugpanov merged 9 commits into
mainfrom
dbconnect/02-target

Conversation

@rugpanov

@rugpanovrugpanov commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Why

  • local-env must turn the user's compute selection into a single environment key before it can fetch anything, and the selection can come from several places with a defined precedence.
  • Isolating resolution behind a narrow seam keeps it testable without a live workspace and keeps SDK details out of the engine.

What

  • target.goResolveTarget with ordered precedence --cluster--serverless--job → bundle target, producing a TargetInfo + env key.
  • Compute lookups go through the narrow ComputeClient interface (stubbable in tests).
  • ValidateTargetFlags rejects more than one target flag; ResolveTarget runs it up front so a non-Cobra caller can't silently resolve the wrong target.
  • Classic-compute jobs read the Spark version from the documented first return of GetJobSparkVersion (not the recorded-version third return).

Testing strategy

  • Unit tests against a stub ComputeClient covering each precedence branch, the mutually-exclusive-flags error, and the job classic-compute contract (target_test.go).
  • Gates: go build, go test, golangci-lint, deadcode, gofmt — all green.
  • Reviewed with codex to a clean pass.

About this stack

This is one of a series of small, stacked PRs that together add the databricks local-env python sync command — it provisions a local Python environment (Python version, databricks-connect pin, and dependency constraints) matched to a selected Databricks compute target. The work was split from one large branch into single-concern layers so each is independently reviewable; the command is kept hidden until the final PR so nothing is user-visible mid-stack.

Review bottom-up. Each PR targets the previous one as its base branch, so its diff shows only that layer.

#PRWhat
1#5823foundation: result types + env-key mapping
2#5824 ← you are herecompute-target resolution
3#5826constraint fetch + offline cache
4#5827formatting-preserving pyproject.toml merge
5#5828six-phase pipeline + detection + package-manager interface
6#5832uv backend + CLI command (registered hidden)
7#5833acceptance tests
8#5835unveil (unhide + help + changelog)

This pull request and its description were written by Isaac.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: a056bb6

Run: 28863001270

Env🔄​flaky💚​RECOVERED🙈​SKIP✅​pass🙈​skipTime
💚​aws linux4423010644:57
💚​aws windows4423210625:59
💚​aws-ucws linux443149827:00
💚​aws-ucws windows443169806:18
🔄​azure linux22423010637:26
🔄​azure windows31423210617:48
💚​azure-ucws linux443169798:41
🔄​azure-ucws windows2243189778:29
💚​gcp linux4422910654:52
💚​gcp windows4423110636:13
8 interesting tests: 4 SKIP, 3 flaky, 1 RECOVERED
Test Nameaws linuxaws windowsaws-ucws linuxaws-ucws windowsazure linuxazure windowsazure-ucws linuxazure-ucws windowsgcp linuxgcp windows
💚​TestAccept💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R
🙈​TestAccept/bundle/invariant/no_drift🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/ssh/connection🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🔄​TestFetchRepositoryInfoAPI_FromRepo💚​R💚​R💚​R💚​R💚​R🔄​f💚​R💚​R💚​R💚​R
🔄​TestFetchRepositoryInfoAPI_FromRepo/root💚​R💚​R💚​R💚​R🔄​f🔄​f💚​R🔄​f💚​R💚​R
🔄​TestFetchRepositoryInfoAPI_FromRepo/subdir💚​R💚​R💚​R💚​R🔄​f🔄​f💚​R🔄​f💚​R💚​R
Top 10 slowest tests (at least 2 minutes):
durationenvtestname
5:13gcp windowsTestAccept
5:00aws windowsTestAccept
5:00azure-ucws windowsTestAccept
5:00aws-ucws windowsTestAccept
4:55azure windowsTestAccept
2:54aws linuxTestAccept
2:54aws-ucws linuxTestAccept
2:54gcp linuxTestAccept
2:54azure linuxTestAccept
2:49azure-ucws linuxTestAccept

First of a stacked series adding `databricks local-env python sync`, which
provisions a local Python environment matched to a Databricks compute
target. The feature lands across small, single-concern PRs; each layer is
independently reviewable and adds no user-facing surface until the final PR
wires the command in.
This PR is the foundation the rest of the stack builds on:
- result.go: the result types and the --json / E_* error contract that
every phase reports through (Result, PipelineError, ErrorCode, PhaseName,
PhaseStatus, Mode, TargetInfo, ResolvedInfo, Plan, Warning), plus the
command-path constants (local-env / python / sync) defined once.
- envkey.go: mapping a compute target to an environment key and parsing the
Python minor from a requires-python specifier.
Nothing imports this package yet, so the CLI is unchanged. The unexported
filesystem/artifact constants and the canonical phase-order slice live with
the pipeline that consumes them (a later PR in the stack).
Co-authored-by: Isaac
@rugpanov
rugpanovforce-pushed the dbconnect/01-engine branch from 865a2cd to 22f99d9CompareJuly 3, 2026 13:20
@rugpanov
rugpanovforce-pushed the dbconnect/02-target branch from 5fdb8b6 to 2092138CompareJuly 3, 2026 13:20
@rugpanovrugpanov changed the title Add dbconnect compute-target resolution[VPEX][2/8] Add local-env compute-target resolutionJul 3, 2026
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 13:21 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 13:21 — with GitHub Actions Inactive
…pecifiers
Review of the foundation layer flagged that PythonMinorFromRequires took the
first MAJOR.MINOR in the string via first-match regex. For a multi-clause
requires-python where the exclusive upper bound comes first — e.g.
"<3.13,>=3.10" — it returned 3.13, the version the "<3.13" clause forbids,
because PEP 440 clause order is arbitrary. The result feeds
PM.EnsurePython, so the tool could target a Python the constraint excludes.
Prefer a lower-bound / pinning clause (>=, >, ==, ~=, ===) and only fall
back to the first version when none is present. Adds multi-clause test
coverage; the prior tests exercised only single-bound specifiers.
Co-authored-by: Isaac
@rugpanov
rugpanovforce-pushed the dbconnect/02-target branch from 2092138 to 22d1137CompareJuly 3, 2026 15:26
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 15:27 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 15:27 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 15:31 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 15:31 — with GitHub Actions Inactive
…dden version
Round-2 review of the foundation layer noted that when a requires-python has
no lower-bound/pin clause at all (only upper-bound or exclusion, e.g.
"<3.13,!=3.12"), PythonMinorFromRequires fell back to the first number and
returned 3.13 — a version the specifier forbids. Such a spec has no floor to
install from, so it now errors rather than guessing. A bare "3.12" (no
operator) is still accepted as a valid floor.
Co-authored-by: Isaac
@rugpanov
rugpanovforce-pushed the dbconnect/02-target branch from 8d309ae to d9b9c50CompareJuly 3, 2026 18:15
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 18:15 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 18:15 — with GitHub Actions Inactive
…ower bound
Round-3 review found two edge cases in the regex-based PythonMinorFromRequires:
with multiple lower bounds (">=3.8,>=3.11") it returned the first (3.8) rather
than the effective floor (3.11), and a bare floor alongside an exclusion
("!=3.11,3.12") was wrongly rejected as having no floor.
Replaced the layered regexes with a small clause parser: split on commas,
classify each clause by operator (>=,>,==,~=,=== and bare = floor; <,<=,!=
never a floor), and return the highest floor. A spec with no floor clause
("<3.13", "!=3.12") still errors. Covers multi-lower-bound, bare-floor +
exclusion, ordering, and whitespace.
Co-authored-by: Isaac
@rugpanov
rugpanovforce-pushed the dbconnect/02-target branch from d9b9c50 to 77ac2c8CompareJuly 3, 2026 18:27
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 18:27 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 18:27 — with GitHub Actions Inactive
Round-4 review noted PythonMinorFromRequires returned 3.10 for ">3.10", but
PEP 440's ">" excludes the entire given release series (neither 3.10 nor any
3.10.x satisfies ">3.10"), so the lowest installable minor is 3.11. The clause
parser now bumps the minor by one for a strict ">" bound.
Co-authored-by: Isaac
@rugpanov
rugpanovforce-pushed the dbconnect/02-target branch from 77ac2c8 to 436dabfCompareJuly 3, 2026 19:14
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 3, 2026 19:14 — with GitHub Actions Inactive

@anton-107anton-107 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Clean precedence resolution behind a stubbable seam, and I like that ResolveTarget runs ValidateTargetFlags up front so a non-Cobra caller can't silently resolve the wrong target. Tests cover each branch well. One non-blocking design suggestion inline — feel free to take it or leave it in a follow-up.

GetClusterSparkVersion(ctx context.Context, clusterID string) (string, error)
// GetJobSparkVersion returns either a Spark version (isServerless=false) or a
// serverless marker (isServerless=true) for a job, plus a recorded version string.
GetJobSparkVersion(ctx context.Context, jobID string) (sparkVersion string, isServerless bool, version string, err error)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking design nit: the positional contract here is subtle — classic compute reads the 1st return (sparkVersion), serverless reads the 3rd (version), and the 2nd (isServerless) selects between them. It's documented and the test guards it, but (string, bool, string, error) puts the meaning in argument position rather than field names, so it's easy for a future implementer to wire the wrong string.

Consider returning a small typed struct instead, e.g.

typeJobComputestruct {
SparkVersionstring// classic computeServerlessboolServerlessVersionstring// when Serverless
}

That makes the caller's if isServerless { ... version ... } else { ... sparkVersion ... } self-describing and removes the need for the "not the recorded-version third return" comment. Fine to defer to a follow-up — not blocking this PR.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed the positional (string, bool, string, error) is easy to miswire. Taking your suggestion as a follow-up rather than in this PR to keep the stack's diffs minimal — tracked. Thanks for the approve.

…json arrays
Two items from review of the foundation layer:
- PythonMinorFromRequires bumped the minor for any strict ">" bound, but a
patch-qualified bound like ">3.10.5" is still satisfied by 3.10.6, so the
floor should stay 3.10 (only a bare ">3.10" excludes the whole 3.10.x
series). clauseRe now captures the patch component and the minor is bumped
only when it is absent. Adds >3.10.5 / >=3.10.2 test cases.
- Result.Phases and Result.Warnings are non-omitempty slices, so a bare
Result{} would marshal them as "null" rather than "[]", an ambiguity in the
--json contract. Added NewResult() which seeds both to empty slices, a doc
note on the invariant, and a test asserting the JSON emits [] not null. The
pipeline (later in the stack) constructs its Result through this.
Co-authored-by: Isaac
Second in the stacked local-env series (builds on the foundation types).
target.go resolves a compute target to a TargetInfo (and its environment
key) using ordered precedence: --cluster flag → --serverless flag → --job
flag → bundle target. Compute lookups go through the narrow ComputeClient
seam so the resolver is unit-tested against a stub with no SDK dependency.
ValidateTargetFlags guards the library path against more than one target
flag being set.
The classic-compute job branch reads the Spark version from the first
return of GetJobSparkVersion, per that method's documented contract, rather
than the recorded-version third return.
Depends on the foundation PR for NewError, the E_RESOLVE / E_NO_TARGET
codes, TargetInfo, and the EnvKeyFor* helpers. Still dormant.
Co-authored-by: Isaac
Review of the target layer noted that ResolveTarget accepted incompatible
flags on the library path: called directly with e.g.
TargetFlags{Cluster: "c", Serverless: "v4"} it silently took the first
precedence branch and ignored the rest, resolving a different target than
requested. Cobra and the cmd layer already reject this, but ResolveTarget is
exported and ValidateTargetFlags exists specifically to guard callers that
bypass Cobra, so the resolver now runs that check first and returns
E_RESOLVE on conflicting flags.
Co-authored-by: Isaac
@rugpanov
rugpanovforce-pushed the dbconnect/02-target branch from 436dabf to 42a7988CompareJuly 6, 2026 11:47
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 6, 2026 11:47 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 6, 2026 11:47 — with GitHub Actions Inactive

@anton-107anton-107 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after the rebase onto the updated base — target.go is unchanged in substance. The positional-return nit (#5) is being taken as a follow-up, which is fine since it was non-blocking. LGTM.

Base automatically changed from dbconnect/01-engine to mainJuly 7, 2026 10:10
@rugpanov
rugpanov enabled auto-merge July 7, 2026 11:33
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 7, 2026 11:33 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 7, 2026 11:33 — with GitHub Actions Inactive
@rugpanov
rugpanov added this pull request to the merge queueJul 7, 2026
Merged via the queue into main with commit 932fa0bJul 7, 2026
23 checks passed
@rugpanov
rugpanov deleted the dbconnect/02-target branch July 7, 2026 12:24
@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 932fa0b

Run: 28865893198

Env❌​FAIL🟨​KNOWN🔄​flaky💚​RECOVERED🙈​SKIP✅​pass🙈​skipTime
🔄​aws linux33452799339:38
🔄​aws windows534482100650:32
🔄​aws-ucws linux352986808110:22
🔄​aws-ucws windows462926826126:00
🟨​azure linux231452199446:18
🔄​azure windows644474100756:32
❌​azure-ucws linux21942884839133:52
❌​azure-ucws windows1562821857123:07
🔄​gcp linux64450899947:45
🔄​gcp windows344468101253:07
42 interesting tests: 17 FAIL, 17 flaky, 6 KNOWN, 2 SKIP
Test Nameaws linuxaws windowsaws-ucws linuxaws-ucws windowsazure linuxazure windowsazure-ucws linuxazure-ucws windowsgcp linuxgcp windows
🟨​TestAccept🔄​f🔄​f🔄​f💚​R💚​R💚​R🔄​f🟨​K💚​R💚​R
❌​TestAccept/bundle/apps/compute_size✅​p✅​p✅​p✅​p✅​p✅​p✅​p❌​F✅​p✅​p
❌​TestAccept/bundle/apps/compute_size/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p✅​p🔄​f🔄​f🔄​f❌​F✅​p✅​p
❌​TestAccept/bundle/apps/compute_size/DATABRICKS_BUNDLE_ENGINE=terraform✅​p✅​p✅​p✅​p🔄​f🔄​f🔄​f❌​F✅​p✅​p
🔄​TestAccept/bundle/deployment/bind/alert✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p
🔄​TestAccept/bundle/deployment/bind/alert/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p
🔄​TestAccept/bundle/deployment/bind/dashboard✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p
🔄​TestAccept/bundle/deployment/bind/dashboard/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p
❌​TestAccept/bundle/generate/pipeline_and_deploy✅​p✅​p✅​p🔄​f✅​p🔄​f✅​p❌​F✅​p✅​p
❌​TestAccept/bundle/generate/pipeline_and_deploy/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p✅​p✅​p✅​p✅​p❌​F✅​p✅​p
🔄​TestAccept/bundle/generate/pipeline_and_deploy/DATABRICKS_BUNDLE_ENGINE=terraform✅​p✅​p✅​p🔄​f✅​p🔄​f✅​p✅​p✅​p✅​p
❌​TestAccept/bundle/generate/python_job_and_deploy✅​p🔄​f✅​p🔄​f✅​p✅​p✅​p❌​F✅​p✅​p
❌​TestAccept/bundle/generate/python_job_and_deploy/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p✅​p✅​p✅​p✅​p❌​F✅​p✅​p
🔄​TestAccept/bundle/generate/python_job_and_deploy/DATABRICKS_BUNDLE_ENGINE=terraform✅​p🔄​f✅​p🔄​f✅​p✅​p✅​p✅​p✅​p✅​p
❌​TestAccept/bundle/invariant/continue_293🙈​s🙈​s✅​p✅​p🙈​s🙈​s✅​p❌​F🙈​s🙈​s
❌​TestAccept/bundle/invariant/continue_293/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=synced_database_table.yml.tmpl✅​p✅​p✅​p❌​F
❌​TestAccept/bundle/invariant/migrate🙈​s🙈​s✅​p✅​p🙈​s🙈​s🔄​f❌​F🙈​s🙈​s
❌​TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=database_catalog.yml.tmpl✅​p✅​p🔄​f❌​F
🟨​TestAccept/bundle/invariant/no_drift🙈​S🙈​S💚​R💚​R🙈​S🙈​S🟨​K🟨​K🙈​S🙈​S
❌​TestAccept/bundle/invariant/no_drift/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=database_catalog.yml.tmpl/READPLAN=1✅​p✅​p✅​p❌​F
❌​TestAccept/bundle/invariant/no_drift/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=synced_database_table.yml.tmpl/READPLAN=✅​p✅​p❌​F✅​p
❌​TestAccept/bundle/invariant/no_drift/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=synced_database_table.yml.tmpl/READPLAN=1✅​p✅​p❌​F✅​p
🔄​TestAccept/bundle/resources/apps/lifecycle-started✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p
🔄​TestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p
🔄​TestAccept/bundle/resources/dashboards/change-name🔄​f✅​p✅​p✅​p✅​p🔄​f✅​p✅​p🔄​f✅​p
🔄​TestAccept/bundle/resources/dashboards/change-name/DATABRICKS_BUNDLE_ENGINE=terraform🔄​f✅​p✅​p✅​p✅​p🔄​f✅​p✅​p🔄​f✅​p
🔄​TestAccept/bundle/resources/postgres_synced_tables/recreate🙈​s🙈​s🔄​f✅​p🙈​s🙈​s🙈​s🙈​s🙈​s🙈​s
🔄​TestAccept/bundle/resources/postgres_synced_tables/recreate/DATABRICKS_BUNDLE_ENGINE=terraform🔄​f✅​p
❌​TestAccept/bundle/resources/synced_database_tables/basic🙈​s🙈​s✅​p✅​p🙈​s🙈​s🔄​f❌​F🙈​s🙈​s
❌​TestAccept/bundle/resources/synced_database_tables/basic/DATABRICKS_BUNDLE_ENGINE=terraform✅​p✅​p🔄​f❌​F
🟨​TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name🙈​S🙈​S💚​R💚​R🙈​S🙈​S💚​R🟨​K🙈​S🙈​S
❌​TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p❌​F
🙈​TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🔄​TestAccept/bundle/templates/default-python/integration_classic/DATABRICKS_BUNDLE_ENGINE=direct/UV_PYTHON=3.13✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f
🔄​TestAccept/bundle/templates/default-python/integration_classic/DATABRICKS_BUNDLE_ENGINE=terraform/UV_PYTHON=3.13✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f
🙈​TestAccept/ssh/connection🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🔄​TestSecretsPutSecretBytesValue✅​p🔄​f🙈​s🙈​s✅​p✅​p✅​p✅​p✅​p✅​p
🔄​TestSecretsPutSecretStringValue✅​p🔄​f🙈​s🙈​s✅​p✅​p✅​p✅​p✅​p✅​p
🔄​TestSyncIncrementalSyncPythonNotebookToFile✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f
🟨​TestFetchRepositoryInfoAPI_FromRepo💚​R💚​R💚​R💚​R🟨​K💚​R💚​R🟨​K💚​R💚​R
🟨​TestFetchRepositoryInfoAPI_FromRepo/root💚​R💚​R💚​R💚​R🟨​K💚​R💚​R🟨​K💚​R💚​R
🟨​TestFetchRepositoryInfoAPI_FromRepo/subdir💚​R💚​R💚​R💚​R🔄​f💚​R💚​R🟨​K💚​R💚​R
Top 50 slowest tests (at least 2 minutes):
durationenvtestname
18:16aws-ucws linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
12:12gcp linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
11:47gcp windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
9:16gcp linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
9:08aws-ucws windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
8:48azure-ucws windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
8:45aws-ucws windowsTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
8:41aws-ucws linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
8:29aws-ucws linuxTestAccept/bundle/resources/clusters/resize-terminated-fallback/DATABRICKS_BUNDLE_ENGINE=direct
8:12azure-ucws linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
8:10azure linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
8:10aws windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:58azure windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:54aws linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:36gcp windowsTestAccept/bundle/resources/clusters/deploy/local_ssd_count/DATABRICKS_BUNDLE_ENGINE=direct
7:25azure-ucws windowsTestAccept/bundle/invariant/no_drift/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=cluster.yml.tmpl/READPLAN=
7:23azure-ucws linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:02aws-ucws windowsTestAccept/bundle/invariant/no_drift/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=cluster_apply_policy_default_values.yml.tmpl/READPLAN=
7:00azure-ucws windowsTestAccept/bundle/resources/clusters/deploy/data_security_mode/DATABRICKS_BUNDLE_ENGINE=direct
6:58gcp linuxTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
6:51azure-ucws linuxTestAccept/bundle/config-remote-sync/multiple_resources/DATABRICKS_BUNDLE_ENGINE=terraform
6:48gcp linuxTestAccept/bundle/resources/clusters/deploy/local_ssd_count/DATABRICKS_BUNDLE_ENGINE=direct
6:43aws-ucws windowsTestAccept/bundle/invariant/no_drift/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=cluster.yml.tmpl/READPLAN=1
6:42gcp windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
6:34aws linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
6:19azure linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
6:07aws-ucws windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
6:02aws windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
5:52azure-ucws windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
5:46azure-ucws linuxTestAccept/bundle/config-remote-sync/multiple_resources/DATABRICKS_BUNDLE_ENGINE=direct
5:33aws windowsTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
5:22aws-ucws windowsTestAccept/bundle/deploy/spark-jar-task/DATABRICKS_BUNDLE_ENGINE=terraform
5:21aws-ucws windowsTestAccept/bundle/deploy/spark-jar-task/DATABRICKS_BUNDLE_ENGINE=direct
5:06gcp windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
5:03aws linuxTestAccept/bundle/deploy/spark-jar-task/DATABRICKS_BUNDLE_ENGINE=direct
5:03azure windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
5:02aws-ucws linuxTestAccept/bundle/config-remote-sync/multiple_resources/DATABRICKS_BUNDLE_ENGINE=terraform
5:00aws windowsTestAccept/bundle/deploy/spark-jar-task/DATABRICKS_BUNDLE_ENGINE=terraform
4:59azure-ucws windowsTestAccept/bundle/resources/apps/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
4:54gcp windowsTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
4:53gcp windowsTestAccept/bundle/deploy/spark-jar-task/DATABRICKS_BUNDLE_ENGINE=direct
4:46gcp windowsTestAccept/bundle/resources/apps/lifecycle-started-omitted/DATABRICKS_BUNDLE_ENGINE=direct
4:36gcp windowsTestAccept/bundle/resources/apps/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
4:36gcp linuxTestAccept/bundle/resources/apps/lifecycle-started-omitted/DATABRICKS_BUNDLE_ENGINE=direct
4:35azure-ucws linuxTestAccept/bundle/deploy/spark-jar-task/DATABRICKS_BUNDLE_ENGINE=direct
4:34gcp linuxTestAccept/bundle/resources/apps/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
4:28azure windowsTestAccept/bundle/resources/apps/lifecycle-started-omitted/DATABRICKS_BUNDLE_ENGINE=direct
4:26azure-ucws windowsTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
4:23aws-ucws linuxTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
4:20gcp linuxTestAccept/bundle/deploy/spark-jar-task/DATABRICKS_BUNDLE_ENGINE=direct

ZaSkittles pushed a commit to ZaSkittles/cli that referenced this pull request Jul 7, 2026
databricks#5823)
## Why
- The `local-env` feature needs a shared vocabulary before any behavior
can be built: the result shape, the error taxonomy, and how a compute
target maps to an environment key.
- Landing these contract types first lets every later layer (resolve /
fetch / merge / pipeline / command) depend on stable, reviewed
definitions.
- Kept deliberately minimal and dependency-free so it reviews on its own
and stays `unused`/`deadcode`-clean with no consumers yet.
## What
- **`result.go`** — the `--json` / `E_*` output contract: `Result`,
`PipelineError`, `ErrorCode`, `PhaseName`, `PhaseStatus`, `Mode`,
`TargetInfo`, `ResolvedInfo`, `Plan`, `Warning`; plus the command-path
constants (`local-env` / `python` / `sync`) defined in one place.
- **`envkey.go`** — `EnvKeyForServerless` / `EnvKeyForSparkVersion` /
`NormalizeServerless`, and `PythonMinorFromRequires` (clause-aware:
returns the effective highest lower bound of a `requires-python`).
- No wiring into `cmd/`, so the CLI is unchanged. Filesystem/artifact
constants and the phase-order slice deliberately live with their
consumer (PR 5).
## Testing strategy
- Unit tests for the error/type contract (`result_test.go`) and env-key
mapping incl. multi-clause / strict-`>` / no-floor `requires-python`
cases (`envkey_test.go`).
- Gates: `go build`, `go test`, `golangci-lint`, `deadcode`, `gofmt` —
all green.
- Reviewed with codex across several rounds to convergence (all findings
fixed or explicitly rejected as speculative).
---
## About this stack
This is one of a series of small, stacked PRs that together add the
`databricks local-env python sync` command — it provisions a local
Python environment (Python version, `databricks-connect` pin, and
dependency constraints) matched to a selected Databricks compute target.
The work was split from one large branch into single-concern layers so
each is independently reviewable; the command is kept hidden until the
final PR so nothing is user-visible mid-stack.
**Review bottom-up.** Each PR targets the previous one as its base
branch, so its diff shows only that layer.
| # | PR | What |
|---|----|------|
| 1 | **databricks#5823 ← you are here** | foundation: result types + env-key
mapping |
| 2 | databricks#5824 | compute-target resolution |
| 3 | databricks#5826 | constraint fetch + offline cache |
| 4 | databricks#5827 | formatting-preserving pyproject.toml merge |
| 5 | databricks#5828 | six-phase pipeline + detection + package-manager interface
|
| 6 | databricks#5832 | uv backend + CLI command (registered hidden) |
| 7 | databricks#5833 | acceptance tests |
| 8 | databricks#5835 | unveil (unhide + help + changelog) |
This pull request and its description were written by Isaac.
ZaSkittles pushed a commit to ZaSkittles/cli that referenced this pull request Jul 7, 2026
…icks#5826)
## Why
- Once a target resolves to an env key, `local-env` needs the pinned
Python version, `databricks-connect` version, and dependency constraints
published for that key.
- The fetch must degrade gracefully offline and distinguish “this
environment isn't published” from “the network is down,” because those
call for different user action.
- The artifact host must be a Databricks-owned, access-controlled
location and must never default to a personal repo — whoever controls
the host controls what the CLI installs.
## What
- **`constraints.go`** — fetches the per-environment `pyproject.toml`,
parses `requires-python`, the `databricks-connect` pin, and `[tool.uv]`
`constraint-dependencies`, and caches it on disk.
- **Host parameterization** — no host is hardcoded:
`RepoConstraintBaseURL` reads the repo (`owner/name`) from the temporary
`DATABRICKS_LOCALENV_CONSTRAINT_REPO` env var and builds a
`raw.githubusercontent.com/<repo>/main` URL; the built-in default is
empty. When unset it returns `""` and `FetchConstraints` reports the
missing source as a fetch-phase `E_FETCH` error (so there is no
untrusted default, and the failure flows through the normal phase/JSON
reporting). Once `databricks/environments` can publish, that becomes the
hardcoded default and the env var is no longer required.
- Failure classification: **404** → `E_ENV_UNSUPPORTED` (no cache
fallback — a distinct non-transient condition); **transport / non-404**
→ `E_FETCH` with fallback to the last-good cached copy.
- Robustness: validate the body (parse + require `requires-python`)
**before** caching so a bad 2xx can't poison the cache; atomic cache
write (mkdir + temp-file + rename); dedicated `http.Client` with a 30s
timeout; body read bounded by `io.LimitReader` at 1 MiB;
`databricks-connect` matched by leading package name under PEP 503
normalization (so `Databricks_Connect` matches, `databricks-connectors`
does not); cache filename = readable slug + sha256 suffix to prevent
collisions.
## Testing strategy
- Unit tests with an `httptest` server: 200-parse, 404 →
`E_ENV_UNSUPPORTED`, transport failure + cache fallback,
missing-`requires-python` rejection, PEP 503 name matching, cache-dir
creation, collision-free filenames, oversized-body rejection
(`constraints_test.go`).
- Host resolution: `TestRepoConstraintBaseURL` (env var → URL, unset →
`""`, whitespace treated as unset) and
`TestFetchConstraintsNoSourceConfigured` (empty host → `E_FETCH` naming
the env var).
- Gates: `go build`, `go test`, `golangci-lint`, `deadcode`, `gofmt` —
all green.
- Reviewed with codex to a clean pass (several fetch/cache edge-case
fixes landed from review).
---
## About this stack
This is one of a series of small, stacked PRs that together add the
`databricks local-env python sync` command — it provisions a local
Python environment (Python version, `databricks-connect` pin, and
dependency constraints) matched to a selected Databricks compute target.
The work was split from one large branch into single-concern layers so
each is independently reviewable; the command is kept hidden until the
final PR so nothing is user-visible mid-stack.
**Review bottom-up.** Each PR targets the previous one as its base
branch, so its diff shows only that layer.
| # | PR | What |
|---|----|------|
| 1 | databricks#5823 | foundation: result types + env-key mapping |
| 2 | databricks#5824 | compute-target resolution |
| 3 | **databricks#5826 ← you are here** | constraint fetch + offline cache |
| 4 | databricks#5827 | formatting-preserving pyproject.toml merge |
| 5 | databricks#5828 | six-phase pipeline + detection + package-manager interface
|
| 6 | databricks#5832 | uv backend + CLI command (registered hidden) |
| 7 | databricks#5833 | acceptance tests |
| 8 | databricks#5835 | unveil (unhide + help + changelog) |
This pull request and its description were written by Isaac.
ZaSkittles pushed a commit to ZaSkittles/cli that referenced this pull request Jul 7, 2026
…atabricks#5827)
## Why
- `local-env` must apply the resolved Python version and constraints to
the user's `pyproject.toml` without disturbing their own content —
comments, ordering, formatting, and unrelated config must survive
untouched.
- Re-running must be safe and idempotent, and a greenfield project needs
a sensible file created from scratch.
- This is the most intricate logic in the feature, so it lands in its
own PR for focused review.
## What
- **`merge.go`** — a formatting-preserving merge that rewrites only the
env-owned regions (`requires-python`, the `databricks-connect` entry in
`[dependency-groups].dev`, and a marker-bracketed managed `[tool.uv]`
block) and preserves every other byte incl. CRLF; idempotent.
`RenderFreshPyproject` builds a complete managed file for a greenfield
project.
- Scoping/robustness: managed `constraint-dependencies` nests
header-less inside an existing user `[tool.uv]` (never a duplicate
header); single- vs multi-line array detection tracks real bracket depth
outside strings/comments; the `databricks-connect` rewrite is confined
to `dev` and leaves trailing comments alone; `requires-python`'s inline
comment is preserved; table-header parsing tolerates inline comments and
recognizes `[[array.of.tables]]`.
## Testing strategy
- Unit tests that parse the merged output as TOML (not just substring
checks), covering idempotency, CRLF preservation, user-key preservation,
the duplicate-`[tool.uv]` case, bracket-in-element arrays,
sibling-group/comment non-clobbering, and `[[tool.uv.index]]` children
(`merge_test.go`).
- Gates: `go build`, `go test`, `golangci-lint`, `deadcode`, `gofmt` —
all green.
- Reviewed with codex to a clean pass (multiple TOML-corruption edge
cases were caught and fixed).
---
## About this stack
This is one of a series of small, stacked PRs that together add the
`databricks local-env python sync` command — it provisions a local
Python environment (Python version, `databricks-connect` pin, and
dependency constraints) matched to a selected Databricks compute target.
The work was split from one large branch into single-concern layers so
each is independently reviewable; the command is kept hidden until the
final PR so nothing is user-visible mid-stack.
**Review bottom-up.** Each PR targets the previous one as its base
branch, so its diff shows only that layer.
| # | PR | What |
|---|----|------|
| 1 | databricks#5823 | foundation: result types + env-key mapping |
| 2 | databricks#5824 | compute-target resolution |
| 3 | databricks#5826 | constraint fetch + offline cache |
| 4 | **databricks#5827 ← you are here** | formatting-preserving pyproject.toml
merge |
| 5 | databricks#5828 | six-phase pipeline + detection + package-manager interface
|
| 6 | databricks#5832 | uv backend + CLI command (registered hidden) |
| 7 | databricks#5833 | acceptance tests |
| 8 | databricks#5835 | unveil (unhide + help + changelog) |
This pull request and its description were written by Isaac.
swearyangupta pushed a commit to swearyangupta/cli that referenced this pull request Jul 17, 2026
…ks#5832)
## Why
- The engine needs a real `PackageManager` implementation (uv) and a CLI
entry point so a user can actually run the feature.
- Wiring it in while keeping it `Hidden` lets the command be dogfooded
and exercised by acceptance tests without becoming user-visible until
the stack is complete.
- This is the first layer reachable from `main`, so it also makes the
whole `libs/localenv` package live for the `deadcode` checker.
## What
- **`libs/localenv/uv.go`** — the uv implementation of `PackageManager`:
discover/install uv, install the Python minor, `uv sync`, seed pip into
the venv, validate; plus the `pip.conf` → `UV_INDEX_URL` bridge for
Databricks-managed machines.
- **`cmd/localenv/`** — the command tree matching `local-env python
sync`: a `local-env` group (`Hidden: true`), a `python` subgroup, and
the `sync` verb. Parent nodes use `root.ReportUnknownSubcommand`; `sync`
uses `cobra.NoArgs`, resolves flags/bundle target, builds the `Pipeline`
with the uv manager, and renders text or `--json`. All Cobra `Use`
values + the `--json` command field come from the `libs/localenv`
constants.
- **`cmd/cmd.go`** — registers the group.
## Testing strategy
- Unit tests for uv helper logic (discovery, `pip.conf` index-url, arg
builders, stderr surfacing) (`uv_test.go`).
- Runtime smoke test of the hidden 3-level command: absent from
top-level help; help works at each level; unknown subcommand exits
non-zero; bare group shows help; flags + mutual-exclusion + `NoArgs`
behave.
- Gates: `go build ./...`, `go test`, `golangci-lint`, `deadcode` (whole
tree, no pragmas), `gofmt` — all green.
- `cmd/localenv/` unit tests are intentionally deferred to the
acceptance PR (databricks#5833), per the repo convention that user-visible CLI
output is covered by acceptance tests.
---
## About this stack
This is one of a series of small, stacked PRs that together add the
`databricks local-env python sync` command — it provisions a local
Python environment (Python version, `databricks-connect` pin, and
dependency constraints) matched to a selected Databricks compute target.
The work was split from one large branch into single-concern layers so
each is independently reviewable; the command is kept hidden until the
final PR so nothing is user-visible mid-stack.
**Review bottom-up.** Each PR targets the previous one as its base
branch (this PR targets `main`), so its diff shows only that layer.
Layers 1–5 have merged; the original layer-5 PR (databricks#5828) was split into
5a/5b/5c during review.
| # | PR | What | Status |
|---|----|------|--------|
| 1 | databricks#5823 | foundation: result types + env-key mapping | merged |
| 2 | databricks#5824 | compute-target resolution | merged |
| 3 | databricks#5826 | constraint fetch + offline cache | merged |
| 4 | databricks#5827 | formatting-preserving pyproject.toml merge | merged |
| 5a | databricks#5850 | package-manager interface + detection | merged |
| 5b | databricks#5851 | six-phase pipeline orchestrator | merged |
| 5c | databricks#5854 | --check cache purity, greenfield name, dbc insertion |
merged |
| 6 | **databricks#5832 ← you are here** | uv backend + CLI command (registered
hidden) | |
| 7 | databricks#5833 | acceptance tests | |
| 8 | databricks#5835 | unveil (unhide + help + changelog) | |
This pull request and its description were written by Isaac.
swearyangupta pushed a commit to swearyangupta/cli that referenced this pull request Jul 17, 2026
## Why
- The command's user-visible behavior — text and `--json` output, and
every error path — needs end-to-end coverage against the real CLI.
- `cmd/localenv/` carries no unit tests by design, so acceptance tests
are where that surface is verified (repo convention: user-visible CLI
output is covered by acceptance tests).
## What
- **`acceptance/localenv/`** — 9 scenarios driven through the (hidden)
command against the in-process fake server: `help` (three-level tree),
`no-target` (`E_NO_TARGET`), `flag-conflict` (Cobra mutual-exclusion),
`manager-unsupported` (conda project → clean P1 exit), `env-unsupported`
(404 → `E_ENV_UNSUPPORTED` at fetch), `json-error` (`--output json`
error object), `serverless-check` (dry-run plan), `serverless-json`
(`--json` plan), and `constraints-only`.
- Scripts use `local-env python sync` and the
`DATABRICKS_LOCALENV_CONSTRAINT_SOURCE` override; goldens show the
`local-env python sync` command field and managed marker. No source
changes.
## Testing strategy
- Goldens generated with `-update` and verified **stable on a clean
re-run** (no `-update`); all 9 subtests pass.
- `musterr` guards the five expected-failure scenarios; `trace` shows
the three output-producing ones.
- Full acceptance suite run to confirm no regressions elsewhere (only
pre-existing, environment-specific failures unrelated to this change).
- Diff confined to `acceptance/localenv/`.
- Independently verified by a review subagent (PASS — goldens, scripts,
stubs, stale-refs, hygiene) and by codex (no issues).
---
## About this stack
This is one of a series of small, stacked PRs that together add the
`databricks local-env python sync` command — it provisions a local
Python environment (Python version, `databricks-connect` pin, and
dependency constraints) matched to a selected Databricks compute target.
The work was split from one large branch into single-concern layers so
each is independently reviewable; the command is kept hidden until the
final PR so nothing is user-visible mid-stack.
**Review bottom-up.** Layers 1–5 have merged (the original layer-5 PR
databricks#5828 was split into 5a/5b/5c during review).
| # | PR | What | Status |
|---|----|------|--------|
| 1 | databricks#5823 | foundation: result types + env-key mapping | merged |
| 2 | databricks#5824 | compute-target resolution | merged |
| 3 | databricks#5826 | constraint fetch + offline cache | merged |
| 4 | databricks#5827 | formatting-preserving pyproject.toml merge | merged |
| 5a | databricks#5850 | package-manager interface + detection | merged |
| 5b | databricks#5851 | six-phase pipeline orchestrator | merged |
| 5c | databricks#5854 | --check cache purity, greenfield name, dbc insertion |
merged |
| 6 | databricks#5832 | uv backend + CLI command (registered hidden) | |
| 7 | **databricks#5833 ← you are here** | acceptance tests | |
| 8 | databricks#5835 | unveil (unhide + help + changelog) | |
This pull request and its description were written by Isaac.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@rugpanov@eng-dev-ecosystem-bot@anton-107