From 9d8fc59e53a93e13c87114cb9e2ceb9066413625 Mon Sep 17 00:00:00 2001 From: Grigory Panov Date: Fri, 17 Jul 2026 14:49:26 +0200 Subject: [PATCH] Default serverless target to v5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the [P0] CLI Changes spec, the serverless stand-in used when the source does not pin a version is now serverless-v5 (was v4). This applies only to the fallback cases — a serverless job with no recorded version, and a bundle that records 'serverless' without a version. Explicitly passing --serverless-version is unaffected. Introduce defaultServerlessVersion in envkey.go and use it at both fallback sites so the default lives in one place. VS Code resolves the real version itself and passes --serverless-version explicitly, so this fallback applies only when the version is genuinely unknown. Review (anton-107, #5962): - Add serverless-default-check acceptance test: an unpinned serverless job resolves to serverless-v5 and fetches it end-to-end, so the v5 default is actually exercised (and would break if v5 were unpublished). - Assert concrete "serverless/serverless-v5" in the two default unit tests instead of "serverless-"+defaultServerlessVersion, which compared the constant to itself and passed for any value. - Update stale v4 comments left by the default move (compute.go and job-serverless-check/test.toml) to v5. Co-authored-by: Isaac --- .../localenv/job-serverless-check/test.toml | 2 +- .../serverless-default-check/out.test.toml | 3 ++ .../serverless-default-check/output.txt | 13 +++++++ .../localenv/serverless-default-check/script | 1 + .../serverless-default-check/test.toml | 39 +++++++++++++++++++ cmd/environments/compute.go | 4 +- libs/localenv/envkey.go | 8 ++++ libs/localenv/target.go | 14 +++---- libs/localenv/target_test.go | 15 ++++--- 9 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 acceptance/localenv/serverless-default-check/out.test.toml create mode 100644 acceptance/localenv/serverless-default-check/output.txt create mode 100644 acceptance/localenv/serverless-default-check/script create mode 100644 acceptance/localenv/serverless-default-check/test.toml diff --git a/acceptance/localenv/job-serverless-check/test.toml b/acceptance/localenv/job-serverless-check/test.toml index 34f8f09eb73..9e201b0b3db 100644 --- a/acceptance/localenv/job-serverless-check/test.toml +++ b/acceptance/localenv/job-serverless-check/test.toml @@ -4,7 +4,7 @@ EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] DATABRICKS_LOCALENV_CONSTRAINT_SOURCE = "$DATABRICKS_HOST" # A serverless job pins its environment_version on the environment spec, so the -# target resolves to that serverless-vN (here v3) rather than defaulting to v4. +# target resolves to that serverless-vN (here v3) rather than defaulting to v5. [[Server]] Pattern = "GET /api/2.2/jobs/get" Response.Body = ''' diff --git a/acceptance/localenv/serverless-default-check/out.test.toml b/acceptance/localenv/serverless-default-check/out.test.toml new file mode 100644 index 00000000000..d6187dcb046 --- /dev/null +++ b/acceptance/localenv/serverless-default-check/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] diff --git a/acceptance/localenv/serverless-default-check/output.txt b/acceptance/localenv/serverless-default-check/output.txt new file mode 100644 index 00000000000..96512162951 --- /dev/null +++ b/acceptance/localenv/serverless-default-check/output.txt @@ -0,0 +1,13 @@ + +>>> [CLI] environments setup-local --job-id 12345 --dry-run +preflight ok check +resolve ok source=job envKey=serverless/serverless-v5 +fetch ok source=[DATABRICKS_URL]/serverless/serverless-v5/pyproject.toml fromCache=false +merge ok +provision ok +validate ok +Plan: [TEST_TMP_DIR]/pyproject.toml + changed region: requires-python + changed region: tool.uv.constraint-dependencies + changed region: databricks-connect +Check complete. No files were modified. diff --git a/acceptance/localenv/serverless-default-check/script b/acceptance/localenv/serverless-default-check/script new file mode 100644 index 00000000000..07e14a5386f --- /dev/null +++ b/acceptance/localenv/serverless-default-check/script @@ -0,0 +1 @@ +trace $CLI environments setup-local --job-id 12345 --dry-run diff --git a/acceptance/localenv/serverless-default-check/test.toml b/acceptance/localenv/serverless-default-check/test.toml new file mode 100644 index 00000000000..dfb5335a345 --- /dev/null +++ b/acceptance/localenv/serverless-default-check/test.toml @@ -0,0 +1,39 @@ +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] + +[Env] +DATABRICKS_LOCALENV_CONSTRAINT_SOURCE = "$DATABRICKS_HOST" + +# A serverless job that pins no environment_version exercises the default-version +# path: ResolveTarget falls back to defaultServerlessVersion (v5), so the target +# resolves to serverless-v5. This is the end-to-end coverage that the v5 default +# actually resolves and fetches, distinct from job-serverless-check which pins v3. +[[Server]] +Pattern = "GET /api/2.2/jobs/get" +Response.Body = ''' +{ + "job_id": 12345, + "settings": { + "name": "serverless-job-unpinned", + "environments": [ + {"environment_key": "default", "spec": {}} + ] + } +} +''' + +[[Server]] +Pattern = "GET /serverless/serverless-v5/pyproject.toml" +Response.Body = ''' +[project] +requires-python = ">=3.12" + +[dependency-groups] +dev = ["databricks-connect~=17.2.0"] + +[tool.uv] +constraint-dependencies = ["pyarrow<19", "pandas<3"] +''' + +[[Repls]] +Old = 'uv uv \S+(?: \([^)]+\))?' +New = 'uv [UV_VERSION]' diff --git a/cmd/environments/compute.go b/cmd/environments/compute.go index dc75c2e1d43..97a29ef56f1 100644 --- a/cmd/environments/compute.go +++ b/cmd/environments/compute.go @@ -108,7 +108,7 @@ func (c sdkCompute) GetJobSparkVersion(ctx context.Context, jobID string) (spark // The serverless environment version (e.g. "4") is recorded on the job's // environment spec, unlike the bundle path where it is unavailable. Return // it so ResolveTarget pins the matching serverless-vN instead of defaulting - // to v4. An empty version (older jobs) falls back to v4 in ResolveTarget. + // to v5. An empty version (older jobs) falls back to v5 in ResolveTarget. version := environmentVersion(job.Settings.Environments[0]) // Tasks can reference any environment_key, so if the job's environments do // not all share one version there is no single correct local environment @@ -146,7 +146,7 @@ func (c sdkCompute) GetJobSparkVersion(ctx context.Context, jobID string) (spark // // The version can arrive in either of two fields. environment_version is the // current one; client is its deprecated predecessor ("Use environment_version -// instead") and is still what some jobs pin. Reading both means the v4 fallback +// instead") and is still what some jobs pin. Reading both means the v5 fallback // and the divergence guard observe whichever field actually carries the pin, // rather than treating a client-pinned job as unversioned. base_environment is // deliberately ignored: it is a path/ID, not a version. diff --git a/libs/localenv/envkey.go b/libs/localenv/envkey.go index 070668ba826..23dc0f9ca53 100644 --- a/libs/localenv/envkey.go +++ b/libs/localenv/envkey.go @@ -14,6 +14,14 @@ import ( // by 3.10.6, so only the former bumps the minor. var clauseRe = regexp.MustCompile(`^(>=|<=|===|==|~=|!=|<|>)?\s*(\d+)\.(\d+)(\.\d+)?`) +// defaultServerlessVersion is the serverless environment version used when the +// source (a serverless job with no recorded version, or a bundle that only +// records "serverless") does not pin one. It is a documented stand-in for the +// latest LTS (spec §4.3 / §target-resolution); VS Code resolves the real version +// itself and passes --serverless-version explicitly, so this fallback only +// applies when the version is genuinely unknown. +const defaultServerlessVersion = "v5" + // NormalizeServerless returns the canonical "vN" spelling of a serverless // version accepting "4", "v4", or "V4". func NormalizeServerless(version string) string { diff --git a/libs/localenv/target.go b/libs/localenv/target.go index 86b89bd56ef..814f6d722b8 100644 --- a/libs/localenv/target.go +++ b/libs/localenv/target.go @@ -118,11 +118,11 @@ func ResolveTarget(ctx context.Context, f TargetFlags, c ComputeClient, bt Bundl } if isServerless { // Use the job's recorded serverless environment version when present; - // fall back to v4 when the job did not pin one (documented stand-in from - // the original script, spec §4.3). + // fall back to the default when the job did not pin one (documented + // stand-in, spec §4.3). v := version if v == "" { - v = "v4" + v = defaultServerlessVersion } return &TargetInfo{ Source: "job", @@ -145,12 +145,12 @@ func ResolveTarget(ctx context.Context, f TargetFlags, c ComputeClient, bt Bundl } if bt.Serverless { - // Default to serverless-v4: the serverless env version is not recorded - // in the bundle/project (documented stand-in from the original script). + // The bundle records that the target is serverless but not which version, + // so use the default stand-in (spec §4.3). return &TargetInfo{ Source: "bundle", - ServerlessVersion: "v4", - EnvKey: EnvKeyForServerless("v4"), + ServerlessVersion: NormalizeServerless(defaultServerlessVersion), + EnvKey: EnvKeyForServerless(defaultServerlessVersion), }, nil } diff --git a/libs/localenv/target_test.go b/libs/localenv/target_test.go index 1033b90909d..6438921a642 100644 --- a/libs/localenv/target_test.go +++ b/libs/localenv/target_test.go @@ -93,10 +93,13 @@ func TestResolveBundleNothingSelected(t *testing.T) { } func TestResolveBundleServerless(t *testing.T) { + // The bundle records serverless but no version, so the default stand-in applies. ti, err := ResolveTarget(t.Context(), TargetFlags{}, stubCompute{}, BundleTarget{Selected: true, Serverless: true}) require.NoError(t, err) assert.Equal(t, "bundle", ti.Source) - assert.Equal(t, "serverless/serverless-v4", ti.EnvKey) + // Concrete literal, not "serverless-"+defaultServerlessVersion: the default + // is v5, and asserting the constant against itself would pass for any value. + assert.Equal(t, "serverless/serverless-v5", ti.EnvKey) } // jobStubCompute returns distinct values for the first (sparkVersion) and third @@ -143,13 +146,15 @@ func TestResolveJobServerlessUsesRecordedVersion(t *testing.T) { assert.Equal(t, "serverless/serverless-v3", ti.EnvKey) } -func TestResolveJobServerlessEmptyVersionFallsBackToV4(t *testing.T) { - // When the job records no serverless version, ResolveTarget defaults to v4 - // (documented stand-in), matching the bundle serverless path. +func TestResolveJobServerlessEmptyVersionFallsBackToDefault(t *testing.T) { + // When the job records no serverless version, ResolveTarget uses the default + // stand-in, matching the bundle serverless path. c := jobStubCompute{isServerless: true, version: ""} ti, err := ResolveTarget(t.Context(), TargetFlags{Job: "42"}, c, BundleTarget{}) require.NoError(t, err) - assert.Equal(t, "serverless/serverless-v4", ti.EnvKey) + // Concrete literal, not "serverless-"+defaultServerlessVersion: the default + // is v5, and asserting the constant against itself would pass for any value. + assert.Equal(t, "serverless/serverless-v5", ti.EnvKey) } func TestValidateTargetFlagsMutuallyExclusive(t *testing.T) {