From e429e52b6f6b0517dc22eb4f1dc0142ea68a5050 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 16 Sep 2026 11:33:26 +0000 Subject: [PATCH 1/5] bundle: upload files after plan approval and DMS version creation --- .nextchanges/bundles/upload-after-plan.md | 1 + .../deploy/wal/failed-plan-no-wal/output.txt | 4 -- .../bind/pipelines/recreate/output.txt | 6 +-- .../bundle/dms/declined-deploy/output.txt | 2 - .../bundle/dms/failed-recreate/output.txt | 2 +- .../operation-upload-fails-once/output.txt | 2 +- .../bundle/dms/successful-recreate/output.txt | 2 +- .../dms/version-never-created/output.txt | 4 -- .../pipelines_recreate/output.txt | 2 +- .../catalogs/auto-approve/output.txt | 2 - .../dashboards/change-parent-path/output.txt | 2 +- .../database_instances/recreate/output.txt | 2 +- .../genie_spaces/delete_warning/output.txt | 2 +- .../pipelines/auto-approve/output.txt | 2 - .../change-ingestion-definition/output.txt | 2 +- .../recreate-keys/change-storage/output.txt | 2 +- .../resources/pipelines/recreate/output.txt | 2 - .../postgres_branches/recreate/output.txt | 2 +- .../recreate_source_branch_time/output.txt | 2 +- .../postgres_databases/recreate/output.txt | 4 +- .../postgres_projects/recreate/output.txt | 2 +- .../resources/schemas/auto-approve/output.txt | 2 - .../resources/schemas/recreate/output.txt | 2 +- .../recreate/output.txt | 2 +- .../drift/orphaned_endpoint/output.txt | 2 +- .../recreate/embedding_dimension/output.txt | 2 +- .../recreate/pending_deletion/output.txt | 2 +- .../recreate/with_endpoint/output.txt | 2 +- .../volumes/change-schema-name/output.txt | 2 +- .../resources/volumes/recreate/output.txt | 2 - bundle/phases/deploy.go | 51 ++++++++++--------- 31 files changed, 50 insertions(+), 70 deletions(-) create mode 100644 .nextchanges/bundles/upload-after-plan.md diff --git a/.nextchanges/bundles/upload-after-plan.md b/.nextchanges/bundles/upload-after-plan.md new file mode 100644 index 00000000000..447d94fd791 --- /dev/null +++ b/.nextchanges/bundles/upload-after-plan.md @@ -0,0 +1 @@ +* Delay bundle file and artifact uploads until planning, approval, and any DMS version creation succeed. diff --git a/acceptance/bundle/deploy/wal/failed-plan-no-wal/output.txt b/acceptance/bundle/deploy/wal/failed-plan-no-wal/output.txt index 194a223a02c..5072482478a 100644 --- a/acceptance/bundle/deploy/wal/failed-plan-no-wal/output.txt +++ b/acceptance/bundle/deploy/wal/failed-plan-no-wal/output.txt @@ -8,7 +8,6 @@ Resources: 1 created, 0 changed, 0 deleted, 0 unchanged === Deploy 2 (planning fails, must not leave a WAL) >>> errcode [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Error: cannot plan resources.jobs.test_job: reading id="[NUMID]": Fault injected by test. (403 INJECTED) Endpoint: GET [DATABRICKS_URL]/api/2.2/jobs/get?job_id=[NUMID] @@ -18,7 +17,6 @@ API message: Fault injected by test. Error: planning failed -Files: 0 uploaded, 0 deleted Exit code: 1 @@ -26,7 +24,6 @@ Exit code: 1 === Deploy 3 (planning fails again, must not leave a WAL) >>> errcode [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Error: cannot plan resources.jobs.test_job: reading id="[NUMID]": Fault injected by test. (403 INJECTED) Endpoint: GET [DATABRICKS_URL]/api/2.2/jobs/get?job_id=[NUMID] @@ -36,7 +33,6 @@ API message: Fault injected by test. Error: planning failed -Files: 0 uploaded, 0 deleted Exit code: 1 diff --git a/acceptance/bundle/deployment/bind/pipelines/recreate/output.txt b/acceptance/bundle/deployment/bind/pipelines/recreate/output.txt index ac517cb9363..f85c4730a38 100644 --- a/acceptance/bundle/deployment/bind/pipelines/recreate/output.txt +++ b/acceptance/bundle/deployment/bind/pipelines/recreate/output.txt @@ -9,7 +9,6 @@ recreate pipelines.foo Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged >>> musterr [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-pipeline-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakeflow Spark Declarative Pipelines along with the Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating the pipelines will @@ -20,18 +19,17 @@ Error: the deployment requires destructive actions, but the current console does Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. To proceed, use --auto-approve after reviewing the plan above. -Files: 10 uploaded, 0 deleted >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-pipeline-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakeflow Spark Declarative Pipelines along with the Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating the pipelines will restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: recreate resources.pipelines.foo +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-pipeline-recreate-[UNIQUE_NAME]/default/files... Recreated pipelines.foo -Files: 0 uploaded, 0 deleted +Files: 10 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 0 unchanged >>> print_requests.py ^//import-file/ ^//workspace/ ^//telemetry-ext diff --git a/acceptance/bundle/dms/declined-deploy/output.txt b/acceptance/bundle/dms/declined-deploy/output.txt index 7a3e4e58bed..9bdb5090a20 100644 --- a/acceptance/bundle/dms/declined-deploy/output.txt +++ b/acceptance/bundle/dms/declined-deploy/output.txt @@ -107,7 +107,6 @@ Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged >>> update_file.py databricks.yml catalog_name: main catalog_name: dms_other_[UNIQUE_NAME] >>> musterr [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-declined-deploy-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: recreate resources.schemas.foo @@ -115,7 +114,6 @@ Error: the deployment requires destructive actions, but the current console does Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. To proceed, use --auto-approve after reviewing the plan above. -Files: 2 uploaded, 0 deleted === Nothing was recorded for the declined deploy - no version, so none to abort >>> print_requests.py --dms --get //api/2.0/bundle diff --git a/acceptance/bundle/dms/failed-recreate/output.txt b/acceptance/bundle/dms/failed-recreate/output.txt index 9f354ed381d..b7a22d40a01 100644 --- a/acceptance/bundle/dms/failed-recreate/output.txt +++ b/acceptance/bundle/dms/failed-recreate/output.txt @@ -102,10 +102,10 @@ Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged >>> fault.py POST /api/2.1/unity-catalog/schemas 400 0 1 INVALID_PARAMETER_VALUE >>> musterr [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-failed-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: recreate resources.schemas.foo +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-failed-recreate-[UNIQUE_NAME]/default/files... Error: cannot recreate resources.schemas.foo: Fault injected by test. (400 INVALID_PARAMETER_VALUE) Endpoint: POST [DATABRICKS_URL]/api/2.1/unity-catalog/schemas diff --git a/acceptance/bundle/dms/operation-upload-fails-once/output.txt b/acceptance/bundle/dms/operation-upload-fails-once/output.txt index e33b870fd84..d15e2714ad9 100644 --- a/acceptance/bundle/dms/operation-upload-fails-once/output.txt +++ b/acceptance/bundle/dms/operation-upload-fails-once/output.txt @@ -49,10 +49,10 @@ Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged >>> update_file.py databricks.yml catalog_name: main catalog_name: dms_other_[UNIQUE_NAME] >>> musterr [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-upload-fails-once-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: recreate resources.schemas.foo +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-upload-fails-once-[UNIQUE_NAME]/default/files... Recreated schemas.foo Error: recording operation for resources.schemas.foo: Fault injected by test. (500 INJECTED) diff --git a/acceptance/bundle/dms/successful-recreate/output.txt b/acceptance/bundle/dms/successful-recreate/output.txt index 2257b78cc8c..955fe68cca4 100644 --- a/acceptance/bundle/dms/successful-recreate/output.txt +++ b/acceptance/bundle/dms/successful-recreate/output.txt @@ -100,10 +100,10 @@ Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged >>> update_file.py databricks.yml catalog_name: main catalog_name: dms_other_[UNIQUE_NAME] >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-successful-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: recreate resources.schemas.foo +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-successful-recreate-[UNIQUE_NAME]/default/files... Recreated schemas.foo Files: 2 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 0 unchanged diff --git a/acceptance/bundle/dms/version-never-created/output.txt b/acceptance/bundle/dms/version-never-created/output.txt index 536d226a964..7aca5581429 100644 --- a/acceptance/bundle/dms/version-never-created/output.txt +++ b/acceptance/bundle/dms/version-never-created/output.txt @@ -1,7 +1,6 @@ === The first version fails, so no deployment record exists - only the node naming its ID >>> musterr [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-version-never-created/default/files... Error: failed to create deployment version: Internal error (500 INTERNAL_ERROR) Endpoint: POST [DATABRICKS_URL]/api/2.0/bundle/deployments/[NUMID]/versions?version_id=1 @@ -9,7 +8,6 @@ HTTP Status: 500 Internal Server Error API error_code: INTERNAL_ERROR API message: Internal error -Files: 3 uploaded, 0 deleted >>> [CLI] bundle plan -o json { @@ -43,7 +41,6 @@ Plan: 1 to add, 0 to change, 0 to delete, 0 unchanged === The next deploy reuses the ID that node names and retries version 1, rather than creating a second deployment >>> musterr [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-version-never-created/default/files... Error: failed to create deployment version: Internal error (500 INTERNAL_ERROR) Endpoint: POST [DATABRICKS_URL]/api/2.0/bundle/deployments/[NUMID]/versions?version_id=1 @@ -51,7 +48,6 @@ HTTP Status: 500 Internal Server Error API error_code: INTERNAL_ERROR API message: Internal error -Files: 0 uploaded, 0 deleted >>> print_requests.py --dms //api/2.0/bundle --get --oneline {"method": "GET", "path": "/api/2.0/bundle/deployments/[NUMID]/resources"} diff --git a/acceptance/bundle/resource_deps/pipelines_recreate/output.txt b/acceptance/bundle/resource_deps/pipelines_recreate/output.txt index 73802cef930..f54d61f4cb1 100644 --- a/acceptance/bundle/resource_deps/pipelines_recreate/output.txt +++ b/acceptance/bundle/resource_deps/pipelines_recreate/output.txt @@ -27,13 +27,13 @@ recreate pipelines.foo Plan: 1 to add, 1 to change, 1 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... This action will result in the deletion or recreation of the following Lakeflow Spark Declarative Pipelines along with the Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating the pipelines will restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: recreate resources.pipelines.foo +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Updated jobs.bar Recreated pipelines.foo Files: 3 uploaded, 0 deleted diff --git a/acceptance/bundle/resources/catalogs/auto-approve/output.txt b/acceptance/bundle/resources/catalogs/auto-approve/output.txt index 03a59924a75..3605ec8b0bd 100644 --- a/acceptance/bundle/resources/catalogs/auto-approve/output.txt +++ b/acceptance/bundle/resources/catalogs/auto-approve/output.txt @@ -37,12 +37,10 @@ test-file-[UNIQUE_NAME].txt -> dbfs:/Volumes/test-catalog-[UNIQUE_NAME]/test-sch === Try to redeploy the bundle - should fail without --auto-approve >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... Error: cannot plan resources.schemas.foo: cannot resolve "${resources.catalogs.bar.name}": internal error: resources.catalogs.bar: action is "delete" missing new_state Error: planning failed -Files: 1 uploaded, 1 deleted === Test cleanup >>> [CLI] bundle destroy --auto-approve diff --git a/acceptance/bundle/resources/dashboards/change-parent-path/output.txt b/acceptance/bundle/resources/dashboards/change-parent-path/output.txt index 9c4a9959969..0ea33336e4e 100644 --- a/acceptance/bundle/resources/dashboards/change-parent-path/output.txt +++ b/acceptance/bundle/resources/dashboards/change-parent-path/output.txt @@ -26,11 +26,11 @@ recreate dashboards.my_dashboard Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/change-parent-path-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following dashboards. This will result in changed IDs and permanent URLs of the dashboards that will be recreated: recreate resources.dashboards.my_dashboard +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/change-parent-path-[UNIQUE_NAME]/default/files... Recreated dashboards.my_dashboard Files: 1 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 0 unchanged diff --git a/acceptance/bundle/resources/database_instances/recreate/output.txt b/acceptance/bundle/resources/database_instances/recreate/output.txt index dc6c194d76f..bd3dc183a7b 100644 --- a/acceptance/bundle/resources/database_instances/recreate/output.txt +++ b/acceptance/bundle/resources/database_instances/recreate/output.txt @@ -12,11 +12,11 @@ recreate database_instances.my_database Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-lakebase-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakebase database instances. All data stored in them will be permanently lost: recreate resources.database_instances.my_database +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-lakebase-recreate-[UNIQUE_NAME]/default/files... Recreated database_instances.my_database Files: 0 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 0 unchanged diff --git a/acceptance/bundle/resources/genie_spaces/delete_warning/output.txt b/acceptance/bundle/resources/genie_spaces/delete_warning/output.txt index c604814dc1b..a4a5d36a7b8 100644 --- a/acceptance/bundle/resources/genie_spaces/delete_warning/output.txt +++ b/acceptance/bundle/resources/genie_spaces/delete_warning/output.txt @@ -7,11 +7,11 @@ Resources: 1 created, 0 changed, 0 deleted, 0 unchanged === Deploying without the genie space warns before deleting it (chat history loss) >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/genie-delete-warning-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Genie spaces. The conversation history attached to a deleted space is permanently lost: delete resources.genie_spaces.sales_analytics +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/genie-delete-warning-[UNIQUE_NAME]/default/files... Deleted genie_spaces.sales_analytics Files: 1 uploaded, 0 deleted Resources: 0 created, 0 changed, 1 deleted, 0 unchanged diff --git a/acceptance/bundle/resources/pipelines/auto-approve/output.txt b/acceptance/bundle/resources/pipelines/auto-approve/output.txt index 7b5b1c73f3e..adda8a018a1 100644 --- a/acceptance/bundle/resources/pipelines/auto-approve/output.txt +++ b/acceptance/bundle/resources/pipelines/auto-approve/output.txt @@ -44,7 +44,6 @@ Plan: 0 to add, 0 to change, 2 to delete, 0 unchanged === Try to redeploy the bundle - should fail without --auto-approve >>> errcode [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... This action will result in the deletion or recreation of the following Lakeflow Spark Declarative Pipelines along with the Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating the pipelines will @@ -55,7 +54,6 @@ Error: the deployment requires destructive actions, but the current console does Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. To proceed, use --auto-approve after reviewing the plan above. -Files: 0 uploaded, 1 deleted Exit code: 1 diff --git a/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/output.txt b/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/output.txt index 00f7bea1300..08b7983e8af 100644 --- a/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/output.txt +++ b/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/output.txt @@ -61,13 +61,13 @@ recreate pipelines.my Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakeflow Spark Declarative Pipelines along with the Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating the pipelines will restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: recreate resources.pipelines.my +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files... Recreated pipelines.my Files: 2 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 0 unchanged diff --git a/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/output.txt b/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/output.txt index cfd5f551fae..25dd7484afd 100644 --- a/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/output.txt +++ b/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/output.txt @@ -56,13 +56,13 @@ recreate pipelines.my Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakeflow Spark Declarative Pipelines along with the Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating the pipelines will restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: recreate resources.pipelines.my +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files... Recreated pipelines.my Files: 2 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 0 unchanged diff --git a/acceptance/bundle/resources/pipelines/recreate/output.txt b/acceptance/bundle/resources/pipelines/recreate/output.txt index e19c431df32..37ccc380d62 100644 --- a/acceptance/bundle/resources/pipelines/recreate/output.txt +++ b/acceptance/bundle/resources/pipelines/recreate/output.txt @@ -43,7 +43,6 @@ Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged === Redeploy the bundle (should recreate the pipeline) >>> errcode [CLI] bundle deploy --force-lock -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... This action will result in the deletion or recreation of the following Lakeflow Spark Declarative Pipelines along with the Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating the pipelines will @@ -54,7 +53,6 @@ Error: the deployment requires destructive actions, but the current console does Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. To proceed, use --auto-approve after reviewing the plan above. -Files: 1 uploaded, 0 deleted Exit code: 1 diff --git a/acceptance/bundle/resources/postgres_branches/recreate/output.txt b/acceptance/bundle/resources/postgres_branches/recreate/output.txt index 78cf8d611dc..c8669f8a0c7 100644 --- a/acceptance/bundle/resources/postgres_branches/recreate/output.txt +++ b/acceptance/bundle/resources/postgres_branches/recreate/output.txt @@ -64,11 +64,11 @@ resources: no_expiry: true >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-branch-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakebase branches. All data stored in them will be permanently lost: recreate resources.postgres_branches.main +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-branch-recreate-[UNIQUE_NAME]/default/files... Recreated postgres_branches.main Files: 0 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 1 unchanged diff --git a/acceptance/bundle/resources/postgres_branches/recreate_source_branch_time/output.txt b/acceptance/bundle/resources/postgres_branches/recreate_source_branch_time/output.txt index 76b348aa3fc..5dc85315f67 100644 --- a/acceptance/bundle/resources/postgres_branches/recreate_source_branch_time/output.txt +++ b/acceptance/bundle/resources/postgres_branches/recreate_source_branch_time/output.txt @@ -18,11 +18,11 @@ Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged >>> [CLI] bundle plan -o json >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-postgres-branch-fork-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakebase branches. All data stored in them will be permanently lost: recreate resources.postgres_branches.dev_branch +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-postgres-branch-fork-[UNIQUE_NAME]/default/files... Recreated postgres_branches.dev_branch Files: 0 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 1 unchanged diff --git a/acceptance/bundle/resources/postgres_databases/recreate/output.txt b/acceptance/bundle/resources/postgres_databases/recreate/output.txt index 2619af86a8b..6343c279e49 100644 --- a/acceptance/bundle/resources/postgres_databases/recreate/output.txt +++ b/acceptance/bundle/resources/postgres_databases/recreate/output.txt @@ -144,7 +144,6 @@ Plan: 1 to add, 0 to change, 1 to delete, 3 unchanged === Recreate requires approval: non-interactive deploy must abort, not silently delete the database >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-database-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakebase databases. All data stored in them will be permanently lost: @@ -153,14 +152,13 @@ Error: the deployment requires destructive actions, but the current console does Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. To proceed, use --auto-approve after reviewing the plan above. -Files: 0 uploaded, 0 deleted >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-database-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakebase databases. All data stored in them will be permanently lost: recreate resources.postgres_databases.my_database +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-database-recreate-[UNIQUE_NAME]/default/files... Recreated postgres_databases.my_database Files: 0 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 3 unchanged diff --git a/acceptance/bundle/resources/postgres_projects/recreate/output.txt b/acceptance/bundle/resources/postgres_projects/recreate/output.txt index c731c8a3f2f..a4057e9efba 100644 --- a/acceptance/bundle/resources/postgres_projects/recreate/output.txt +++ b/acceptance/bundle/resources/postgres_projects/recreate/output.txt @@ -50,11 +50,11 @@ resources: suspend_timeout_duration: "300s" >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Lakebase projects along with all their branches, databases, and endpoints. All data stored in them will be permanently lost: recreate resources.postgres_projects.my_project +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-recreate-[UNIQUE_NAME]/default/files... Recreated postgres_projects.my_project Files: 0 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 0 unchanged diff --git a/acceptance/bundle/resources/schemas/auto-approve/output.txt b/acceptance/bundle/resources/schemas/auto-approve/output.txt index 9a925613e6c..a5c104db4ca 100644 --- a/acceptance/bundle/resources/schemas/auto-approve/output.txt +++ b/acceptance/bundle/resources/schemas/auto-approve/output.txt @@ -54,7 +54,6 @@ test-file-[UNIQUE_NAME].txt -> dbfs:/Volumes/main/test-schema-[UNIQUE_NAME]/test === Try to redeploy the bundle - should fail without --auto-approve >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: delete resources.schemas.bar @@ -62,7 +61,6 @@ Error: the deployment requires destructive actions, but the current console does Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. To proceed, use --auto-approve after reviewing the plan above. -Files: 1 uploaded, 1 deleted === Test cleanup >>> [CLI] bundle destroy --auto-approve diff --git a/acceptance/bundle/resources/schemas/recreate/output.txt b/acceptance/bundle/resources/schemas/recreate/output.txt index d3b3cab14ac..8734cd37f32 100644 --- a/acceptance/bundle/resources/schemas/recreate/output.txt +++ b/acceptance/bundle/resources/schemas/recreate/output.txt @@ -36,10 +36,10 @@ recreate schemas.schema1 Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: recreate resources.schemas.schema1 +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Recreated schemas.schema1 Files: 1 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 0 unchanged diff --git a/acceptance/bundle/resources/synced_database_tables/recreate/output.txt b/acceptance/bundle/resources/synced_database_tables/recreate/output.txt index a0af7751e47..113660df9b2 100644 --- a/acceptance/bundle/resources/synced_database_tables/recreate/output.txt +++ b/acceptance/bundle/resources/synced_database_tables/recreate/output.txt @@ -14,11 +14,11 @@ recreate synced_database_tables.my_synced_table Plan: 1 to add, 0 to change, 1 to delete, 2 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-lakebase-synced-recreate-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following synced database tables. The synced data in the destination database will be lost (the source table is preserved): recreate resources.synced_database_tables.my_synced_table +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-lakebase-synced-recreate-[UNIQUE_NAME]/default/files... Recreated synced_database_tables.my_synced_table Files: 1 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 2 unchanged diff --git a/acceptance/bundle/resources/vector_search_indexes/drift/orphaned_endpoint/output.txt b/acceptance/bundle/resources/vector_search_indexes/drift/orphaned_endpoint/output.txt index 2f0118a9743..bb2906d5e51 100644 --- a/acceptance/bundle/resources/vector_search_indexes/drift/orphaned_endpoint/output.txt +++ b/acceptance/bundle/resources/vector_search_indexes/drift/orphaned_endpoint/output.txt @@ -19,12 +19,12 @@ Plan: 2 to add, 0 to change, 1 to delete, 0 unchanged === Deploy recreates endpoint and rebinds index >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-index-orphaned-endpoint-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Vector Search indexes. Recreating a Delta Sync index re-runs the full embedding pipeline; recreating a Direct Access index drops all upserted vectors. Both can be expensive to rebuild: recreate resources.vector_search_indexes.my_index +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-index-orphaned-endpoint-[UNIQUE_NAME]/default/files... Created vector_search_endpoints.vs_endpoint Recreated vector_search_indexes.my_index Files: 0 uploaded, 0 deleted diff --git a/acceptance/bundle/resources/vector_search_indexes/recreate/embedding_dimension/output.txt b/acceptance/bundle/resources/vector_search_indexes/recreate/embedding_dimension/output.txt index cdb909152fd..22f37eb3e2d 100644 --- a/acceptance/bundle/resources/vector_search_indexes/recreate/embedding_dimension/output.txt +++ b/acceptance/bundle/resources/vector_search_indexes/recreate/embedding_dimension/output.txt @@ -18,12 +18,12 @@ recreate vector_search_indexes.my_index Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-vs-index-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Vector Search indexes. Recreating a Delta Sync index re-runs the full embedding pipeline; recreating a Direct Access index drops all upserted vectors. Both can be expensive to rebuild: recreate resources.vector_search_indexes.my_index +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-vs-index-[UNIQUE_NAME]/default/files... Recreated vector_search_indexes.my_index Files: 0 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 1 unchanged diff --git a/acceptance/bundle/resources/vector_search_indexes/recreate/pending_deletion/output.txt b/acceptance/bundle/resources/vector_search_indexes/recreate/pending_deletion/output.txt index a175c2dcae3..d2dea03df77 100644 --- a/acceptance/bundle/resources/vector_search_indexes/recreate/pending_deletion/output.txt +++ b/acceptance/bundle/resources/vector_search_indexes/recreate/pending_deletion/output.txt @@ -11,12 +11,12 @@ Resources: 2 created, 0 changed, 0 deleted, 0 unchanged >>> update_file.py databricks.yml embedding_dimension: 768 embedding_dimension: 384 >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-vs-index-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Vector Search indexes. Recreating a Delta Sync index re-runs the full embedding pipeline; recreating a Direct Access index drops all upserted vectors. Both can be expensive to rebuild: recreate resources.vector_search_indexes.my_index +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-vs-index-[UNIQUE_NAME]/default/files... Recreated vector_search_indexes.my_index Files: 0 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 1 unchanged diff --git a/acceptance/bundle/resources/vector_search_indexes/recreate/with_endpoint/output.txt b/acceptance/bundle/resources/vector_search_indexes/recreate/with_endpoint/output.txt index d14f3f90b0d..c845b4ab9fa 100644 --- a/acceptance/bundle/resources/vector_search_indexes/recreate/with_endpoint/output.txt +++ b/acceptance/bundle/resources/vector_search_indexes/recreate/with_endpoint/output.txt @@ -134,12 +134,12 @@ recreate vector_search_indexes.my_index Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-vs-index-with-endpoint-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following Vector Search indexes. Recreating a Delta Sync index re-runs the full embedding pipeline; recreating a Direct Access index drops all upserted vectors. Both can be expensive to rebuild: recreate resources.vector_search_indexes.my_index +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-vs-index-with-endpoint-[UNIQUE_NAME]/default/files... Recreated vector_search_indexes.my_index Files: 0 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 1 unchanged diff --git a/acceptance/bundle/resources/volumes/change-schema-name/output.txt b/acceptance/bundle/resources/volumes/change-schema-name/output.txt index 9a987f412ca..5def0106015 100644 --- a/acceptance/bundle/resources/volumes/change-schema-name/output.txt +++ b/acceptance/bundle/resources/volumes/change-schema-name/output.txt @@ -43,13 +43,13 @@ recreate volumes.volume1 Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... This action will result in the deletion or recreation of the following volumes. For managed volumes, the files stored in the volume are also deleted from your cloud tenant within 30 days. For external volumes, the metadata about the volume is removed from the catalog, but the underlying files are not deleted: recreate resources.volumes.volume1 +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Recreated volumes.volume1 Files: 1 uploaded, 0 deleted Resources: 1 created, 0 changed, 1 deleted, 0 unchanged diff --git a/acceptance/bundle/resources/volumes/recreate/output.txt b/acceptance/bundle/resources/volumes/recreate/output.txt index fed59b1a868..ac53a71fb1f 100644 --- a/acceptance/bundle/resources/volumes/recreate/output.txt +++ b/acceptance/bundle/resources/volumes/recreate/output.txt @@ -34,7 +34,6 @@ Validation OK! === Recreation of the volume without --auto-approve should fail since prompting is not possible >>> errcode [CLI] bundle deploy --var=schema_name=${resources.schemas.schema2.name} -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-uc-volumes-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following volumes. For managed volumes, the files stored in the volume are also deleted from your @@ -45,7 +44,6 @@ Error: the deployment requires destructive actions, but the current console does Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. To proceed, use --auto-approve after reviewing the plan above. -Files: 0 uploaded, 0 deleted Exit code: 1 diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 92c6f5c076d..693cec6f2d2 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -216,30 +216,6 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand return } - if !immutable { - uploadLibraries(ctx, b, libs) - if logdiag.HasError(ctx) { - return - } - - bundle.ApplySeqContext(ctx, b, files.Upload(outputHandler)) - if logdiag.HasError(ctx) { - return - } - } - - // From here on the files are uploaded, so report them however the rest of the - // deploy turns out. Deferred rather than repeated at each of the returns below, so - // that a new early return cannot silently drop it. On success logDeploySummary - // prints this line itself, between the per-resource lines and the resource summary, - // and sets the flag so the defer does not print it twice. - filesReported := false - defer func() { - if !filesReported { - logFileSummary(ctx, b) - } - }() - bundle.ApplySeqContext( ctx, b, deploy.StateUpdate(), @@ -347,6 +323,33 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand } } + // Upload only after planning, approval, and (for DMS deployments with changes) + // version creation. In particular, a failed CreateVersion must not leave files + // from an unrecorded deployment in the workspace. + if !immutable { + uploadLibraries(ctx, b, libs) + if logdiag.HasError(ctx) { + return + } + + bundle.ApplySeqContext(ctx, b, files.Upload(outputHandler)) + if logdiag.HasError(ctx) { + return + } + } + + // From here on the files are uploaded, so report them however the rest of the + // deploy turns out. Deferred rather than repeated at each of the returns below, so + // that a new early return cannot silently drop it. On success logDeploySummary + // prints this line itself, between the per-resource lines and the resource summary, + // and sets the flag so the defer does not print it twice. + filesReported := false + defer func() { + if !filesReported { + logFileSummary(ctx, b) + } + }() + deployCore(ctx, b, plan, stateEngine) if logdiag.HasError(ctx) { From d98009a1cbf0e464b492574abd68410e73913997 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 16 Sep 2026 11:35:16 +0000 Subject: [PATCH 2/5] docs: link upload-order changelog to PR --- .nextchanges/bundles/upload-after-plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nextchanges/bundles/upload-after-plan.md b/.nextchanges/bundles/upload-after-plan.md index 447d94fd791..af9fd5d1fc0 100644 --- a/.nextchanges/bundles/upload-after-plan.md +++ b/.nextchanges/bundles/upload-after-plan.md @@ -1 +1 @@ -* Delay bundle file and artifact uploads until planning, approval, and any DMS version creation succeed. +* Delay bundle file and artifact uploads until planning, approval, and any DMS version creation succeed. ([#6704](https://github.com/databricks/cli/pull/6704)) From 5f9529cb318dd915a0117476a68f3c3be0ac33d1 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 16 Sep 2026 14:08:35 +0000 Subject: [PATCH 3/5] bundle: preserve upload bookkeeping and version lifecycle after planning --- .../bind/dashboard/recreation/output.txt | 4 +- .../dms/file-upload-failure/databricks.yml | 20 +++++ .../dms/file-upload-failure/out.test.toml | 3 + .../bundle/dms/file-upload-failure/output.txt | 36 +++++++++ .../bundle/dms/file-upload-failure/script | 11 +++ .../bundle/dms/file-upload-failure/test.toml | 1 + .../dms/version-never-created/output.txt | 2 + .../bundle/dms/version-never-created/script | 1 + .../lifecycle/prevent-destroy/out.direct.txt | 4 +- .../prevent-destroy/out.terraform.txt | 4 +- .../pipelines/deploy/auto-approve/output.txt | 6 +- bundle/direct/bundle_plan.go | 4 +- bundle/direct/dstate/state.go | 7 +- bundle/phases/deploy.go | 67 ++++++++++------ bundle/phases/dms_heartbeat.go | 60 ++++++++++++++ bundle/phases/dms_heartbeat_test.go | 78 +++++++++++++++++++ 16 files changed, 266 insertions(+), 42 deletions(-) create mode 100644 acceptance/bundle/dms/file-upload-failure/databricks.yml create mode 100644 acceptance/bundle/dms/file-upload-failure/out.test.toml create mode 100644 acceptance/bundle/dms/file-upload-failure/output.txt create mode 100644 acceptance/bundle/dms/file-upload-failure/script create mode 100644 acceptance/bundle/dms/file-upload-failure/test.toml create mode 100644 bundle/phases/dms_heartbeat.go create mode 100644 bundle/phases/dms_heartbeat_test.go diff --git a/acceptance/bundle/deployment/bind/dashboard/recreation/output.txt b/acceptance/bundle/deployment/bind/dashboard/recreation/output.txt index d1aee793bb0..dae4dd1bcea 100644 --- a/acceptance/bundle/deployment/bind/dashboard/recreation/output.txt +++ b/acceptance/bundle/deployment/bind/dashboard/recreation/output.txt @@ -10,7 +10,6 @@ Run 'bundle deploy' to deploy changes to your workspace recreate dashboards.dashboard1 Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... This action will result in the deletion or recreation of the following dashboards. This will result in changed IDs and permanent URLs of the dashboards that will be recreated: @@ -19,14 +18,13 @@ Error: the deployment requires destructive actions, but the current console does Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. To proceed, use --auto-approve after reviewing the plan above. -Files: 6 uploaded, 0 deleted Exit code: 1 >>> [CLI] bundle deployment unbind dashboard1 Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... Created dashboards.dashboard1 -Files: 1 uploaded, 0 deleted +Files: 7 uploaded, 0 deleted Resources: 1 created, 0 changed, 0 deleted, 0 unchanged >>> [CLI] lakeview get [DASHBOARD_ID] diff --git a/acceptance/bundle/dms/file-upload-failure/databricks.yml b/acceptance/bundle/dms/file-upload-failure/databricks.yml new file mode 100644 index 00000000000..060fbd0459e --- /dev/null +++ b/acceptance/bundle/dms/file-upload-failure/databricks.yml @@ -0,0 +1,20 @@ +bundle: + name: dms-file-upload-failure + deployment: + lock: + enabled: false + +experimental: + deployment_history: true + +workspace: + artifact_path: /Workspace/Users/${workspace.current_user.userName}/dms-file-upload-failure-artifacts + +permissions: + - user_name: ${workspace.current_user.userName} + level: CAN_MANAGE + +resources: + jobs: + foo: + name: foo diff --git a/acceptance/bundle/dms/file-upload-failure/out.test.toml b/acceptance/bundle/dms/file-upload-failure/out.test.toml new file mode 100644 index 00000000000..9921e91a794 --- /dev/null +++ b/acceptance/bundle/dms/file-upload-failure/out.test.toml @@ -0,0 +1,3 @@ +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DMS = ["true"] diff --git a/acceptance/bundle/dms/file-upload-failure/output.txt b/acceptance/bundle/dms/file-upload-failure/output.txt new file mode 100644 index 00000000000..d87864d48a0 --- /dev/null +++ b/acceptance/bundle/dms/file-upload-failure/output.txt @@ -0,0 +1,36 @@ + +=== An upload failure completes the version without applying resources or opening a WAL +>>> fault.py POST /api/2.0/workspace-files/import-file/* 403 0 1 PERMISSION_DENIED + +>>> musterr [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-file-upload-failure/default/files... +Error: Failed to update, encountered possible permission error: /Workspace/Users/[USERNAME]/.bundle/dms-file-upload-failure/default/files +Error: unable to deploy to /Workspace/Users/[USERNAME]/.bundle/dms-file-upload-failure/default/files as [USERNAME]. Cannot apply local deployment permissions. +For assistance, contact the owners of this project. +They can redeploy the project to apply the latest set of permissions. +Please refer to https://docs.databricks.com/dev-tools/bundles/permissions.html for more on managing permissions. + + +>>> assert_not_exists.py .databricks/bundle/default/resources.json.wal + +>>> print_requests.py --keep //jobs + +>>> print_requests.py --dms //versions --oneline +{"method": "POST", "path": "/api/2.0/bundle/deployments/[NUMID]/versions", "q": {"version_id": "1"}, "body": {"cli_version": "[CLI_VERSION]", "version_type": "VERSION_TYPE_DEPLOY", "operations": [{"resource_key": "jobs.foo", "action_type": "OPERATION_ACTION_TYPE_CREATE"}, {"resource_key": "jobs.foo.permissions", "action_type": "OPERATION_ACTION_TYPE_CREATE"}]}} +{"method": "POST", "path": "/api/2.0/bundle/deployments/[NUMID]/versions/1/complete", "body": {"completion_reason": "VERSION_COMPLETE_FAILURE"}} + +=== Retry succeeds, including permissions on the custom artifact directory +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-file-upload-failure/default/files... +Created jobs.foo +Created jobs.foo.permissions +Files: 3 uploaded, 0 deleted +Resources: 2 created, 0 changed, 0 deleted, 0 unchanged + +>>> assert_not_exists.py .databricks/bundle/default/resources.json.wal + +>>> print_requests.py --dms //versions --oneline +{"method": "POST", "path": "/api/2.0/bundle/deployments/[NUMID]/versions", "q": {"version_id": "2"}, "body": {"cli_version": "[CLI_VERSION]", "version_type": "VERSION_TYPE_DEPLOY", "previous_version_id": "1", "operations": [{"resource_key": "jobs.foo", "action_type": "OPERATION_ACTION_TYPE_CREATE"}, {"resource_key": "jobs.foo.permissions", "action_type": "OPERATION_ACTION_TYPE_CREATE"}]}} +{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[NUMID]/versions/2/operations/jobs.foo", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "[NUMID]", "sequence_id": "0", "state": "{\"state\":{\"deployment\":{\"deployment_id\":\"[NUMID]\",\"kind\":\"BUNDLE\",\"metadata_file_path\":\"/Workspace/Users/[USERNAME]/.bundle/dms-file-upload-failure/default/state/metadata.json\",\"version_id\":\"2\"},\"edit_mode\":\"UI_LOCKED\",\"format\":\"MULTI_TASK\",\"max_concurrent_runs\":1,\"name\":\"foo\",\"queue\":{\"enabled\":true}}}", "status": "OPERATION_STATUS_SUCCEEDED"}} +{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[NUMID]/versions/2/operations/jobs.foo.permissions", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "/jobs/[NUMID]", "sequence_id": "0", "state": "{\"state\":{\"object_id\":\"/jobs/[NUMID]\",\"__embed__\":[{\"level\":\"IS_OWNER\",\"user_name\":\"[USERNAME]\"}]},\"depends_on\":[{\"node\":\"resources.jobs.foo\",\"label\":\"${resources.jobs.foo.id}\"}]}", "status": "OPERATION_STATUS_SUCCEEDED"}} +{"method": "POST", "path": "/api/2.0/bundle/deployments/[NUMID]/versions/2/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}} diff --git a/acceptance/bundle/dms/file-upload-failure/script b/acceptance/bundle/dms/file-upload-failure/script new file mode 100644 index 00000000000..2122aaed3b8 --- /dev/null +++ b/acceptance/bundle/dms/file-upload-failure/script @@ -0,0 +1,11 @@ +title "An upload failure completes the version without applying resources or opening a WAL" +trace fault.py "POST /api/2.0/workspace-files/import-file/*" 403 0 1 PERMISSION_DENIED +trace musterr $CLI bundle deploy +trace assert_not_exists.py .databricks/bundle/default/resources.json.wal +trace print_requests.py --keep //jobs | contains.py "!POST" +trace print_requests.py --dms //versions --oneline + +title "Retry succeeds, including permissions on the custom artifact directory" +trace $CLI bundle deploy +trace assert_not_exists.py .databricks/bundle/default/resources.json.wal +trace print_requests.py --dms //versions --oneline diff --git a/acceptance/bundle/dms/file-upload-failure/test.toml b/acceptance/bundle/dms/file-upload-failure/test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/bundle/dms/file-upload-failure/test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/bundle/dms/version-never-created/output.txt b/acceptance/bundle/dms/version-never-created/output.txt index 7aca5581429..f29eb70952c 100644 --- a/acceptance/bundle/dms/version-never-created/output.txt +++ b/acceptance/bundle/dms/version-never-created/output.txt @@ -9,6 +9,8 @@ API error_code: INTERNAL_ERROR API message: Internal error +>>> print_requests.py --keep //files/ //artifacts/ + >>> [CLI] bundle plan -o json { "plan_version": [PLAN_VERSION], diff --git a/acceptance/bundle/dms/version-never-created/script b/acceptance/bundle/dms/version-never-created/script index 18d3b71c3b4..c3297c7cef1 100644 --- a/acceptance/bundle/dms/version-never-created/script +++ b/acceptance/bundle/dms/version-never-created/script @@ -1,5 +1,6 @@ title "The first version fails, so no deployment record exists - only the node naming its ID" trace musterr $CLI bundle deploy +trace print_requests.py --keep //files/ //artifacts/ | contains.py "!POST" "!DELETE" dms_plan trace MSYS_NO_PATHCONV=1 $CLI workspace get-status "/Workspace/Users/${CURRENT_USER_NAME}/.bundle/dms-version-never-created/default/state/resources.deployment.json" | jq '{object_type}' trace print_requests.py --dms //api/2.0/bundle --get --oneline diff --git a/acceptance/bundle/lifecycle/prevent-destroy/out.direct.txt b/acceptance/bundle/lifecycle/prevent-destroy/out.direct.txt index a7082af177b..1e9b90f8cd4 100644 --- a/acceptance/bundle/lifecycle/prevent-destroy/out.direct.txt +++ b/acceptance/bundle/lifecycle/prevent-destroy/out.direct.txt @@ -31,7 +31,6 @@ recreate schemas.my_schema Plan: 2 to add, 0 to change, 2 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/prevent-destroy/default/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: recreate resources.schemas.my_schema @@ -41,6 +40,7 @@ Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: recreate resources.pipelines.my_pipelines +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/prevent-destroy/default/files... Recreated pipelines.my_pipelines Recreated schemas.my_schema Files: 3 uploaded, 0 deleted @@ -53,7 +53,6 @@ delete schemas.my_schema Plan: 0 to add, 0 to change, 2 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/prevent-destroy/default/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: delete resources.schemas.my_schema @@ -63,6 +62,7 @@ Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: delete resources.pipelines.my_pipelines +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/prevent-destroy/default/files... Deleted pipelines.my_pipelines Deleted schemas.my_schema Files: 1 uploaded, 2 deleted diff --git a/acceptance/bundle/lifecycle/prevent-destroy/out.terraform.txt b/acceptance/bundle/lifecycle/prevent-destroy/out.terraform.txt index 4baf1cc6d1f..b9f0c7797fb 100644 --- a/acceptance/bundle/lifecycle/prevent-destroy/out.terraform.txt +++ b/acceptance/bundle/lifecycle/prevent-destroy/out.terraform.txt @@ -115,7 +115,6 @@ recreate schemas.my_schema Plan: 2 to add, 0 to change, 2 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/prevent-destroy/default/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: recreate resources.schemas.my_schema @@ -125,6 +124,7 @@ Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: recreate resources.pipelines.my_pipelines +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/prevent-destroy/default/files... Recreated pipelines.my_pipelines Recreated schemas.my_schema Files: 3 uploaded, 0 deleted @@ -137,7 +137,6 @@ delete schemas.my_schema Plan: 0 to add, 0 to change, 2 to delete, 0 unchanged >>> [CLI] bundle deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/prevent-destroy/default/files... This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: delete resources.schemas.my_schema @@ -147,6 +146,7 @@ Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: delete resources.pipelines.my_pipelines +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/prevent-destroy/default/files... Deleted pipelines.my_pipelines Deleted schemas.my_schema Files: 1 uploaded, 2 deleted diff --git a/acceptance/pipelines/deploy/auto-approve/output.txt b/acceptance/pipelines/deploy/auto-approve/output.txt index 2acd80d5910..9015e6786c5 100644 --- a/acceptance/pipelines/deploy/auto-approve/output.txt +++ b/acceptance/pipelines/deploy/auto-approve/output.txt @@ -11,7 +11,6 @@ View your pipeline foo here: [DATABRICKS_URL]/pipelines/[UUID]?w=[NUMID] === Try to redeploy without --auto-approve - should fail >>> errcode [CLI] pipelines deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-pipeline-auto-approve/default/files... This action will result in the deletion or recreation of the following Lakeflow Spark Declarative Pipelines along with the Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating the pipelines will @@ -22,19 +21,18 @@ Error: the deployment requires destructive actions, but the current console does Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. To proceed, use --auto-approve after reviewing the plan above. -Files: 0 uploaded, 1 deleted Exit code: 1 === Redeploy with --auto-approve - should succeed >>> [CLI] pipelines deploy --auto-approve -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-pipeline-auto-approve/default/files... This action will result in the deletion or recreation of the following Lakeflow Spark Declarative Pipelines along with the Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating the pipelines will restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: delete resources.pipelines.foo +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-pipeline-auto-approve/default/files... Deleted pipelines.foo -Files: 0 uploaded, 0 deleted +Files: 0 uploaded, 1 deleted Resources: 0 created, 0 changed, 1 deleted, 0 unchanged diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index baa5aa8d1eb..76f61d70f55 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -67,9 +67,9 @@ func ValidatePlanAgainstState(stateDB *dstate.DeploymentState, plan *deployplan. } // InitForApply initializes the DeploymentBundle for applying a pre-computed plan. -// StateDB must already be open for write before calling this function. +// StateDB must already be open for read or write before calling this function. func (b *DeploymentBundle) InitForApply(ctx context.Context, client *databricks.WorkspaceClient, plan *deployplan.Plan) error { - b.StateDB.AssertOpenedForWrite() + b.StateDB.AssertOpenedForReadOrWrite() err := b.init(client) if err != nil { diff --git a/bundle/direct/dstate/state.go b/bundle/direct/dstate/state.go index f4fb00c6b9e..63ef3b1bd85 100644 --- a/bundle/direct/dstate/state.go +++ b/bundle/direct/dstate/state.go @@ -251,10 +251,9 @@ func (db *DeploymentState) CompleteVersion(ctx context.Context, success bool) (b deploymentID, client, versionID := db.DeploymentID, db.dmsClient, db.VersionID db.mu.Unlock() - // A recording failure fails the version even when the caller counted the deploy a success: the - // service does not then hold everything the WAL does. Finalize already drained and surfaced it; - // this reads the drained buffer's error so a destroy whose uploads failed keeps its record. - if buf.Err() != nil { + // Uploads can fail before resource application starts, bypassing Finalize. + // Always close the buffer here too; Drain is idempotent after Finalize. + if buf.Drain() != nil { success = false } diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 693cec6f2d2..8720a57e930 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "slices" + "time" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/artifacts" @@ -201,13 +202,21 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand // // The version is created only after approval; CompleteVersion is deferred before // lock.Release and no-ops until then. + var stopHeartbeat func() defer func() { + success := ctx.Err() == nil + if stopHeartbeat != nil { + stopHeartbeat() + } + // Release the version and workspace lock even after an upload is canceled. + cleanupCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second) + defer cancel() if b.DeploymentBundle.StateDB.IsDeploymentMetadataService() { - if _, err := b.DeploymentBundle.StateDB.CompleteVersion(ctx, !logdiag.HasError(ctx)); err != nil { + if _, err := b.DeploymentBundle.StateDB.CompleteVersion(cleanupCtx, success && !logdiag.HasError(ctx)); err != nil { logdiag.LogError(ctx, err) } } - bundle.ApplyContext(ctx, b, lock.Release(lock.GoalDeploy)) + bundle.ApplyContext(cleanupCtx, b, lock.Release(lock.GoalDeploy)) }() immutable := b.IsImmutableFolder() @@ -216,19 +225,6 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand return } - bundle.ApplySeqContext( - ctx, b, - deploy.StateUpdate(), - deploy.StatePush(), - permissions.ApplyWorkspaceRootPermissions(), - metrics.TrackUsedCompute(), - deploy.ResourcePathMkdir(), - ) - - if logdiag.HasError(ctx) { - return - } - if immutable { // Only discard previously staged zips when building a fresh plan. When applying // a pre-existing plan (plan != nil), its zip_path points at a file staged when @@ -253,14 +249,6 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand return } - if stateEngine.IsDirect() { - // Upgrade from read (opened by process.go) to write mode - if err := b.DeploymentBundle.StateDB.UpgradeToWrite(); err != nil { - logdiag.LogError(ctx, err) - return - } - } - if planFromFile { // Initialize DeploymentBundle for applying the loaded plan err := b.DeploymentBundle.InitForApply(ctx, b.WorkspaceClient(ctx), plan) @@ -271,8 +259,7 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand } // InitForApply receives ctx and could log a diagnostic without returning an - // error, so re-check before deploying. (UpgradeToWrite above takes no ctx and - // thus cannot log, so the earlier check is enough to guard the WAL open.) + // error, so re-check before deploying. if logdiag.HasError(ctx) { return } @@ -296,6 +283,14 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand // IsDirect first: the state must be open to read its features, and only the direct engine opens it. if stateEngine.IsDirect() && b.DeploymentBundle.StateDB.IsDeploymentMetadataService() { firstDeploy := b.DeploymentBundle.StateDB.DeploymentID == "" + if firstDeploy { + // StatePush used to create this parent before registering the deployment. + // Create it explicitly, including when workspace locking is disabled. + if err := b.WorkspaceClient(ctx).Workspace.MkdirsByPath(ctx, b.Config.Workspace.StatePath); err != nil { + logdiag.LogError(ctx, err) + return + } + } createOrUpdateDeployment(ctx, b, dmsDeployment) if logdiag.HasError(ctx) { return @@ -320,6 +315,7 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand return } logDeploymentVersion(ctx, b) + ctx, stopHeartbeat = startDeploymentHeartbeat(ctx, b) } } @@ -350,6 +346,27 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand } }() + // Persist the inventory produced by sync, and apply permissions only after + // uploads have created the file and artifact directories (including custom paths). + bundle.ApplySeqContext(ctx, b, + deploy.StateUpdate(), + deploy.StatePush(), + permissions.ApplyWorkspaceRootPermissions(), + metrics.TrackUsedCompute(), + deploy.ResourcePathMkdir(), + ) + if logdiag.HasError(ctx) { + return + } + + if stateEngine.IsDirect() { + // No resource state is written until uploads and their bookkeeping succeed. + if err := b.DeploymentBundle.StateDB.UpgradeToWrite(); err != nil { + logdiag.LogError(ctx, err) + return + } + } + deployCore(ctx, b, plan, stateEngine) if logdiag.HasError(ctx) { diff --git a/bundle/phases/dms_heartbeat.go b/bundle/phases/dms_heartbeat.go new file mode 100644 index 00000000000..66e617912b0 --- /dev/null +++ b/bundle/phases/dms_heartbeat.go @@ -0,0 +1,60 @@ +package phases + +import ( + "context" + "fmt" + "time" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/databricks-sdk-go/service/bundledeployments" +) + +func startDeploymentHeartbeat(ctx context.Context, b *bundle.Bundle) (context.Context, func()) { + db := &b.DeploymentBundle.StateDB + name := fmt.Sprintf("deployments/%s/versions/%d", db.DeploymentID, db.VersionID) + heartbeat := func(ctx context.Context) error { + _, err := db.DmsClient().Service.Heartbeat(ctx, bundledeployments.HeartbeatRequest{Name: name}) + return err + } + ticker := time.NewTicker(30 * time.Second) + runCtx, stop := keepDeploymentAlive(ctx, ticker.C, heartbeat) + return runCtx, func() { + ticker.Stop() + stop() + } +} + +// A failed renewal cancels uploads/apply rather than continuing without a lease. +// Bound each RPC well below the two-minute lease, including SDK retries. +func keepDeploymentAlive(ctx context.Context, ticks <-chan time.Time, heartbeat func(context.Context) error) (context.Context, func()) { + runCtx, cancelRun := context.WithCancel(ctx) + heartbeatCtx, cancelHeartbeat := context.WithCancel(runCtx) + done := make(chan struct{}) + go func() { + defer close(done) + for { + select { + case <-heartbeatCtx.Done(): + return + case <-ticks: + rpcCtx, cancel := context.WithTimeout(heartbeatCtx, 15*time.Second) + err := heartbeat(rpcCtx) + cancel() + if heartbeatCtx.Err() != nil { + return + } + if err != nil { + logdiag.LogError(ctx, fmt.Errorf("renewing deployment version lease: %w", err)) + cancelRun() + return + } + } + } + }() + return runCtx, func() { + cancelHeartbeat() + <-done + cancelRun() + } +} diff --git a/bundle/phases/dms_heartbeat_test.go b/bundle/phases/dms_heartbeat_test.go new file mode 100644 index 00000000000..d9314c202ae --- /dev/null +++ b/bundle/phases/dms_heartbeat_test.go @@ -0,0 +1,78 @@ +package phases + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/databricks/cli/libs/logdiag" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDeploymentHeartbeatRenewsAndStops(t *testing.T) { + ctx := logdiag.InitContext(t.Context()) + ticks := make(chan time.Time) + called := make(chan time.Time, 2) + runCtx, stop := keepDeploymentAlive(ctx, ticks, func(ctx context.Context) error { + deadline, ok := ctx.Deadline() + if !ok { + return errors.New("heartbeat has no deadline") + } + called <- deadline + return nil + }) + defer stop() + for range 2 { + ticks <- time.Now() + select { + case deadline := <-called: + assert.WithinDuration(t, time.Now().Add(15*time.Second), deadline, time.Second) + case <-time.After(5 * time.Second): + t.Fatal("heartbeat did not run") + } + } + require.NoError(t, runCtx.Err()) + stop() + assert.ErrorIs(t, runCtx.Err(), context.Canceled) + assert.False(t, logdiag.HasError(ctx)) +} + +func TestDeploymentHeartbeatFailureCancelsWork(t *testing.T) { + ctx := logdiag.InitContext(t.Context()) + logdiag.SetCollect(ctx, true) + ticks := make(chan time.Time, 1) + runCtx, stop := keepDeploymentAlive(ctx, ticks, func(context.Context) error { + return errors.New("lease lost") + }) + defer stop() + ticks <- time.Now() + select { + case <-runCtx.Done(): + case <-time.After(5 * time.Second): + t.Fatal("work was not canceled after losing the lease") + } + stop() + assert.Contains(t, logdiag.GetFirstErrorSummary(ctx), "lease lost") +} + +func TestDeploymentHeartbeatStopCancelsInflightRequest(t *testing.T) { + ctx := logdiag.InitContext(t.Context()) + ticks := make(chan time.Time, 1) + started := make(chan struct{}) + _, stop := keepDeploymentAlive(ctx, ticks, func(ctx context.Context) error { + close(started) + <-ctx.Done() + return ctx.Err() + }) + defer stop() + ticks <- time.Now() + select { + case <-started: + case <-time.After(5 * time.Second): + t.Fatal("heartbeat did not start") + } + stop() + assert.False(t, logdiag.HasError(ctx), "normal shutdown must not fail a successful deploy") +} From a0ec0e11bb86b943d2d72ad541dcec0459484a07 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 16 Sep 2026 14:21:04 +0000 Subject: [PATCH 4/5] test: align plan-failure and saved-plan coverage with deferred uploads --- .../bundle/resource_deps/loop_jobs/out.deploy.direct.txt | 2 -- .../bundle/resource_deps/loop_jobs/out.deploy.terraform.txt | 2 -- .../bundle/resource_deps/loop_self/out.deploy.direct.txt | 2 -- .../bundle/resource_deps/loop_self/out.deploy.terraform.txt | 2 -- .../missing_ingestion_definition/out.deploy.terraform.txt | 2 -- .../resource_deps/missing_map_key/out.deploy.terraform.txt | 2 -- .../resource_deps/non_existent_field/out.deploy.direct.txt | 2 -- .../resource_deps/non_existent_field/out.deploy.terraform.txt | 2 -- .../bundle/resources/jobs/unknown-terraform-field/output.txt | 2 +- .../bundle/resources/models/readplan-permissions/output.txt | 4 +++- .../bundle/resources/models/readplan-permissions/script | 2 +- .../bundle/resources/models/readplan-permissions/test.toml | 4 ++++ .../without_branch_id/out.deploy.terraform.txt | 2 -- .../without_endpoint_id/out.deploy.terraform.txt | 2 -- .../without_project_id/out.deploy.terraform.txt | 2 -- bundle/phases/deploy.go | 4 ++-- 16 files changed, 11 insertions(+), 27 deletions(-) diff --git a/acceptance/bundle/resource_deps/loop_jobs/out.deploy.direct.txt b/acceptance/bundle/resource_deps/loop_jobs/out.deploy.direct.txt index 6e3100041a0..224dba5bfed 100644 --- a/acceptance/bundle/resource_deps/loop_jobs/out.deploy.direct.txt +++ b/acceptance/bundle/resource_deps/loop_jobs/out.deploy.direct.txt @@ -1,4 +1,2 @@ -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Error: cycle detected: resources.jobs.foo refers to resources.jobs.bar via ${resources.jobs.bar.id} which refers to resources.jobs.foo via ${resources.jobs.foo.id} -Files: 5 uploaded, 0 deleted diff --git a/acceptance/bundle/resource_deps/loop_jobs/out.deploy.terraform.txt b/acceptance/bundle/resource_deps/loop_jobs/out.deploy.terraform.txt index 365063d84df..083ada1adb3 100644 --- a/acceptance/bundle/resource_deps/loop_jobs/out.deploy.terraform.txt +++ b/acceptance/bundle/resource_deps/loop_jobs/out.deploy.terraform.txt @@ -1,8 +1,6 @@ -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Error: exit status 1 Error: Cycle: databricks_job.foo, databricks_job.bar -Files: 5 uploaded, 0 deleted diff --git a/acceptance/bundle/resource_deps/loop_self/out.deploy.direct.txt b/acceptance/bundle/resource_deps/loop_self/out.deploy.direct.txt index 84e31413888..5beba6a18fa 100644 --- a/acceptance/bundle/resource_deps/loop_self/out.deploy.direct.txt +++ b/acceptance/bundle/resource_deps/loop_self/out.deploy.direct.txt @@ -1,4 +1,2 @@ -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Error: cycle detected: resources.jobs.foo refers to itself via ${resources.jobs.foo.id} -Files: 5 uploaded, 0 deleted diff --git a/acceptance/bundle/resource_deps/loop_self/out.deploy.terraform.txt b/acceptance/bundle/resource_deps/loop_self/out.deploy.terraform.txt index 67b028a54a9..8434d76ae8a 100644 --- a/acceptance/bundle/resource_deps/loop_self/out.deploy.terraform.txt +++ b/acceptance/bundle/resource_deps/loop_self/out.deploy.terraform.txt @@ -1,4 +1,3 @@ -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Error: exit status 1 Error: Self-referential block @@ -16,4 +15,3 @@ Error: Self-referential block Configuration for databricks_job.foo may not refer to itself. -Files: 5 uploaded, 0 deleted diff --git a/acceptance/bundle/resource_deps/missing_ingestion_definition/out.deploy.terraform.txt b/acceptance/bundle/resource_deps/missing_ingestion_definition/out.deploy.terraform.txt index 44cae02948c..e603a9eb9ec 100644 --- a/acceptance/bundle/resource_deps/missing_ingestion_definition/out.deploy.terraform.txt +++ b/acceptance/bundle/resource_deps/missing_ingestion_definition/out.deploy.terraform.txt @@ -1,4 +1,3 @@ -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Error: exit status 1 Error: Invalid index @@ -12,6 +11,5 @@ The given key does not identify an element in this collection value: the collection has no elements. -Files: 6 uploaded, 0 deleted Exit code: 1 diff --git a/acceptance/bundle/resource_deps/missing_map_key/out.deploy.terraform.txt b/acceptance/bundle/resource_deps/missing_map_key/out.deploy.terraform.txt index 098c70891f0..2dd6d52d768 100644 --- a/acceptance/bundle/resource_deps/missing_map_key/out.deploy.terraform.txt +++ b/acceptance/bundle/resource_deps/missing_map_key/out.deploy.terraform.txt @@ -1,4 +1,3 @@ -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/state/default/files... Error: exit status 1 Error: Missing map element @@ -11,4 +10,3 @@ Error: Missing map element This map does not have an element with the key "missing_tag". -Files: 6 uploaded, 0 deleted diff --git a/acceptance/bundle/resource_deps/non_existent_field/out.deploy.direct.txt b/acceptance/bundle/resource_deps/non_existent_field/out.deploy.direct.txt index c117064ca6d..6a2b5d1157c 100644 --- a/acceptance/bundle/resource_deps/non_existent_field/out.deploy.direct.txt +++ b/acceptance/bundle/resource_deps/non_existent_field/out.deploy.direct.txt @@ -1,6 +1,4 @@ -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Error: cannot plan resources.volumes.foo: cannot resolve "${resources.volumes.bar.non_existent}": schema mismatch: non_existent: field "non_existent" not found in catalog.CreateVolumeRequestContent; non_existent: field "non_existent" not found in catalog.VolumeInfo Error: planning failed -Files: 5 uploaded, 0 deleted diff --git a/acceptance/bundle/resource_deps/non_existent_field/out.deploy.terraform.txt b/acceptance/bundle/resource_deps/non_existent_field/out.deploy.terraform.txt index ddea4b4a236..4df0c4bcd61 100644 --- a/acceptance/bundle/resource_deps/non_existent_field/out.deploy.terraform.txt +++ b/acceptance/bundle/resource_deps/non_existent_field/out.deploy.terraform.txt @@ -1,4 +1,3 @@ -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Error: exit status 1 Error: Unsupported attribute @@ -10,4 +9,3 @@ This object has no argument, nested block, or exported attribute named "non_existent". -Files: 5 uploaded, 0 deleted diff --git a/acceptance/bundle/resources/jobs/unknown-terraform-field/output.txt b/acceptance/bundle/resources/jobs/unknown-terraform-field/output.txt index 6a6eb192d41..0db2d1fd236 100644 --- a/acceptance/bundle/resources/jobs/unknown-terraform-field/output.txt +++ b/acceptance/bundle/resources/jobs/unknown-terraform-field/output.txt @@ -1,10 +1,10 @@ >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Warning: unknown field: autotermination_minutes at task[0].new_cluster in databricks.yml:14:13 +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Created jobs.job Files: 3 uploaded, 0 deleted Resources: 1 created, 0 changed, 0 deleted, 0 unchanged diff --git a/acceptance/bundle/resources/models/readplan-permissions/output.txt b/acceptance/bundle/resources/models/readplan-permissions/output.txt index 632ed865cb1..0e97691efa1 100644 --- a/acceptance/bundle/resources/models/readplan-permissions/output.txt +++ b/acceptance/bundle/resources/models/readplan-permissions/output.txt @@ -29,7 +29,9 @@ Resources: 2 created, 0 changed, 0 deleted, 0 unchanged } } -=== Remove manager permission and description, deploy from planUploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +=== Remove manager permission and description, deploy from plan +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Updated models.mymodel Updated models.mymodel.permissions Files: 2 uploaded, 0 deleted diff --git a/acceptance/bundle/resources/models/readplan-permissions/script b/acceptance/bundle/resources/models/readplan-permissions/script index a7a2d16db97..279856d191e 100644 --- a/acceptance/bundle/resources/models/readplan-permissions/script +++ b/acceptance/bundle/resources/models/readplan-permissions/script @@ -18,6 +18,6 @@ trace print_requests.py //permissions/registered-models title "Remove manager permission and description, deploy from plan" grep -v TO_REMOVE databricks.yml > updated.yml && mv updated.yml databricks.yml $CLI bundle plan -o json > tmp.plan.json -$CLI bundle deploy $(readplanarg tmp.plan.json) +trace $CLI bundle deploy $(readplanarg tmp.plan.json) register_model_id trace print_requests.py //permissions/registered-models diff --git a/acceptance/bundle/resources/models/readplan-permissions/test.toml b/acceptance/bundle/resources/models/readplan-permissions/test.toml index 6a843fa06d4..5203c67d0a7 100644 --- a/acceptance/bundle/resources/models/readplan-permissions/test.toml +++ b/acceptance/bundle/resources/models/readplan-permissions/test.toml @@ -3,3 +3,7 @@ Ignore = [".databricks", "tmp.plan.json"] EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] EnvMatrix.READPLAN = ["", "1"] + +[[Repls]] +Old = ' --plan tmp\.plan\.json' +New = '' diff --git a/acceptance/bundle/resources/postgres_branches/without_branch_id/out.deploy.terraform.txt b/acceptance/bundle/resources/postgres_branches/without_branch_id/out.deploy.terraform.txt index 169f720c9eb..d59ae0d946e 100644 --- a/acceptance/bundle/resources/postgres_branches/without_branch_id/out.deploy.terraform.txt +++ b/acceptance/bundle/resources/postgres_branches/without_branch_id/out.deploy.terraform.txt @@ -2,7 +2,6 @@ Warning: required field "branch_id" is not set at resources.postgres_branches.main in databricks.yml:21:7 -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-branch-no-id-[UNIQUE_NAME]/default/files... Error: exit status 1 Error: Missing required argument @@ -13,4 +12,3 @@ Error: Missing required argument The argument "branch_id" is required, but no definition was found. -Files: 0 uploaded, 0 deleted diff --git a/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/out.deploy.terraform.txt b/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/out.deploy.terraform.txt index 1b6eb357abf..87eac5f6ddd 100644 --- a/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/out.deploy.terraform.txt +++ b/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/out.deploy.terraform.txt @@ -2,7 +2,6 @@ Warning: required field "endpoint_id" is not set at resources.postgres_endpoints.primary in databricks.yml:27:7 -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-endpoint-no-id-[UNIQUE_NAME]/default/files... Error: exit status 1 Error: Missing required argument @@ -13,4 +12,3 @@ Error: Missing required argument The argument "endpoint_id" is required, but no definition was found. -Files: 0 uploaded, 0 deleted diff --git a/acceptance/bundle/resources/postgres_projects/without_project_id/out.deploy.terraform.txt b/acceptance/bundle/resources/postgres_projects/without_project_id/out.deploy.terraform.txt index 8068586bd85..9acbb916ab1 100644 --- a/acceptance/bundle/resources/postgres_projects/without_project_id/out.deploy.terraform.txt +++ b/acceptance/bundle/resources/postgres_projects/without_project_id/out.deploy.terraform.txt @@ -2,7 +2,6 @@ Warning: required field "project_id" is not set at resources.postgres_projects.my_project in databricks.yml:10:7 -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-no-id-[UNIQUE_NAME]/default/files... Error: exit status 1 Error: Missing required argument @@ -13,4 +12,3 @@ Error: Missing required argument The argument "project_id" is required, but no definition was found. -Files: 0 uploaded, 0 deleted diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 8720a57e930..d355bfb8ddf 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -283,9 +283,9 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand // IsDirect first: the state must be open to read its features, and only the direct engine opens it. if stateEngine.IsDirect() && b.DeploymentBundle.StateDB.IsDeploymentMetadataService() { firstDeploy := b.DeploymentBundle.StateDB.DeploymentID == "" - if firstDeploy { + if firstDeploy && !b.Config.Bundle.Deployment.Lock.IsEnabled() { // StatePush used to create this parent before registering the deployment. - // Create it explicitly, including when workspace locking is disabled. + // With locking enabled, lock acquisition has already created it. if err := b.WorkspaceClient(ctx).Workspace.MkdirsByPath(ctx, b.Config.Workspace.StatePath); err != nil { logdiag.LogError(ctx, err) return From f8112747fab71b4caf435dcb496f29a162330f9e Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 16 Sep 2026 14:59:29 +0000 Subject: [PATCH 5/5] test: normalize periodic DMS heartbeats in request recordings --- acceptance/bin/print_requests.py | 33 ++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/acceptance/bin/print_requests.py b/acceptance/bin/print_requests.py index 728db3ec1a8..c072ffd8f1f 100755 --- a/acceptance/bin/print_requests.py +++ b/acceptance/bin/print_requests.py @@ -76,6 +76,14 @@ ... ] >>> [x["method"] for x in filter_requests(seq, ["//idx"], True, False, unique=True)] ['GET', 'DELETE', 'GET'] + +>>> # Periodic lease renewals vary with deployment duration; inspect them explicitly. +>>> heartbeat = {"method": "POST", "path": "/api/2.0/bundle/deployments/123/versions/1/heartbeat"} +>>> complete = {"method": "POST", "path": "/api/2.0/bundle/deployments/123/versions/1/complete"} +>>> filter_requests([heartbeat, complete], [], False, False, include_dms=True) == [complete] +True +>>> filter_requests([heartbeat], [], False, False, include_dms=True, include_heartbeats=True) == [heartbeat] +True """ import argparse @@ -137,7 +145,14 @@ def read_json_many(s): def filter_requests( - requests, path_filters, include_get, should_sort, unique=False, method_filter=None, include_dms=False + requests, + path_filters, + include_get, + should_sort, + unique=False, + method_filter=None, + include_dms=False, + include_heartbeats=False, ): """Filter requests based on method and path filters.""" positive_filters = [] @@ -170,6 +185,15 @@ def filter_requests( # Apply path filters path = req.get("path", "") + # A slow cloud deployment can renew its lease several times while the local + # fake finishes before the first tick. Heartbeat tests opt in explicitly. + if ( + not include_heartbeats + and req.get("method") == "POST" + and path.startswith(DMS_PATH + "/deployments/") + and path.endswith("/heartbeat") + ): + continue should_include = True # Check positive filters - if any exist, at least one must match (OR logic) @@ -222,6 +246,11 @@ def main(): help="Include deployment-history requests (excluded by default; see filter_requests)", ) parser.add_argument("--keep", action="store_true", help="Keep out.requests.json file after processing") + parser.add_argument( + "--heartbeats", + action="store_true", + help="Include periodic DMS heartbeat requests (use with --dms)", + ) parser.add_argument("--sort", action="store_true", help="Sort requests before output") parser.add_argument( "--unique", @@ -281,7 +310,7 @@ def main(): requests = read_json_many(data) filtered_requests = filter_requests( - requests, args.path_filters, args.get, args.sort, args.unique, args.method, args.dms + requests, args.path_filters, args.get, args.sort, args.unique, args.method, args.dms, args.heartbeats ) for req in filtered_requests: