Skip to content

Commit 3ff74bd

Browse files
Stop prefixing vector_search_endpoints names (#5209)
## Changes - Stop applying `presets.name_prefix` (and the dev-mode `[dev <user>]` rename) to `vector_search_endpoints` in `bundle/config/mutator/resourcemutator/apply_presets.go`. - Add `.agent/rules/name-prefix.md` capturing the principle (only prefix display-name fields; never primary-key / object-id Names), scoped via globs to `apply_presets.go`, `apply_target_mode*.go`, and `bundle/direct/dresources/*.go`. Mirror as `.cursor/rules/name-prefix.mdc`. - Rename `TestAllNonUcResourcesAreRenamed` → `TestAppropriateResourcesAreRenamed` (the carve-out list now includes a non-UC resource), and refactor the long `resourceType ==` OR chain into a `slices.Contains` over a named slice hoisted to the outer loop. - `NEXT_CHANGELOG.md` entry under Bundles. ## Why The vector search endpoint name is the API primary key — it's how GET, UPDATE, and DELETE address the resource (`bundle/direct/dresources/vector_search_endpoint.go`: `id := config.Name`; `recreate_on_changes` for the resource doesn't list `name` only because there's no rename API at all, so a name change would silently drift). Prefixing it changed which remote endpoint the bundle pointed at, not just the label the user saw. The rule we want to encode is broader (display-name fields can be prefixed; identity-bearing Names cannot), but this PR only fixes the vector_search_endpoints case to keep the change focused; mlflow Models, ModelServingEndpoints, etc. have the same issue and are tracked for follow-up. ## Tests - `go test ./bundle/config/mutator/resourcemutator/` passes; `TestProcessTargetModeDevelopment` now asserts `vs_endpoint1` (not `dev_lennart_vs_endpoint1`), and `TestAppropriateResourcesAreRenamed` includes `*resources.VectorSearchEndpoint` in the carve-out list and verifies the Name doesn't pick up a `dev` prefix. - Confirmed locally that re-introducing the prefix loop in `apply_presets.go` causes both the explicit assertion and the reflective sweep to fail with clear diffs. - `./task fmt`, `./task checks`, `./task lint`, `./task test` clean. _This PR was written by Claude Code._
1 parent 2f25245 commit 3ff74bd

3 files changed

Lines changed: 45 additions & 36 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* 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.
99

1010
### Bundles
11+
* Stop applying `presets.name_prefix` (and the dev-mode `[dev <user>]` rename) to `vector_search_endpoints` ([#5209](https://github.com/databricks/cli/pull/5209)).
1112

1213
* Fix `bundle generate` job to preserve nested notebook directory structure ([#4596](https://github.com/databricks/cli/pull/4596))
1314
* 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.

bundle/config/mutator/resourcemutator/apply_presets.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,9 @@ func (m *applyPresets) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnos
290290
}
291291
}
292292

293-
// Vector Search Endpoints: Prefix
294-
for _, e := range r.VectorSearchEndpoints {
295-
if e == nil {
296-
continue
297-
}
298-
e.Name = normalizePrefix(prefix) + e.Name
299-
}
293+
// Vector Search Endpoints: no prefix. The endpoint name is the primary key
294+
// (it's what GET/UPDATE/DELETE address by), so prefixing it would change
295+
// the resource's identity rather than just its display name.
300296

301297
return diags
302298
}

bundle/config/mutator/resourcemutator/apply_target_mode_test.go

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,8 @@ func TestProcessTargetModeDevelopment(t *testing.T) {
303303
// Model serving endpoint 1
304304
assert.Equal(t, "dev_lennart_servingendpoint1", b.Config.Resources.ModelServingEndpoints["servingendpoint1"].Name)
305305

306-
// Vector search endpoint 1
307-
assert.Equal(t, "dev_lennart_vs_endpoint1", b.Config.Resources.VectorSearchEndpoints["vs_endpoint1"].Name)
306+
// Vector search endpoint 1: name is the primary key, so it must not be prefixed.
307+
assert.Equal(t, "vs_endpoint1", b.Config.Resources.VectorSearchEndpoints["vs_endpoint1"].Name)
308308

309309
// Registered model 1
310310
assert.Equal(t, "dev_lennart_registeredmodel1", b.Config.Resources.RegisteredModels["registeredmodel1"].Name)
@@ -414,17 +414,33 @@ func TestAllResourcesMocked(t *testing.T) {
414414
}
415415
}
416416

417-
// Make sure that we at rename all non UC resources
418-
func TestAllNonUcResourcesAreRenamed(t *testing.T) {
417+
// TestAppropriateResourcesAreRenamed checks that every resource with a user-facing
418+
// Name field is renamed by dev-mode / presets.name_prefix, except for an
419+
// explicit carve-out list. The carve-out applies to resources whose Name is
420+
// the API primary key / object id (not a display name) — prefixing those
421+
// would change the resource's identity rather than its label.
422+
func TestAppropriateResourcesAreRenamed(t *testing.T) {
419423
b := mockBundle(config.Development)
420424

421-
// UC resources should not have a prefix added to their name. Right now
422-
// this list only contains the Volume, Catalog, and ExternalLocation resources since we have yet to remove
423-
// prefixing support for UC schemas and registered models.
424-
ucFields := []reflect.Type{
425+
notRenamedFields := []reflect.Type{
425426
reflect.TypeFor[*resources.Catalog](),
426427
reflect.TypeFor[*resources.ExternalLocation](),
427428
reflect.TypeFor[*resources.Volume](),
429+
reflect.TypeFor[*resources.VectorSearchEndpoint](),
430+
}
431+
432+
// Resources whose Name is server-generated or otherwise not a user-facing
433+
// label, so the rename matrix doesn't apply. Reflection still finds a
434+
// Name field on these via embedded SDK types, hence the explicit skip.
435+
notUserNamed := []string{
436+
"Apps",
437+
"SecretScopes",
438+
"DatabaseInstances",
439+
"DatabaseCatalogs",
440+
"SyncedDatabaseTables",
441+
"PostgresProjects",
442+
"PostgresBranches",
443+
"PostgresEndpoints",
428444
}
429445

430446
diags := bundle.ApplySeq(t.Context(), b, ApplyTargetMode(), ApplyPresets())
@@ -433,28 +449,24 @@ func TestAllNonUcResourcesAreRenamed(t *testing.T) {
433449
resources := reflect.ValueOf(b.Config.Resources)
434450
for i := range resources.NumField() {
435451
field := resources.Field(i)
452+
if field.Kind() != reflect.Map {
453+
continue
454+
}
455+
resourceType := resources.Type().Field(i).Name
456+
if slices.Contains(notUserNamed, resourceType) {
457+
continue
458+
}
459+
for _, key := range field.MapKeys() {
460+
resource := field.MapIndex(key)
461+
nameField := resource.Elem().FieldByName("Name")
462+
if !nameField.IsValid() || nameField.Kind() != reflect.String {
463+
continue
464+
}
436465

437-
if field.Kind() == reflect.Map {
438-
for _, key := range field.MapKeys() {
439-
resource := field.MapIndex(key)
440-
nameField := resource.Elem().FieldByName("Name")
441-
resourceType := resources.Type().Field(i).Name
442-
443-
// Skip resources that are not renamed (either because they don't have a user-facing Name field,
444-
// or because their Name is server-generated rather than user-specified)
445-
if resourceType == "Apps" || resourceType == "SecretScopes" || resourceType == "DatabaseInstances" || resourceType == "DatabaseCatalogs" || resourceType == "SyncedDatabaseTables" || resourceType == "PostgresProjects" || resourceType == "PostgresBranches" || resourceType == "PostgresEndpoints" {
446-
continue
447-
}
448-
449-
if !nameField.IsValid() || nameField.Kind() != reflect.String {
450-
continue
451-
}
452-
453-
if slices.Contains(ucFields, resource.Type()) {
454-
assert.NotContains(t, nameField.String(), "dev", "process_target_mode should not rename '%s' in '%s'", key, resources.Type().Field(i).Name)
455-
} else {
456-
assert.Contains(t, nameField.String(), "dev", "process_target_mode should rename '%s' in '%s'", key, resources.Type().Field(i).Name)
457-
}
466+
if slices.Contains(notRenamedFields, resource.Type()) {
467+
assert.NotContains(t, nameField.String(), "dev", "process_target_mode should not rename '%s' in '%s'", key, resourceType)
468+
} else {
469+
assert.Contains(t, nameField.String(), "dev", "process_target_mode should rename '%s' in '%s'", key, resourceType)
458470
}
459471
}
460472
}

0 commit comments

Comments
 (0)