Skip to content

Commit c6168a1

Browse files
Revert vector_search_endpoints UUID persistence (#5127, #5192) (#5193)
## Summary - Reverts #5127 (`Persist endpoint UUID for vector_search_endpoints drift detection`) and the follow-up changelog entry from #5192. - The badness #5127 was meant to fix — bundle silently rebinding permissions to a different backing endpoint after an out-of-band recreate — was actually addressed by the testserver fix in #5186 (`testserver: 404 on permissions GET when V2 parent is gone`). With the testserver matching real V2 cloud behavior, bundle correctly observes that the new endpoint has no permissions and creates them, with no permanent drift afterwards. UUID persistence in state is no longer necessary. - Reworks the `drift/recreated_same_name` acceptance test: keeps endpoint permissions in `databricks.yml`, drops the obsolete "recreate detected" assertion, and adds a post-deploy `bundle plan` to confirm there is no permanent drift. ## Test plan - [x] `./task build` clean. - [x] `go test ./acceptance -run 'TestAccept/bundle/resources/vector_search_endpoints/drift'` — all green (terraform + direct). - [x] `go test ./bundle/direct/dresources/...` — green. - [x] `./task lint-q` — clean. - [x] Verified post-deploy plan shows `Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged` after an out-of-band endpoint recreate, so permissions don't end up in permanent drift even without UUID-based recreate detection. This pull request and its description were written by Isaac.
1 parent 360b53a commit c6168a1

8 files changed

Lines changed: 28 additions & 98 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
### Bundles
1111
* Validate that resource keys do not contain variable references ([#5169](https://github.com/databricks/cli/pull/5169))
1212
* engine/direct: Drop the deployment state entry on a recreate before the follow-up `Create`, so a `Create` failure no longer leaves a broken state with `invalid state: empty id` on the next `bundle plan` ([#5173](https://github.com/databricks/cli/pull/5173)).
13-
* engine/direct: vector search endpoints: trigger recreate when endpoint is recreated out of band ([#5127](https://github.com/databricks/cli/pull/5127))
1413

1514
### Dependency updates
1615

acceptance/bundle/refschema/out.fields.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3042,7 +3042,7 @@ resources.vector_search_endpoints.*.endpoint_status *vectorsearch.EndpointStatus
30423042
resources.vector_search_endpoints.*.endpoint_status.message string REMOTE
30433043
resources.vector_search_endpoints.*.endpoint_status.state vectorsearch.EndpointStatusState REMOTE
30443044
resources.vector_search_endpoints.*.endpoint_type vectorsearch.EndpointType ALL
3045-
resources.vector_search_endpoints.*.endpoint_uuid string REMOTE STATE
3045+
resources.vector_search_endpoints.*.endpoint_uuid string REMOTE
30463046
resources.vector_search_endpoints.*.id string INPUT REMOTE
30473047
resources.vector_search_endpoints.*.last_updated_timestamp int64 REMOTE
30483048
resources.vector_search_endpoints.*.last_updated_user string REMOTE

acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33
"resources.vector_search_endpoints.my_endpoint": {
44
"action": "update",
55
"changes": {
6-
"endpoint_uuid": {
7-
"action": "skip",
8-
"reason": "custom",
9-
"old": "[MY_ENDPOINT_UUID]",
10-
"remote": "[MY_ENDPOINT_UUID]"
11-
},
126
"min_qps": {
137
"action": "update",
148
"old": 1,

acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Deployment complete!
1515
"state": "ONLINE"
1616
},
1717
"endpoint_type": "STANDARD",
18-
"id": "[MY_ENDPOINT_UUID]",
18+
"id": "[UUID]",
1919
"last_updated_timestamp": [UNIX_TIME_MILLIS][1],
2020
"last_updated_user": "[USERNAME]",
2121
"name": "vs-endpoint-[UNIQUE_NAME]",

acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ trace $CLI bundle deploy
1111

1212
endpoint_name="vs-endpoint-${UNIQUE_NAME}"
1313

14-
# Register a stable label for the endpoint UUID so the plan output shows the
15-
# same token for both saved (old) and remote, confirming they match.
16-
endpoint_uuid=$($CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq -r '.id')
17-
add_repl.py "$endpoint_uuid" "MY_ENDPOINT_UUID"
18-
1914
title "Simulate remote drift: change min_qps to 5 outside the bundle"
2015
trace $CLI vector-search-endpoints patch-endpoint "${endpoint_name}" --min-qps 5
2116

acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ Deployment complete!
1313
"endpoint_type": "STANDARD"
1414
}
1515

16-
>>> print_state.py
17-
"/vector-search-endpoints/[ORIGINAL_ENDPOINT_UUID]"
18-
1916
=== Delete and recreate remotely with the same name
2017
>>> [CLI] vector-search-endpoints delete-endpoint vs-endpoint-[UNIQUE_NAME]
2118

@@ -35,14 +32,12 @@ Deployment complete!
3532
Original endpoint UUID: [ORIGINAL_ENDPOINT_UUID]
3633
Remote recreated endpoint UUID: [REMOTE_RECREATED_ENDPOINT_UUID]
3734

38-
=== Plan detects the UUID change and proposes recreate
35+
=== Plan after out-of-band recreate
3936
>>> [CLI] bundle plan
40-
recreate vector_search_endpoints.my_endpoint
4137
create vector_search_endpoints.my_endpoint.permissions
4238

43-
Plan: 2 to add, 0 to change, 1 to delete, 0 unchanged
39+
Plan: 1 to add, 0 to change, 0 to delete, 1 unchanged
4440

45-
=== Deploy recreates the endpoint and rebinds permissions to the new UUID
4641
>>> [CLI] bundle deploy
4742
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-recreated-same-name-[UNIQUE_NAME]/default/files...
4843
Deploying resources...
@@ -51,12 +46,14 @@ Deployment complete!
5146

5247
>>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME]
5348
{
49+
"id": "[REMOTE_RECREATED_ENDPOINT_UUID]",
5450
"name": "vs-endpoint-[UNIQUE_NAME]",
5551
"endpoint_type": "STANDARD"
5652
}
5753

58-
>>> print_state.py
59-
"/vector-search-endpoints/[UUID]"
54+
=== Verify no permanent drift after deploy
55+
>>> [CLI] bundle plan
56+
Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
6057

6158
>>> [CLI] bundle destroy --auto-approve
6259
The following resources will be deleted:

acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ trace $CLI bundle deploy
1414
original_endpoint_uuid=$($CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq -r '.id')
1515
add_repl.py "$original_endpoint_uuid" "ORIGINAL_ENDPOINT_UUID"
1616
trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{id, name, endpoint_type}'
17-
trace print_state.py | jq '.state."resources.vector_search_endpoints.my_endpoint.permissions".state.object_id'
1817

1918
title "Delete and recreate remotely with the same name"
2019
trace $CLI vector-search-endpoints delete-endpoint "${endpoint_name}"
@@ -32,10 +31,11 @@ if [ "$original_endpoint_uuid" = "$remote_recreated_endpoint_uuid" ]; then
3231
exit 1
3332
fi
3433

35-
title "Plan detects the UUID change and proposes recreate"
36-
trace $CLI bundle plan | contains.py "recreate vector_search_endpoints.my_endpoint" "create vector_search_endpoints.my_endpoint.permissions"
34+
title "Plan after out-of-band recreate"
35+
trace $CLI bundle plan
3736

38-
title "Deploy recreates the endpoint and rebinds permissions to the new UUID"
3937
trace $CLI bundle deploy
40-
trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{name, endpoint_type}'
41-
trace print_state.py | jq '.state."resources.vector_search_endpoints.my_endpoint.permissions".state.object_id'
38+
trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{id, name, endpoint_type}'
39+
40+
title "Verify no permanent drift after deploy"
41+
trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete"

bundle/direct/dresources/vector_search_endpoint.go

Lines changed: 14 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ import (
55
"time"
66

77
"github.com/databricks/cli/bundle/config/resources"
8-
"github.com/databricks/cli/bundle/deployplan"
98
"github.com/databricks/cli/libs/structs/structpath"
109
"github.com/databricks/cli/libs/utils"
1110
"github.com/databricks/databricks-sdk-go"
12-
"github.com/databricks/databricks-sdk-go/marshal"
1311
"github.com/databricks/databricks-sdk-go/service/vectorsearch"
1412
)
1513

@@ -18,23 +16,6 @@ var (
1816
pathMinQps = structpath.MustParsePath("min_qps")
1917
)
2018

21-
// VectorSearchEndpointState is persisted in deployment state. endpoint_uuid is
22-
// tracked so out-of-band replacement of an endpoint with the same name can be
23-
// detected: when saved UUID differs from remote UUID, the endpoint is recreated.
24-
type VectorSearchEndpointState struct {
25-
vectorsearch.CreateEndpoint
26-
EndpointUuid string `json:"endpoint_uuid,omitempty"`
27-
}
28-
29-
// Custom marshalers required because embedded CreateEndpoint has its own.
30-
func (s *VectorSearchEndpointState) UnmarshalJSON(b []byte) error {
31-
return marshal.Unmarshal(b, s)
32-
}
33-
34-
func (s VectorSearchEndpointState) MarshalJSON() ([]byte, error) {
35-
return marshal.Marshal(s)
36-
}
37-
3819
// VectorSearchEndpointRemote is remote state for a vector search endpoint. It embeds API response
3920
// fields for drift comparison and adds endpoint_uuid for permissions; deployment state id remains the endpoint name.
4021
type VectorSearchEndpointRemote struct {
@@ -60,28 +41,22 @@ func (*ResourceVectorSearchEndpoint) New(client *databricks.WorkspaceClient) *Re
6041
return &ResourceVectorSearchEndpoint{client: client}
6142
}
6243

63-
func (*ResourceVectorSearchEndpoint) PrepareState(input *resources.VectorSearchEndpoint) *VectorSearchEndpointState {
64-
return &VectorSearchEndpointState{
65-
CreateEndpoint: input.CreateEndpoint,
66-
EndpointUuid: "",
67-
}
44+
func (*ResourceVectorSearchEndpoint) PrepareState(input *resources.VectorSearchEndpoint) *vectorsearch.CreateEndpoint {
45+
return &input.CreateEndpoint
6846
}
6947

70-
func (*ResourceVectorSearchEndpoint) RemapState(remote *VectorSearchEndpointRemote) *VectorSearchEndpointState {
48+
func (*ResourceVectorSearchEndpoint) RemapState(remote *VectorSearchEndpointRemote) *vectorsearch.CreateEndpoint {
7149
var minQps int64
7250
if remote.ScalingInfo != nil {
7351
minQps = remote.ScalingInfo.RequestedMinQps
7452
}
75-
return &VectorSearchEndpointState{
76-
CreateEndpoint: vectorsearch.CreateEndpoint{
77-
Name: remote.Name,
78-
EndpointType: remote.EndpointType,
79-
BudgetPolicyId: remote.BudgetPolicyId,
80-
UsagePolicyId: "", // Missing in remote
81-
MinQps: minQps,
82-
ForceSendFields: utils.FilterFields[vectorsearch.CreateEndpoint](remote.ForceSendFields, "UsagePolicyId"),
83-
},
84-
EndpointUuid: remote.EndpointUuid,
53+
return &vectorsearch.CreateEndpoint{
54+
Name: remote.Name,
55+
EndpointType: remote.EndpointType,
56+
BudgetPolicyId: remote.BudgetPolicyId,
57+
UsagePolicyId: "", // Missing in remote
58+
MinQps: minQps,
59+
ForceSendFields: utils.FilterFields[vectorsearch.CreateEndpoint](remote.ForceSendFields, "UsagePolicyId"),
8560
}
8661
}
8762

@@ -93,27 +68,24 @@ func (r *ResourceVectorSearchEndpoint) DoRead(ctx context.Context, id string) (*
9368
return newVectorSearchEndpointRemote(info), nil
9469
}
9570

96-
func (r *ResourceVectorSearchEndpoint) DoCreate(ctx context.Context, config *VectorSearchEndpointState) (string, *VectorSearchEndpointRemote, error) {
97-
waiter, err := r.client.VectorSearchEndpoints.CreateEndpoint(ctx, config.CreateEndpoint)
71+
func (r *ResourceVectorSearchEndpoint) DoCreate(ctx context.Context, config *vectorsearch.CreateEndpoint) (string, *VectorSearchEndpointRemote, error) {
72+
waiter, err := r.client.VectorSearchEndpoints.CreateEndpoint(ctx, *config)
9873
if err != nil {
9974
return "", nil, err
10075
}
10176
id := config.Name
102-
if waiter.Response != nil {
103-
config.EndpointUuid = waiter.Response.Id
104-
}
10577
return id, newVectorSearchEndpointRemote(waiter.Response), nil
10678
}
10779

108-
func (r *ResourceVectorSearchEndpoint) WaitAfterCreate(ctx context.Context, config *VectorSearchEndpointState) (*VectorSearchEndpointRemote, error) {
80+
func (r *ResourceVectorSearchEndpoint) WaitAfterCreate(ctx context.Context, config *vectorsearch.CreateEndpoint) (*VectorSearchEndpointRemote, error) {
10981
info, err := r.client.VectorSearchEndpoints.WaitGetEndpointVectorSearchEndpointOnline(ctx, config.Name, 60*time.Minute, nil)
11082
if err != nil {
11183
return nil, err
11284
}
11385
return newVectorSearchEndpointRemote(info), nil
11486
}
11587

116-
func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string, config *VectorSearchEndpointState, entry *PlanEntry) (*VectorSearchEndpointRemote, error) {
88+
func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string, config *vectorsearch.CreateEndpoint, entry *PlanEntry) (*VectorSearchEndpointRemote, error) {
11789
if entry.Changes.HasChange(pathBudgetPolicyId) {
11890
_, err := r.client.VectorSearchEndpoints.UpdateEndpointBudgetPolicy(ctx, vectorsearch.PatchEndpointBudgetPolicyRequest{
11991
EndpointName: id,
@@ -135,36 +107,9 @@ func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string,
135107
}
136108
}
137109

138-
// Preserve endpoint_uuid in saved state: PrepareState leaves it empty because
139-
// it isn't in config, so copy from remote before SaveState writes newState.
140-
if remote, ok := entry.RemoteState.(*VectorSearchEndpointRemote); ok && remote != nil {
141-
config.EndpointUuid = remote.EndpointUuid
142-
}
143-
144110
return nil, nil
145111
}
146112

147113
func (r *ResourceVectorSearchEndpoint) DoDelete(ctx context.Context, id string) error {
148114
return r.client.VectorSearchEndpoints.DeleteEndpointByEndpointName(ctx, id)
149115
}
150-
151-
// OverrideChangeDesc classifies endpoint_uuid drift: Recreate when saved UUID
152-
// differs from remote (endpoint replaced out-of-band), Skip otherwise. The
153-
// field is not in config, so a synthetic diff between saved state and an empty
154-
// newState is expected on every plan.
155-
func (*ResourceVectorSearchEndpoint) OverrideChangeDesc(_ context.Context, path *structpath.PathNode, change *ChangeDesc, remote *VectorSearchEndpointRemote) error {
156-
if path.String() != "endpoint_uuid" {
157-
return nil
158-
}
159-
savedUuid, _ := change.Old.(string)
160-
var remoteUuid string
161-
if remote != nil {
162-
remoteUuid = remote.EndpointUuid
163-
}
164-
if savedUuid != "" && remoteUuid != "" && savedUuid != remoteUuid {
165-
change.Action = deployplan.Recreate
166-
} else {
167-
change.Action = deployplan.Skip
168-
}
169-
return nil
170-
}

0 commit comments

Comments
 (0)