From c16934f48a64cd99ac4dad937c8861ea15ab0f63 Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Wed, 29 Apr 2026 16:44:05 +0200 Subject: [PATCH 1/3] direct: persist endpoint UUID and ignore budget_policy_id drift on vector_search_endpoints Two fixes that together make `vector_search_endpoints` plan/deploy correctly across out-of-band changes the user can trigger from the console. 1) endpoint UUID drift detection. The endpoint name is stable but its UUID changes if the endpoint is deleted and recreated with the same name. Without persisting that UUID the planner couldn't detect: - the endpoint itself being replaced out-of-band (permissions silently rebound to a different backing endpoint); - any caller that depends on endpoint_uuid (e.g. permissions object_id) racing the recreate. VectorSearchEndpointState now embeds CreateEndpoint and adds EndpointUuid. DoCreate records the UUID from the create response; DoUpdate copies it from entry.RemoteState so unrelated updates (e.g. min_qps) don't blank it out. OverrideChangeDesc classifies endpoint_uuid drift as Recreate when saved != remote, Skip otherwise. drift/recreated_same_name flips from a "badness snapshot" to the recreate behavior, with a permissions block on the endpoint to verify the cascade rebinds correctly. 2) ignore_remote_changes for endpoint.budget_policy_id. The API returns effective_budget_policy_id on Get, which folds in workspace-inherited policy. That value rarely matches the user-set budget_policy_id, so every plan was seeing drift on a field the user never touched. drift/budget_policy now asserts the field is correctly ignored. drift/min_qps/out.plan.direct.json regenerates to include the new endpoint_uuid skip entry in the detailed plan. Co-authored-by: Isaac --- .../drift/budget_policy/output.txt | 6 +- .../drift/budget_policy/script | 4 +- .../drift/min_qps/out.plan.direct.json | 6 ++ .../recreated_same_name/databricks.yml.tmpl | 3 + .../drift/recreated_same_name/output.txt | 15 +++- .../drift/recreated_same_name/script | 9 +- bundle/direct/dresources/resources.yml | 5 ++ .../dresources/vector_search_endpoint.go | 85 ++++++++++++++++--- 8 files changed, 107 insertions(+), 26 deletions(-) diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt index 02f23f3f9a3..028acd55791 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt @@ -12,11 +12,9 @@ Deployment complete! "effective_budget_policy_id":"remote-policy" } -=== Plan detects drift and proposes update +=== budget_policy_id drift is ignored (effective vs requested mismatch) >>> [CLI] bundle plan -update vector_search_endpoints.my_endpoint - -Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script index c02467d6528..40328247a30 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script @@ -14,5 +14,5 @@ endpoint_name="vs-endpoint-${UNIQUE_NAME}" title "Simulate remote drift: set budget_policy_id outside the bundle" trace $CLI vector-search-endpoints update-endpoint-budget-policy "${endpoint_name}" "remote-policy" -title "Plan detects drift and proposes update" -trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged" +title "budget_policy_id drift is ignored (effective vs requested mismatch)" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged" diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json index 93aa4f1a24d..c26e16ada67 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json @@ -3,6 +3,12 @@ "resources.vector_search_endpoints.my_endpoint": { "action": "update", "changes": { + "endpoint_uuid": { + "action": "skip", + "reason": "state-only field", + "old": "[UUID]", + "remote": "[UUID]" + }, "min_qps": { "action": "update", "old": 1, diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/databricks.yml.tmpl index 914f4af6e3d..b67caeebad6 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/databricks.yml.tmpl +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/databricks.yml.tmpl @@ -9,3 +9,6 @@ resources: my_endpoint: name: vs-endpoint-$UNIQUE_NAME endpoint_type: STANDARD + permissions: + - level: CAN_USE + group_name: admins diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt index 0da720312a1..08afd3157e1 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt @@ -13,6 +13,9 @@ Deployment complete! "endpoint_type": "STANDARD" } +>>> print_state.py +"/vector-search-endpoints/[ORIGINAL_ENDPOINT_UUID]" + === Delete and recreate remotely with the same name >>> [CLI] vector-search-endpoints delete-endpoint vs-endpoint-[UNIQUE_NAME] @@ -32,10 +35,14 @@ Deployment complete! Original endpoint UUID: [ORIGINAL_ENDPOINT_UUID] Remote recreated endpoint UUID: [REMOTE_RECREATED_ENDPOINT_UUID] -=== Badness: bundle should recreate after remote replacement, but currently sees no drift +=== Plan detects the UUID change and proposes recreate >>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged +recreate vector_search_endpoints.my_endpoint +update vector_search_endpoints.my_endpoint.permissions + +Plan: 1 to add, 1 to change, 1 to delete, 0 unchanged +=== Deploy recreates the endpoint and rebinds permissions to the new UUID >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-recreated-same-name-[UNIQUE_NAME]/default/files... Deploying resources... @@ -44,11 +51,13 @@ Deployment complete! >>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME] { - "id": "[REMOTE_RECREATED_ENDPOINT_UUID]", "name": "vs-endpoint-[UNIQUE_NAME]", "endpoint_type": "STANDARD" } +>>> print_state.py +"/vector-search-endpoints/[UUID]" + >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: delete resources.vector_search_endpoints.my_endpoint diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script index 48de644a9a5..0a17aa3152a 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script @@ -14,6 +14,7 @@ trace $CLI bundle deploy original_endpoint_uuid=$($CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq -r '.id') add_repl.py "$original_endpoint_uuid" "ORIGINAL_ENDPOINT_UUID" trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{id, name, endpoint_type}' +trace print_state.py | jq '.state."resources.vector_search_endpoints.my_endpoint.permissions".state.object_id' title "Delete and recreate remotely with the same name" trace $CLI vector-search-endpoints delete-endpoint "${endpoint_name}" @@ -31,8 +32,10 @@ if [ "$original_endpoint_uuid" = "$remote_recreated_endpoint_uuid" ]; then exit 1 fi -title "Badness: bundle should recreate after remote replacement, but currently sees no drift" -trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged" +title "Plan detects the UUID change and proposes recreate" +trace $CLI bundle plan | contains.py "recreate vector_search_endpoints.my_endpoint" "update vector_search_endpoints.my_endpoint.permissions" +title "Deploy recreates the endpoint and rebinds permissions to the new UUID" trace $CLI bundle deploy -trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{id, name, endpoint_type}' +trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{name, endpoint_type}' +trace print_state.py | jq '.state."resources.vector_search_endpoints.my_endpoint.permissions".state.object_id' diff --git a/bundle/direct/dresources/resources.yml b/bundle/direct/dresources/resources.yml index 569fca9ee82..06f4c10a273 100644 --- a/bundle/direct/dresources/resources.yml +++ b/bundle/direct/dresources/resources.yml @@ -512,3 +512,8 @@ resources: recreate_on_changes: - field: endpoint_type reason: immutable + ignore_remote_changes: + # The API returns effective_budget_policy_id which may include inherited workspace policies, + # not the user-set budget_policy_id. Ignore until the API exposes the user-set value directly. + - field: budget_policy_id + reason: effective_vs_requested diff --git a/bundle/direct/dresources/vector_search_endpoint.go b/bundle/direct/dresources/vector_search_endpoint.go index 24bbd1a6e74..8b06a9db01b 100644 --- a/bundle/direct/dresources/vector_search_endpoint.go +++ b/bundle/direct/dresources/vector_search_endpoint.go @@ -5,9 +5,11 @@ import ( "time" "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/utils" "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/vectorsearch" ) @@ -16,6 +18,23 @@ var ( pathMinQps = structpath.MustParsePath("min_qps") ) +// VectorSearchEndpointState is persisted in deployment state. endpoint_uuid is +// tracked so out-of-band replacement of an endpoint with the same name can be +// detected: when saved UUID differs from remote UUID, the endpoint is recreated. +type VectorSearchEndpointState struct { + vectorsearch.CreateEndpoint + EndpointUuid string `json:"endpoint_uuid,omitempty"` +} + +// Custom marshalers required because embedded CreateEndpoint has its own. +func (s *VectorSearchEndpointState) UnmarshalJSON(b []byte) error { + return marshal.Unmarshal(b, s) +} + +func (s VectorSearchEndpointState) MarshalJSON() ([]byte, error) { + return marshal.Marshal(s) +} + // VectorSearchEndpointRemote is remote state for a vector search endpoint. It embeds API response // fields for drift comparison and adds endpoint_uuid for permissions; deployment state id remains the endpoint name. type VectorSearchEndpointRemote struct { @@ -41,22 +60,28 @@ func (*ResourceVectorSearchEndpoint) New(client *databricks.WorkspaceClient) *Re return &ResourceVectorSearchEndpoint{client: client} } -func (*ResourceVectorSearchEndpoint) PrepareState(input *resources.VectorSearchEndpoint) *vectorsearch.CreateEndpoint { - return &input.CreateEndpoint +func (*ResourceVectorSearchEndpoint) PrepareState(input *resources.VectorSearchEndpoint) *VectorSearchEndpointState { + return &VectorSearchEndpointState{ + CreateEndpoint: input.CreateEndpoint, + EndpointUuid: "", + } } -func (*ResourceVectorSearchEndpoint) RemapState(remote *VectorSearchEndpointRemote) *vectorsearch.CreateEndpoint { +func (*ResourceVectorSearchEndpoint) RemapState(remote *VectorSearchEndpointRemote) *VectorSearchEndpointState { var minQps int64 if remote.ScalingInfo != nil { minQps = remote.ScalingInfo.RequestedMinQps } - return &vectorsearch.CreateEndpoint{ - Name: remote.Name, - EndpointType: remote.EndpointType, - BudgetPolicyId: remote.BudgetPolicyId, - UsagePolicyId: "", // Missing in remote - MinQps: minQps, - ForceSendFields: utils.FilterFields[vectorsearch.CreateEndpoint](remote.ForceSendFields, "UsagePolicyId"), + return &VectorSearchEndpointState{ + CreateEndpoint: vectorsearch.CreateEndpoint{ + Name: remote.Name, + EndpointType: remote.EndpointType, + BudgetPolicyId: remote.BudgetPolicyId, + UsagePolicyId: "", // Missing in remote + MinQps: minQps, + ForceSendFields: utils.FilterFields[vectorsearch.CreateEndpoint](remote.ForceSendFields, "UsagePolicyId"), + }, + EndpointUuid: remote.EndpointUuid, } } @@ -68,16 +93,19 @@ func (r *ResourceVectorSearchEndpoint) DoRead(ctx context.Context, id string) (* return newVectorSearchEndpointRemote(info), nil } -func (r *ResourceVectorSearchEndpoint) DoCreate(ctx context.Context, config *vectorsearch.CreateEndpoint) (string, *VectorSearchEndpointRemote, error) { - waiter, err := r.client.VectorSearchEndpoints.CreateEndpoint(ctx, *config) +func (r *ResourceVectorSearchEndpoint) DoCreate(ctx context.Context, config *VectorSearchEndpointState) (string, *VectorSearchEndpointRemote, error) { + waiter, err := r.client.VectorSearchEndpoints.CreateEndpoint(ctx, config.CreateEndpoint) if err != nil { return "", nil, err } id := config.Name + if waiter.Response != nil { + config.EndpointUuid = waiter.Response.Id + } return id, newVectorSearchEndpointRemote(waiter.Response), nil } -func (r *ResourceVectorSearchEndpoint) WaitAfterCreate(ctx context.Context, config *vectorsearch.CreateEndpoint) (*VectorSearchEndpointRemote, error) { +func (r *ResourceVectorSearchEndpoint) WaitAfterCreate(ctx context.Context, config *VectorSearchEndpointState) (*VectorSearchEndpointRemote, error) { info, err := r.client.VectorSearchEndpoints.WaitGetEndpointVectorSearchEndpointOnline(ctx, config.Name, 60*time.Minute, nil) if err != nil { return nil, err @@ -85,7 +113,7 @@ func (r *ResourceVectorSearchEndpoint) WaitAfterCreate(ctx context.Context, conf return newVectorSearchEndpointRemote(info), nil } -func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string, config *vectorsearch.CreateEndpoint, entry *PlanEntry) (*VectorSearchEndpointRemote, error) { +func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string, config *VectorSearchEndpointState, entry *PlanEntry) (*VectorSearchEndpointRemote, error) { if entry.Changes.HasChange(pathBudgetPolicyId) { _, err := r.client.VectorSearchEndpoints.UpdateEndpointBudgetPolicy(ctx, vectorsearch.PatchEndpointBudgetPolicyRequest{ EndpointName: id, @@ -107,9 +135,38 @@ func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string, } } + // Preserve endpoint_uuid in saved state: PrepareState leaves it empty because + // it isn't in config, so copy from remote before SaveState writes newState. + if remote, ok := entry.RemoteState.(*VectorSearchEndpointRemote); ok && remote != nil { + config.EndpointUuid = remote.EndpointUuid + } + return nil, nil } func (r *ResourceVectorSearchEndpoint) DoDelete(ctx context.Context, id string) error { return r.client.VectorSearchEndpoints.DeleteEndpointByEndpointName(ctx, id) } + +// OverrideChangeDesc classifies endpoint_uuid drift: Recreate when saved UUID +// differs from remote (endpoint replaced out-of-band), Skip otherwise. This +// field is not in config, so a synthetic diff between saved state and an empty +// newState is expected on every plan. +func (*ResourceVectorSearchEndpoint) OverrideChangeDesc(_ context.Context, path *structpath.PathNode, change *ChangeDesc, remote *VectorSearchEndpointRemote) error { + if path.String() != "endpoint_uuid" { + return nil + } + savedUuid, _ := change.Old.(string) + var remoteUuid string + if remote != nil { + remoteUuid = remote.EndpointUuid + } + if savedUuid != "" && remoteUuid != "" && savedUuid != remoteUuid { + change.Action = deployplan.Recreate + change.Reason = "endpoint replaced out-of-band" + } else { + change.Action = deployplan.Skip + change.Reason = "state-only field" + } + return nil +} From d86e10dfdbe933d41490fb259e6aba8c809d3681 Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Wed, 29 Apr 2026 16:55:23 +0200 Subject: [PATCH 2/3] Revert budget_policy_id ignore from this PR Will be handled in an SDK update. Keeping this PR focused on the endpoint UUID drift fix. Co-authored-by: Isaac --- .../vector_search_endpoints/drift/budget_policy/output.txt | 6 ++++-- .../vector_search_endpoints/drift/budget_policy/script | 4 ++-- bundle/direct/dresources/resources.yml | 5 ----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt index 028acd55791..02f23f3f9a3 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt @@ -12,9 +12,11 @@ Deployment complete! "effective_budget_policy_id":"remote-policy" } -=== budget_policy_id drift is ignored (effective vs requested mismatch) +=== Plan detects drift and proposes update >>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged +update vector_search_endpoints.my_endpoint + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script index 40328247a30..c02467d6528 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script @@ -14,5 +14,5 @@ endpoint_name="vs-endpoint-${UNIQUE_NAME}" title "Simulate remote drift: set budget_policy_id outside the bundle" trace $CLI vector-search-endpoints update-endpoint-budget-policy "${endpoint_name}" "remote-policy" -title "budget_policy_id drift is ignored (effective vs requested mismatch)" -trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged" +title "Plan detects drift and proposes update" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged" diff --git a/bundle/direct/dresources/resources.yml b/bundle/direct/dresources/resources.yml index 06f4c10a273..569fca9ee82 100644 --- a/bundle/direct/dresources/resources.yml +++ b/bundle/direct/dresources/resources.yml @@ -512,8 +512,3 @@ resources: recreate_on_changes: - field: endpoint_type reason: immutable - ignore_remote_changes: - # The API returns effective_budget_policy_id which may include inherited workspace policies, - # not the user-set budget_policy_id. Ignore until the API exposes the user-set value directly. - - field: budget_policy_id - reason: effective_vs_requested From 268950903b35a0e42d4937de72a54ea621a4a8b6 Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Wed, 29 Apr 2026 18:16:46 +0200 Subject: [PATCH 3/3] direct: address review comments on vector_search_endpoint endpoint_uuid Drop custom Reason strings from OverrideChangeDesc and let the framework default to ReasonCustom when the action is changed (matches dashboard.etag). Register MY_ENDPOINT_UUID via add_repl.py in the min_qps drift test so the plan output shows both old and remote rendering as the same labeled token, proving they're the same UUID rather than two arbitrary UUIDs both masked to [UUID] by the generic regex. Regenerate refschema out.fields.txt to include the new endpoint_uuid STATE classification. Co-authored-by: Isaac --- acceptance/bundle/refschema/out.fields.txt | 2 +- .../drift/min_qps/out.plan.direct.json | 6 +++--- .../vector_search_endpoints/drift/min_qps/output.txt | 2 +- .../resources/vector_search_endpoints/drift/min_qps/script | 5 +++++ bundle/direct/dresources/vector_search_endpoint.go | 4 +--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/acceptance/bundle/refschema/out.fields.txt b/acceptance/bundle/refschema/out.fields.txt index c79b0d3533c..5a55ba006ee 100644 --- a/acceptance/bundle/refschema/out.fields.txt +++ b/acceptance/bundle/refschema/out.fields.txt @@ -3042,7 +3042,7 @@ resources.vector_search_endpoints.*.endpoint_status *vectorsearch.EndpointStatus resources.vector_search_endpoints.*.endpoint_status.message string REMOTE resources.vector_search_endpoints.*.endpoint_status.state vectorsearch.EndpointStatusState REMOTE resources.vector_search_endpoints.*.endpoint_type vectorsearch.EndpointType ALL -resources.vector_search_endpoints.*.endpoint_uuid string REMOTE +resources.vector_search_endpoints.*.endpoint_uuid string REMOTE STATE resources.vector_search_endpoints.*.id string INPUT REMOTE resources.vector_search_endpoints.*.last_updated_timestamp int64 REMOTE resources.vector_search_endpoints.*.last_updated_user string REMOTE diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json index c26e16ada67..dbd1364b122 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json @@ -5,9 +5,9 @@ "changes": { "endpoint_uuid": { "action": "skip", - "reason": "state-only field", - "old": "[UUID]", - "remote": "[UUID]" + "reason": "custom", + "old": "[MY_ENDPOINT_UUID]", + "remote": "[MY_ENDPOINT_UUID]" }, "min_qps": { "action": "update", diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt index 294d7061a4e..9f8c49adba5 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt @@ -15,7 +15,7 @@ Deployment complete! "state":"ONLINE" }, "endpoint_type":"STANDARD", - "id":"[UUID]", + "id":"[MY_ENDPOINT_UUID]", "last_updated_timestamp":[UNIX_TIME_MILLIS][1], "last_updated_user":"[USERNAME]", "name":"vs-endpoint-[UNIQUE_NAME]", diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script index 81e86fefcb2..3c2062e4747 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script @@ -11,6 +11,11 @@ trace $CLI bundle deploy endpoint_name="vs-endpoint-${UNIQUE_NAME}" +# Register a stable label for the endpoint UUID so the plan output shows the +# same token for both saved (old) and remote, confirming they match. +endpoint_uuid=$($CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq -r '.id') +add_repl.py "$endpoint_uuid" "MY_ENDPOINT_UUID" + title "Simulate remote drift: change min_qps to 5 outside the bundle" trace $CLI vector-search-endpoints patch-endpoint "${endpoint_name}" --min-qps 5 diff --git a/bundle/direct/dresources/vector_search_endpoint.go b/bundle/direct/dresources/vector_search_endpoint.go index 8b06a9db01b..1322f474a15 100644 --- a/bundle/direct/dresources/vector_search_endpoint.go +++ b/bundle/direct/dresources/vector_search_endpoint.go @@ -149,7 +149,7 @@ func (r *ResourceVectorSearchEndpoint) DoDelete(ctx context.Context, id string) } // OverrideChangeDesc classifies endpoint_uuid drift: Recreate when saved UUID -// differs from remote (endpoint replaced out-of-band), Skip otherwise. This +// differs from remote (endpoint replaced out-of-band), Skip otherwise. The // field is not in config, so a synthetic diff between saved state and an empty // newState is expected on every plan. func (*ResourceVectorSearchEndpoint) OverrideChangeDesc(_ context.Context, path *structpath.PathNode, change *ChangeDesc, remote *VectorSearchEndpointRemote) error { @@ -163,10 +163,8 @@ func (*ResourceVectorSearchEndpoint) OverrideChangeDesc(_ context.Context, path } if savedUuid != "" && remoteUuid != "" && savedUuid != remoteUuid { change.Action = deployplan.Recreate - change.Reason = "endpoint replaced out-of-band" } else { change.Action = deployplan.Skip - change.Reason = "state-only field" } return nil }