From d4c31b6c64043f6633aedfddf20b6b02efe4a7a2 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Wed, 8 Jul 2026 12:55:43 +0000 Subject: [PATCH 1/4] direct: stop SQL warehouse after edit when lifecycle.started=false Editing a warehouse restarts it (the Edit API returns a WaitGetWarehouseRunning waiter), so DoUpdate must reconcile the started/stopped decision from RUNNING rather than the stale pre-plan state. Without this a redeploy with lifecycle.started=false hung waiting for STOPPED because no Stop was ever issued after the Edit. Adds an acceptance test covering the edit + started=false path. --- .../databricks.yml.tmpl | 14 ++++ .../lifecycle-started-edit/out.test.toml | 4 ++ .../lifecycle-started-edit/output.txt | 67 +++++++++++++++++++ .../lifecycle-started-edit/script | 24 +++++++ .../lifecycle-started-edit/test.toml | 11 +++ bundle/direct/dresources/sql_warehouse.go | 8 ++- 6 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/databricks.yml.tmpl create mode 100644 acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml create mode 100644 acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/output.txt create mode 100644 acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/script create mode 100644 acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/test.toml diff --git a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/databricks.yml.tmpl b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/databricks.yml.tmpl new file mode 100644 index 00000000000..ce2db8adb30 --- /dev/null +++ b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/databricks.yml.tmpl @@ -0,0 +1,14 @@ +bundle: + name: lifecycle-started-edit-$UNIQUE_NAME + +workspace: + root_path: ~/.bundle/$UNIQUE_NAME + +resources: + sql_warehouses: + mywarehouse: + name: $UNIQUE_NAME + cluster_size: "2X-Small" + auto_stop_mins: 10 + lifecycle: + started: false diff --git a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml new file mode 100644 index 00000000000..d0abd00ab97 --- /dev/null +++ b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +CloudSlow = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/output.txt b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/output.txt new file mode 100644 index 00000000000..4379e7528b8 --- /dev/null +++ b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/output.txt @@ -0,0 +1,67 @@ + +=== Deploy with started=false: warehouse created and then stopped +>>> errcode [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //sql/warehouses +{ + "method": "POST", + "path": "/api/2.0/sql/warehouses", + "body": { + "auto_stop_mins": 10, + "cluster_size": "2X-Small", + "enable_photon": true, + "max_num_clusters": 1, + "name": "[UNIQUE_NAME]", + "spot_instance_policy": "COST_OPTIMIZED" + } +} +{ + "method": "POST", + "path": "/api/2.0/sql/warehouses/[WAREHOUSE_ID]/stop" +} + +>>> errcode [CLI] warehouses get [WAREHOUSE_ID] +"STOPPED" + +=== Edit a field with started=false: Edit restarts the warehouse, so a Stop must follow +>>> update_file.py databricks.yml auto_stop_mins: 10 auto_stop_mins: 20 + +>>> errcode [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //sql/warehouses +{ + "method": "POST", + "path": "/api/2.0/sql/warehouses/[WAREHOUSE_ID]/edit", + "body": { + "auto_stop_mins": 20, + "cluster_size": "2X-Small", + "enable_photon": true, + "max_num_clusters": 1, + "name": "[UNIQUE_NAME]", + "spot_instance_policy": "COST_OPTIMIZED" + } +} +{ + "method": "POST", + "path": "/api/2.0/sql/warehouses/[WAREHOUSE_ID]/stop" +} + +>>> errcode [CLI] warehouses get [WAREHOUSE_ID] +"STOPPED" + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.sql_warehouses.mywarehouse + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME] + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/script b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/script new file mode 100644 index 00000000000..2ec69c28804 --- /dev/null +++ b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/script @@ -0,0 +1,24 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +title "Deploy with started=false: warehouse created and then stopped" +trace errcode $CLI bundle deploy +WAREHOUSE_ID=$($CLI bundle summary -o json | jq -r '.resources.sql_warehouses.mywarehouse.id') +add_repl.py "$WAREHOUSE_ID" "WAREHOUSE_ID" +trace print_requests.py //sql/warehouses +rm -f out.requests.txt +{ trace errcode $CLI warehouses get "$WAREHOUSE_ID" | jq '.state'; } || true + +# Editing a warehouse restarts it, so a started=false config must follow the Edit with a +# Stop; before the fix this redeploy hung waiting for STOPPED. +title "Edit a field with started=false: Edit restarts the warehouse, so a Stop must follow" +trace update_file.py databricks.yml "auto_stop_mins: 10" "auto_stop_mins: 20" +trace errcode $CLI bundle deploy +trace print_requests.py //sql/warehouses +rm -f out.requests.txt +{ trace errcode $CLI warehouses get "$WAREHOUSE_ID" | jq '.state'; } || true diff --git a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/test.toml b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/test.toml new file mode 100644 index 00000000000..57d6adc378c --- /dev/null +++ b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/test.toml @@ -0,0 +1,11 @@ +Local = true +RecordRequests = true + +# Starting warehouses can take a while on infra under load. +# Omit this test to avoid timeouts. +Cloud = false + +Ignore = [".databricks", "databricks.yml"] + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/bundle/direct/dresources/sql_warehouse.go b/bundle/direct/dresources/sql_warehouse.go index 146dee5294d..864ee99e039 100644 --- a/bundle/direct/dresources/sql_warehouse.go +++ b/bundle/direct/dresources/sql_warehouse.go @@ -134,7 +134,8 @@ func hasWarehouseChanges(entry *PlanEntry) bool { // DoUpdate updates the warehouse in place. func (r *ResourceSqlWarehouse) DoUpdate(ctx context.Context, id string, config *SqlWarehouseState, entry *PlanEntry) (*SqlWarehouseRemote, error) { - if hasWarehouseChanges(entry) { + edited := hasWarehouseChanges(entry) + if edited { request := sql.EditWarehouseRequest{ AutoStopMins: config.AutoStopMins, Channel: config.Channel, @@ -169,6 +170,11 @@ func (r *ResourceSqlWarehouse) DoUpdate(ctx context.Context, id string, config * desiredStarted := *config.Lifecycle.Started alreadyRunning := remoteWarehouseIsRunning(entry) + if edited { + // Editing a warehouse restarts it (Edit returns a WaitGetWarehouseRunning + // waiter), so reconcile from RUNNING, not the stale pre-plan state. + alreadyRunning = true + } if desiredStarted && !alreadyRunning { // lifecycle.started=true: fire Start; WaitAfterUpdate polls for RUNNING. _, err := r.client.Warehouses.Start(ctx, sql.StartRequest{Id: id}) From bfe52be732f696815e5418c9e71bc397e59e4894 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Wed, 8 Jul 2026 12:55:55 +0000 Subject: [PATCH 2/4] direct: treat registered_models aliases as input_only so plans converge DoRead fetches with IncludeAliases=false and aliases live on model versions via a separate API, so GET never echoes them back. A config that sets aliases reported a perpetual in-place update and never converged. Classify aliases as ignore_remote_changes/input_only. Adds an acceptance test that deploys a model with aliases, edits the comment, and asserts the re-plan is a no-op. --- .../aliases_converge/databricks.yml.tmpl | 16 ++++++ .../aliases_converge/out.test.toml | 3 + .../aliases_converge/output.txt | 57 +++++++++++++++++++ .../registered_models/aliases_converge/script | 27 +++++++++ .../aliases_converge/test.toml | 5 ++ bundle/direct/dresources/resources.yml | 5 ++ 6 files changed, 113 insertions(+) create mode 100644 acceptance/bundle/resources/registered_models/aliases_converge/databricks.yml.tmpl create mode 100644 acceptance/bundle/resources/registered_models/aliases_converge/out.test.toml create mode 100644 acceptance/bundle/resources/registered_models/aliases_converge/output.txt create mode 100644 acceptance/bundle/resources/registered_models/aliases_converge/script create mode 100644 acceptance/bundle/resources/registered_models/aliases_converge/test.toml diff --git a/acceptance/bundle/resources/registered_models/aliases_converge/databricks.yml.tmpl b/acceptance/bundle/resources/registered_models/aliases_converge/databricks.yml.tmpl new file mode 100644 index 00000000000..43550247edc --- /dev/null +++ b/acceptance/bundle/resources/registered_models/aliases_converge/databricks.yml.tmpl @@ -0,0 +1,16 @@ +bundle: + name: deploy-registered-models-aliases-$UNIQUE_NAME + +resources: + registered_models: + my_registered_model: + name: my-registered-model-aliases-$UNIQUE_NAME + comment: $COMMENT + catalog_name: main + schema_name: default + # Aliases live on model versions and GET does not echo them back + # (DoRead uses IncludeAliases=false), so a config that sets them used to + # report a perpetual in-place update and never converge. + aliases: + - alias_name: champion + id: alias-champion diff --git a/acceptance/bundle/resources/registered_models/aliases_converge/out.test.toml b/acceptance/bundle/resources/registered_models/aliases_converge/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/resources/registered_models/aliases_converge/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/registered_models/aliases_converge/output.txt b/acceptance/bundle/resources/registered_models/aliases_converge/output.txt new file mode 100644 index 00000000000..f1ac429ace8 --- /dev/null +++ b/acceptance/bundle/resources/registered_models/aliases_converge/output.txt @@ -0,0 +1,57 @@ + +>>> export COMMENT=original comment + +=== Initial deployment with aliases set +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-registered-models-aliases-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Plan is a no-op: GET never echoes aliases, so they must not drift +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged + +=== The config-set aliases are skipped as input_only (confirms the matched rule) +>>> [CLI] bundle plan --output json +{ + "action": "skip", + "reason": "input_only", + "old": [ + { + "alias_name": "champion", + "id": "alias-champion" + } + ], + "new": [ + { + "alias_name": "champion", + "id": "alias-champion" + } + ] +} + +=== Edit the comment: an in-place update, not a recreate +>>> [CLI] bundle plan +update registered_models.my_registered_model + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-registered-models-aliases-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Re-plan after the edit converges: no further changes +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.registered_models.my_registered_model + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-registered-models-aliases-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/registered_models/aliases_converge/script b/acceptance/bundle/resources/registered_models/aliases_converge/script new file mode 100644 index 00000000000..ca9dc8f700a --- /dev/null +++ b/acceptance/bundle/resources/registered_models/aliases_converge/script @@ -0,0 +1,27 @@ +echo "*" > .gitignore + +trace export COMMENT="original comment" +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve +} +trap cleanup EXIT + +title "Initial deployment with aliases set" +trace $CLI bundle deploy + +title "Plan is a no-op: GET never echoes aliases, so they must not drift" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged" + +title "The config-set aliases are skipped as input_only (confirms the matched rule)" +trace $CLI bundle plan --output json | jq '.plan[].changes.aliases' + +title "Edit the comment: an in-place update, not a recreate" +export COMMENT="updated comment" +envsubst < databricks.yml.tmpl > databricks.yml +trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete" +trace $CLI bundle deploy + +title "Re-plan after the edit converges: no further changes" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged" diff --git a/acceptance/bundle/resources/registered_models/aliases_converge/test.toml b/acceptance/bundle/resources/registered_models/aliases_converge/test.toml new file mode 100644 index 00000000000..b06ed5ceaab --- /dev/null +++ b/acceptance/bundle/resources/registered_models/aliases_converge/test.toml @@ -0,0 +1,5 @@ +RecordRequests = false + +# The aliases input_only classification lives in the direct engine, so scope +# this regression there. +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/bundle/direct/dresources/resources.yml b/bundle/direct/dresources/resources.yml index e5323c81cb4..e01801393ce 100644 --- a/bundle/direct/dresources/resources.yml +++ b/bundle/direct/dresources/resources.yml @@ -337,6 +337,11 @@ resources: # Backend-computed; the user never sets it. Not annotated output_only in the spec. - field: browse_only reason: output_only + # Aliases are managed on model versions through a separate API, and DoRead + # passes IncludeAliases=false, so GET never echoes them back. Without this a + # config that sets aliases reports a perpetual update (remote stays empty). + - field: aliases + reason: input_only provided_id_fields: # The name can technically be updated without recreate. We recreate for now though # to match TF implementation. From 9b6731c3b9480d48a5c840435e2029daef8ad17e Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 14 Jul 2026 08:52:24 +0000 Subject: [PATCH 3/4] direct: document Edit-restarts behaviour and run warehouse edit test on cloud nightly Cite the SDK contract (Edit waits for RUNNING via WaitGetWarehouseRunning) in the DoUpdate comment and the acceptance script, addressing review feedback asking where the "Edit restarts the warehouse" behaviour is documented. Switch the lifecycle-started-edit test from Cloud=false to CloudSlow=true so it runs on cloud in the nightly matrix, confirming the real Edit behaviour without reintroducing per-PR warehouse-start timeouts. --- .../sql_warehouses/lifecycle-started-edit/out.test.toml | 4 ++-- .../resources/sql_warehouses/lifecycle-started-edit/script | 5 +++-- .../sql_warehouses/lifecycle-started-edit/test.toml | 6 +++--- bundle/direct/dresources/sql_warehouse.go | 5 +++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml index d0abd00ab97..5bbfaf5e65a 100644 --- a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml +++ b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml @@ -1,4 +1,4 @@ Local = true -Cloud = false -CloudSlow = false +Cloud = true +CloudSlow = true EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/script b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/script index 2ec69c28804..3028a1f6afa 100644 --- a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/script +++ b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/script @@ -14,8 +14,9 @@ trace print_requests.py //sql/warehouses rm -f out.requests.txt { trace errcode $CLI warehouses get "$WAREHOUSE_ID" | jq '.state'; } || true -# Editing a warehouse restarts it, so a started=false config must follow the Edit with a -# Stop; before the fix this redeploy hung waiting for STOPPED. +# Editing a warehouse restarts it: Edit's long-running op waits for RUNNING +# (SDK WaitGetWarehouseRunning), so a started=false config must follow the Edit +# with a Stop. Before the fix this redeploy hung waiting for STOPPED. title "Edit a field with started=false: Edit restarts the warehouse, so a Stop must follow" trace update_file.py databricks.yml "auto_stop_mins: 10" "auto_stop_mins: 20" trace errcode $CLI bundle deploy diff --git a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/test.toml b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/test.toml index 57d6adc378c..58e386d22ab 100644 --- a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/test.toml +++ b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/test.toml @@ -1,9 +1,9 @@ Local = true RecordRequests = true -# Starting warehouses can take a while on infra under load. -# Omit this test to avoid timeouts. -Cloud = false +# Starting warehouses is slow, so run on cloud nightly (CloudSlow) instead of every PR +# to confirm the real Edit-restarts behaviour without per-PR timeouts. +CloudSlow = true Ignore = [".databricks", "databricks.yml"] diff --git a/bundle/direct/dresources/sql_warehouse.go b/bundle/direct/dresources/sql_warehouse.go index 864ee99e039..4854e5ed1fb 100644 --- a/bundle/direct/dresources/sql_warehouse.go +++ b/bundle/direct/dresources/sql_warehouse.go @@ -171,8 +171,9 @@ func (r *ResourceSqlWarehouse) DoUpdate(ctx context.Context, id string, config * desiredStarted := *config.Lifecycle.Started alreadyRunning := remoteWarehouseIsRunning(entry) if edited { - // Editing a warehouse restarts it (Edit returns a WaitGetWarehouseRunning - // waiter), so reconcile from RUNNING, not the stale pre-plan state. + // Edit restarts the warehouse: its long-running op waits for RUNNING + // (SDK WaitGetWarehouseRunning), so reconcile from RUNNING, not the + // stale pre-plan state. alreadyRunning = true } if desiredStarted && !alreadyRunning { From 750e7a808c4296051aa5a767b6120114c0d35436 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 14 Jul 2026 09:08:09 +0000 Subject: [PATCH 4/4] acceptance: regenerate out.test.toml with GOOSOnPR after merging main Merging main brought in the newer out.test.toml generator that emits GOOSOnPR.darwin/windows. Regenerate the two test configs added in this PR so the generated-files check passes. --- .../resources/registered_models/aliases_converge/out.test.toml | 2 ++ .../sql_warehouses/lifecycle-started-edit/out.test.toml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/acceptance/bundle/resources/registered_models/aliases_converge/out.test.toml b/acceptance/bundle/resources/registered_models/aliases_converge/out.test.toml index e90b6d5d1ba..1a3e24fa574 100644 --- a/acceptance/bundle/resources/registered_models/aliases_converge/out.test.toml +++ b/acceptance/bundle/resources/registered_models/aliases_converge/out.test.toml @@ -1,3 +1,5 @@ Local = true Cloud = false +GOOSOnPR.darwin = false +GOOSOnPR.windows = false EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml index 5bbfaf5e65a..724dfe98dff 100644 --- a/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml +++ b/acceptance/bundle/resources/sql_warehouses/lifecycle-started-edit/out.test.toml @@ -1,4 +1,6 @@ Local = true Cloud = true CloudSlow = true +GOOSOnPR.darwin = false +GOOSOnPR.windows = false EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]