Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion acceptance/localenv/constraints-only/output.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

>>> [CLI] environments setup-local --serverless-version v4 --constraints-only --dry-run --output json
>>> [CLI] environments setup-local --serverless-version 4 --constraints-only --dry-run --output json
{
"schemaVersion": 1,
"command": "environments setup-local",
Expand Down
2 changes: 1 addition & 1 deletion acceptance/localenv/constraints-only/script
Original file line number Diff line number Diff line change
@@ -1 +1 @@
trace $CLI environments setup-local --serverless-version v4 --constraints-only --dry-run --output json
trace $CLI environments setup-local --serverless-version 4 --constraints-only --dry-run --output json
2 changes: 1 addition & 1 deletion acceptance/localenv/env-unsupported/output.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
preflight ok check
resolve ok source=cluster envKey=dbr/15.4.x-scala2.12
fetch error no published environment for "dbr/15.4.x-scala2.12". If this is a new runtime, try the latest LTS target (e.g. --serverless-version v4 or a supported --cluster-id DBR): GET [DATABRICKS_URL]/dbr/15.4.x-scala2.12/pyproject.toml: environment key not found
fetch error no published environment for "dbr/15.4.x-scala2.12". If this is a new runtime, try the latest LTS target (e.g. --serverless-version 5 or a supported --cluster-id DBR): GET [DATABRICKS_URL]/dbr/15.4.x-scala2.12/pyproject.toml: environment key not found
merge pending
provision pending
validate pending
Expand Down
2 changes: 1 addition & 1 deletion acceptance/localenv/flag-conflict-json/script
Original file line number Diff line number Diff line change
@@ -1 +1 @@
musterr $CLI environments setup-local --cluster-id abc --serverless-version v4 --output json
musterr $CLI environments setup-local --cluster-id abc --serverless-version 4 --output json
2 changes: 1 addition & 1 deletion acceptance/localenv/flag-conflict/script
Original file line number Diff line number Diff line change
@@ -1 +1 @@
musterr $CLI environments setup-local --cluster-id abc --serverless-version v4
musterr $CLI environments setup-local --cluster-id abc --serverless-version 4
2 changes: 1 addition & 1 deletion acceptance/localenv/help/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Flags:
--dry-run compute the plan without writing files or provisioning
-h, --help help for setup-local
--job-id string job ID to use as the compute target
--serverless-version string serverless version to use as the compute target (e.g. v4)
--serverless-version string serverless version to use as the compute target (e.g. 5)

Global Flags:
--debug enable debug logging
Expand Down
2 changes: 1 addition & 1 deletion acceptance/localenv/manager-unsupported/script
Original file line number Diff line number Diff line change
@@ -1 +1 @@
musterr $CLI environments setup-local --serverless-version v4 --dry-run
musterr $CLI environments setup-local --serverless-version 4 --dry-run
2 changes: 1 addition & 1 deletion acceptance/localenv/serverless-check/output.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

>>> [CLI] environments setup-local --serverless-version v4 --dry-run
>>> [CLI] environments setup-local --serverless-version 4 --dry-run
preflight ok check
resolve ok source=serverless envKey=serverless/serverless-v4
fetch ok source=[DATABRICKS_URL]/serverless/serverless-v4/pyproject.toml fromCache=false
Expand Down
2 changes: 1 addition & 1 deletion acceptance/localenv/serverless-check/script
Original file line number Diff line number Diff line change
@@ -1 +1 @@
trace $CLI environments setup-local --serverless-version v4 --dry-run
trace $CLI environments setup-local --serverless-version 4 --dry-run
2 changes: 1 addition & 1 deletion acceptance/localenv/serverless-json/output.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

>>> [CLI] environments setup-local --serverless-version v4 --dry-run --output json
>>> [CLI] environments setup-local --serverless-version 4 --dry-run --output json
{
"schemaVersion": 1,
"command": "environments setup-local",
Expand Down
2 changes: 1 addition & 1 deletion acceptance/localenv/serverless-json/script
Original file line number Diff line number Diff line change
@@ -1 +1 @@
trace $CLI environments setup-local --serverless-version v4 --dry-run --output json
trace $CLI environments setup-local --serverless-version 4 --dry-run --output json
2 changes: 1 addition & 1 deletion cmd/environments/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ env-owned sections are refreshed, user-owned content is preserved).`,
func addTargetFlags(cmd *cobra.Command) {
cmd.Flags().String("cluster-id", "", "cluster ID to use as the compute target")
cmd.Flags().String("cluster-name", "", "cluster name to use as the compute target (resolved to an ID via the Clusters API)")
cmd.Flags().String("serverless-version", "", "serverless version to use as the compute target (e.g. v4)")
cmd.Flags().String("serverless-version", "", "serverless version to use as the compute target (e.g. 5)")
cmd.Flags().String("job-id", "", "job ID to use as the compute target")
cmd.Flags().Bool("constraints-only", false, "apply the Python version and constraints without adding the databricks-connect dependency")
cmd.Flags().Bool("dry-run", false, "compute the plan without writing files or provisioning")
Expand Down
2 changes: 1 addition & 1 deletion libs/localenv/constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func FetchConstraints(ctx context.Context, baseURL, envKey, cacheDir string, wri
// fallback: the target resolved to an environment that isn't published.
if errors.Is(fetchErr, errEnvKeyNotFound) {
return nil, NewError(ErrEnvUnsupported, fetchErr,
"no published environment for %q. If this is a new runtime, try the latest LTS target (e.g. --serverless-version v4 or a supported --cluster-id DBR)", envKey)
"no published environment for %q. If this is a new runtime, try the latest LTS target (e.g. --serverless-version 5 or a supported --cluster-id DBR)", envKey)
}

// Network or HTTP failure: attempt to serve from cache.
Expand Down
18 changes: 16 additions & 2 deletions libs/localenv/envkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,24 @@ var clauseRe = regexp.MustCompile(`^(>=|<=|===|==|~=|!=|<|>)?\s*(\d+)\.(\d+)(\.\
// 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"
const defaultServerlessVersion = "5"

// serverlessVersionRe matches an accepted --serverless-version input: a bare
// number ("5", the documented form) or a "v"-prefixed one ("v5"/"V5").
var serverlessVersionRe = regexp.MustCompile(`^[vV]?[0-9]+$`)

// ValidServerlessVersion reports whether s is an accepted serverless version
// input. It is validated at resolve time so a malformed value (e.g. "vv5", "v",
// " 5") fails fast with an actionable error rather than resolving to a bogus
// env key that only 404s two phases later at fetch.
func ValidServerlessVersion(s string) bool {
return serverlessVersionRe.MatchString(s)
}

// NormalizeServerless returns the canonical "vN" spelling of a serverless
// version accepting "4", "v4", or "V4".
// version. The documented input is a bare number ("5"), but a "v"-prefixed form
// ("v5"/"V5") is also accepted; both map to the "serverless-vN" env key.
// Callers should validate with ValidServerlessVersion first.
func NormalizeServerless(version string) string {
return "v" + strings.TrimPrefix(strings.ToLower(version), "v")
}
Expand Down
21 changes: 21 additions & 0 deletions libs/localenv/envkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,30 @@ import (
)

func TestEnvKeyForServerless(t *testing.T) {
// The documented input is a bare number; a "v"-prefixed form is also accepted.
// Both map to the "serverless-vN" env key.
for _, in := range []string{"4", "v4", "V4"} {
assert.Equal(t, "serverless/serverless-v4", EnvKeyForServerless(in))
}
for _, in := range []string{"5", "v5", "V5"} {
assert.Equal(t, "serverless/serverless-v5", EnvKeyForServerless(in))
}
}

func TestDefaultServerlessVersionIsBareNumber(t *testing.T) {
// The default stand-in is stored in the documented bare form; it still maps
// to the serverless-vN env key via NormalizeServerless.
assert.Equal(t, "5", defaultServerlessVersion)
assert.Equal(t, "serverless/serverless-v5", EnvKeyForServerless(defaultServerlessVersion))
}

func TestValidServerlessVersion(t *testing.T) {
for _, ok := range []string{"5", "4", "v5", "V5", "17", "0"} {
assert.True(t, ValidServerlessVersion(ok), "%q should be valid", ok)
}
for _, bad := range []string{"", "v", "vv5", "5x", "latest", " 5", "5 ", "v5.1", "-5"} {
assert.False(t, ValidServerlessVersion(bad), "%q should be invalid", bad)
}
}

func TestEnvKeyForSparkVersion(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions libs/localenv/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ func ResolveTarget(ctx context.Context, f TargetFlags, c ComputeClient, bt Bundl
}

if f.Serverless != "" {
if !ValidServerlessVersion(f.Serverless) {
return nil, NewError(ErrResolve, nil, "invalid --serverless-version %q: expected a version number like 5", f.Serverless)
}
return &TargetInfo{
Source: "serverless",
ServerlessVersion: NormalizeServerless(f.Serverless),
Expand Down
18 changes: 18 additions & 0 deletions libs/localenv/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ func TestResolveServerlessFlag(t *testing.T) {
assert.Equal(t, "serverless/serverless-v4", ti.EnvKey)
}

func TestResolveServerlessFlagBareNumber(t *testing.T) {
// The documented input is a bare number; it normalizes to the vN env key.
ti, err := ResolveTarget(t.Context(), TargetFlags{Serverless: "5"}, stubCompute{}, BundleTarget{})
require.NoError(t, err)
assert.Equal(t, "serverless/serverless-v5", ti.EnvKey)
}

func TestResolveServerlessFlagRejectsMalformed(t *testing.T) {
// Malformed values fail fast at resolve (E_RESOLVE) rather than resolving to
// a bogus env key that only 404s at fetch.
for _, bad := range []string{"vv5", "v", " 5", "5x", "latest"} {
_, err := ResolveTarget(t.Context(), TargetFlags{Serverless: bad}, stubCompute{}, BundleTarget{})
var pe *PipelineError
require.ErrorAs(t, err, &pe, "input %q should error", bad)
assert.Equal(t, ErrResolve, pe.Code, "input %q", bad)
}
}

func TestResolveClusterFlag(t *testing.T) {
c := stubCompute{clusterVersion: "15.4.x-scala2.12"}
ti, err := ResolveTarget(t.Context(), TargetFlags{Cluster: "abc"}, c, BundleTarget{})
Expand Down
Loading