Skip to content

Record deployment history for bundle resource unbindings - #7401

Draft
shreyas-goenka wants to merge 6 commits into
shreyas-goenka_data/shreyas-goenka/record-bind-historyfrom
shreyas-goenka_data/shreyas-goenka/record-unbind-history
Draft

shreyas-goenka wants to merge 6 commits into
shreyas-goenka_data/shreyas-goenka/record-bind-historyfrom
shreyas-goenka_data/shreyas-goenka/record-unbind-history

Conversation

@shreyas-goenka

@shreyas-goenka shreyas-goenka commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Record bundle deployment unbind as UNBIND operations for the resource and each removed child state entry. Skip history versions when no binding was removed and leave workspace resources intact.

The engine returns optional DMS metadata containing the sorted removed keys and committed serial. The phase shares SDK version creation and completion with bind; it does not reopen StateDB or add state writes. History failures warn, and failed state uploads complete the version as failed.

Stacked on #7400.

Tests

  • Focused removed-key unit test passes, including orphan children, returned serial, and nil metadata when history is disabled.
  • bind-unbind and unbind-history-failures acceptance cases pass.
  • Self-review using the repository's PR review skill; diff checks pass.
  • Full CI passes on Linux, macOS, and Windows with history enabled and disabled. Cloud acceptance tests pass on AWS, Azure, and GCP on Linux and Windows.

This PR was written by Codex.

@shreyas-goenka
shreyas-goenka force-pushed the shreyas-goenka_data/shreyas-goenka/record-unbind-history branch from 6247b4f to 1ea379e Compare November 9, 2026 08:01
@shreyas-goenka
shreyas-goenka force-pushed the shreyas-goenka_data/shreyas-goenka/record-bind-history branch from 938294b to 2c7cf79 Compare November 9, 2026 08:08
@shreyas-goenka
shreyas-goenka force-pushed the shreyas-goenka_data/shreyas-goenka/record-unbind-history branch from 1ea379e to d74b071 Compare November 9, 2026 08:08
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: 6f52001

Run: 42813599821

Env ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 281 19 6:56
✅​ aws-windows-latest-4core-16gb 283 17 9:38
✅​ azure linux-2core-8gb 280 19 8:26
✅​ azure-windows-latest-4core-16gb 282 17 9:49
✅​ gcp linux-2core-8gb 281 19 6:50
✅​ gcp-windows-latest-4core-16gb 283 17 8:55
Top 9 slowest tests (at least 2 minutes):
duration env testname
7:07 gcp-windows-latest-4core-16gb TestAccept
6:49 azure-windows-latest-4core-16gb TestAccept
6:37 aws-windows-latest-4core-16gb TestAccept
3:51 gcp linux-2core-8gb TestAccept
3:51 aws linux-2core-8gb TestAccept
3:49 azure linux-2core-8gb TestAccept
2:13 azure-windows-latest-4core-16gb TestAccept/bundle/deployment/bind/dashboard/recreation/DMS=/READPLAN=
2:01 azure-windows-latest-4core-16gb TestAccept/bundle/dms/bind-unbind/DMS=true
2:00 aws-windows-latest-4core-16gb TestAccept/bundle/dms/bind-unbind/DMS=true

@shreyas-goenka
shreyas-goenka force-pushed the shreyas-goenka_data/shreyas-goenka/record-unbind-history branch from d74b071 to f75d31f Compare November 9, 2026 09:21
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Commit: 6f52001eb | Workflow run

Regression test report

Selected 7 changed tests (limit=20, 0 not selected)

12 tests: regression=4 unreleased=0 coverage=8 failing=0
test branch main (33bcce3) latest (v1.25.0)
TestAccept/bundle/deployment/bind/dashboard/recreation/DMS=/READPLAN= ✅ ✅ ✅
TestAccept/bundle/deployment/bind/dashboard/recreation/DMS=/READPLAN=1 ✅ ✅ ✅
TestAccept/bundle/deployment/bind/dashboard/recreation/DMS=true/READPLAN= ✅ ✅ ✅
TestAccept/bundle/deployment/bind/dashboard/recreation/DMS=true/READPLAN=1 ✅ ✅ ✅
TestAccept/bundle/deployment/bind/pipelines/recreate/DMS= ✅ ✅ ✅
TestAccept/bundle/deployment/bind/pipelines/recreate/DMS=true ✅ ✅ ✅
TestAccept/bundle/deployment/bind/pipelines/update/DMS= ✅ ✅ ✅
TestAccept/bundle/deployment/bind/pipelines/update/DMS=true ✅ ✅ ✅
TestAccept/bundle/dms/bind-history-failures/DMS=true ✅ ❌ ❌
TestAccept/bundle/dms/bind-immutable/DMS=true ✅ ❌ ❌
TestAccept/bundle/dms/bind-unbind/DMS=true ✅ ❌ ❌
TestAccept/bundle/dms/unbind-history-failures/DMS=true ✅ ❌ ❌
TestAccept/bundle/dms/bind-history-failures/DMS=true: branch ✅ | main (33bcce3) ❌ | latest (v1.25.0) ❌

main (33bcce3):

acceptance_test.go:1345: Diff:
        --- bundle/dms/bind-history-failures/output.txt
        +++ /tmp/TestAcceptbundledmsbind-history-failuresDMS=true1812686010/002/output.txt
        @@ -3,7 +3,6 @@
         >>> fault.py POST /api/2.0/bundle/deployments/*/versions 400 0 1
         
         >>> [CLI] bundle deployment bind create_failed [JOB_ID] --auto-approve
        -Warn: Failed to record bind history: failed to create deployment version: Fault injected by test.
         Successfully bound job with an id '[JOB_ID]'
         Run 'bundle deploy' to deploy changes to your workspace
         
        @@ -11,7 +10,6 @@
         >>> fault.py PATCH /api/2.0/bundle/** 400 0 1
         
         >>> [CLI] bundle deployment bind record_failed [JOB_ID] --auto-approve
        -Warn: Failed to record bind history: Fault injected by test.
         Successfully bound job with an id '[JOB_ID]'
         Run 'bundle deploy' to deploy changes to your workspace
         
        @@ -19,7 +17,6 @@
         >>> fault.py POST /api/2.0/bundle/deployments/*/versions/*/complete 400 0 1
         
         >>> [CLI] bundle deployment bind complete_failed [JOB_ID] --auto-approve
        -Warn: Failed to complete deployment history version: Fault injected by test.
         Successfully bound job with an id '[JOB_ID]'
         Run 'bundle deploy' to deploy changes to your workspace
         
        @@ -38,13 +35,6 @@
         }
         
         >>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "3"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_BIND", "resource_key": "jobs.create_failed"}], "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "5"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_BIND", "resource_key": "jobs.record_failed"}], "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/5/operations/jobs.record_failed", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "[JOB_ID]", "sequence_id": 0, "state": "{\"state\":{\"deployment\":{\"kind\":\"BUNDLE\",\"metadata_file_path\":\"/Workspace/Users/[USERNAME]/.bundle/dms-bind-history-failures-[UNIQUE_NAME]/default/state/metadata.json\"},\"edit_mode\":\"UI_LOCKED\",\"format\":\"MULTI_TASK\",\"max_concurrent_runs\":1,\"name\":\"record_failed\",\"queue\":{\"enabled\":true}}}", "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/5/complete", "body": {"completion_reason": "VERSION_COMPLETE_FAILURE"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "7"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_BIND", "resource_key": "jobs.complete_failed"}], "previous_version_id": "5", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/7/operations/jobs.complete_failed", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "[JOB_ID]", "sequence_id": 0, "state": "{\"state\":{\"deployment\":{\"kind\":\"BUNDLE\",\"metadata_file_path\":\"/Workspace/Users/[USERNAME]/.bundle/dms-bind-history-failures-[UNIQUE_NAME]/default/state/metadata.json\"},\"edit_mode\":\"UI_LOCKED\",\"format\":\"MULTI_TASK\",\"max_concurrent_runs\":1,\"name\":\"complete_failed\",\"queue\":{\"enabled\":true}}}", "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/7/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
         
         === A remote state upload failure fails the bind version
         >>> fault.py POST /api/2.0/workspace-files/import-file/**resources.json 400 0 1
        @@ -59,9 +49,6 @@
         
         
         >>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "9"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_BIND", "resource_key": "jobs.upload_failed"}], "previous_version_id": "7", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/9/operations/jobs.upload_failed", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "[JOB_ID]", "sequence_id": 0, "state": "{\"state\":{\"deployment\":{\"kind\":\"BUNDLE\",\"metadata_file_path\":\"/Workspace/Users/[USERNAME]/.bundle/dms-bind-history-failures-[UNIQUE_NAME]/default/state/metadata.json\"},\"edit_mode\":\"UI_LOCKED\",\"format\":\"MULTI_TASK\",\"max_concurrent_runs\":1,\"name\":\"upload_failed\",\"queue\":{\"enabled\":true}}}", "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/9/complete", "body": {"completion_reason": "VERSION_COMPLETE_FAILURE"}}
         
         === Remote state still contains only the successful bindings
         >>> [CLI] bundle summary -o json

latest (v1.25.0): same output as main (33bcce3)

TestAccept/bundle/dms/bind-immutable/DMS=true: branch ✅ | main (33bcce3) ❌ | latest (v1.25.0) ❌

main (33bcce3):

acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n>>> [CLI] bundle deployment bind foo [JOB_ID] --auto-approve\nSuccessfully bound job with an id '[JOB_ID]'\nRun 'bundle deploy' to deploy changes to your workspace\n\n>>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json\n[\n  {\n    \"last_action_type\": \"OPERATION_ACTION_TYPE_BIND\",\n    \"resource_id\": \"[JOB_ID]\",\n    \"resource_key\": \"jobs.foo\"\n  }\n]\n"
        	            	actual  : "\n>>> [CLI] bundle deployment bind foo [JOB_ID] --auto-approve\nSuccessfully bound job with an id '[JOB_ID]'\nRun 'bundle deploy' to deploy changes to your workspace\nError: Path (/Workspace/Users/[USERNAME]/.bundle/dms-bind-immutable-[UNIQUE_NAME]/default/state/resources.deployment.json) doesn't exist.\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -4,11 +4,5 @@
        	            	 Run 'bundle deploy' to deploy changes to your workspace
        	            	+Error: Path (/Workspace/Users/[USERNAME]/.bundle/dms-bind-immutable-[UNIQUE_NAME]/default/state/resources.deployment.json) doesn't exist.
        	            	 
        	            	->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
        	            	-[
        	            	-  {
        	            	-    "last_action_type": "OPERATION_ACTION_TYPE_BIND",
        	            	-    "resource_id": "[JOB_ID]",
        	            	-    "resource_key": "jobs.foo"
        	            	-  }
        	            	-]
        	            	+Exit code: 1
        	            	 
        	Test:       	TestAccept/bundle/dms/bind-immutable/DMS=true
        	Messages:   	bundle/dms/bind-immutable/output.txt vs /tmp/TestAcceptbundledmsbind-immutableDMS=true2911638858/002/output.txt

latest (v1.25.0): same output as main (33bcce3)

TestAccept/bundle/dms/bind-unbind/DMS=true: branch ✅ | main (33bcce3) ❌ | latest (v1.25.0) ❌

main (33bcce3):

acceptance_test.go:1345: Diff:
        --- bundle/dms/bind-unbind/output.txt
        +++ /tmp/TestAcceptbundledmsbind-unbindDMS=true3686828141/002/output.txt
        @@ -19,109 +19,12 @@
         Run 'bundle deploy' to deploy changes to your workspace
         
         >>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "3"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_BIND", "resource_key": "jobs.foo"}], "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/3/operations/jobs.foo", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "[JOB_ID]", "sequence_id": 0, "state": "{\"state\":{\"deployment\":{\"kind\":\"BUNDLE\",\"metadata_file_path\":\"/Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/state/metadata.json\"},\"edit_mode\":\"UI_LOCKED\",\"format\":\"MULTI_TASK\",\"max_concurrent_runs\":1,\"name\":\"foo\",\"queue\":{\"enabled\":true}}}", "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/3/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
         
         >>> [CLI] jobs get [JOB_ID]
         {
           "job_id": [JOB_ID],
           "name": "bind-target-[UNIQUE_NAME]"
         }
        +Error: Path (/Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/state/resources.deployment.json) doesn't exist.
         
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
        -[
        -  {
        -    "last_action_type": "OPERATION_ACTION_TYPE_BIND",
        -    "resource_id": "[JOB_ID]",
        -    "resource_key": "jobs.foo"
        -  }
        -]
        -
        -=== Deploy keeps the bound job ID with recorded history
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/files...
        -Created jobs.foo.permissions
        -Updated jobs.foo
        -Files: 2 uploaded, 0 deleted
        -Resources: 1 created, 1 changed, 0 deleted, 0 unchanged
        -
        ->>> jq {features, resource_id: .state["resources.jobs.foo"].__id__} .databricks/bundle/default/resources.json
        -{
        -  "features": {
        -    "deployment_history_shadow": {}
        -  },
        -  "resource_id": "[JOB_ID]"
        -}
        -
        -=== Unbind preserves the shadow history marker and leaves the job intact
        ->>> [CLI] bundle deployment unbind foo
        -
        ->>> print_requests.py --dms //versions //jobs //permissions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "5"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.foo"}, {"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.foo.permissions"}], "previous_version_id": "4", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/5/operations/jobs.foo", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/5/operations/jobs.foo.permissions", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/5/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
        -
        ->>> jq {features, state} .databricks/bundle/default/resources.json
        -{
        -  "features": {
        -    "deployment_history_shadow": {}
        -  },
        -  "state": {}
        -}
        -
        ->>> [CLI] jobs get [JOB_ID]
        -{
        -  "job_id": [JOB_ID]
        -}
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
        -[]
        -
        -=== Unbinding an already unbound resource creates no history version
        ->>> [CLI] bundle deployment unbind foo
        -
        ->>> print_requests.py --dms //versions
        -
        -=== Rebind preserves the existing shadow history marker
        ->>> [CLI] bundle deployment bind foo [JOB_ID] --auto-approve
        -Successfully bound job with an id '[JOB_ID]'
        -Run 'bundle deploy' to deploy changes to your workspace
        -
        ->>> jq {features, resource_id: .state["resources.jobs.foo"].__id__} .databricks/bundle/default/resources.json
        -{
        -  "features": {
        -    "deployment_history_shadow": {}
        -  },
        -  "resource_id": "[JOB_ID]"
        -}
        -
        ->>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "8"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_BIND", "resource_key": "jobs.foo"}], "previous_version_id": "5", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/8/operations/jobs.foo", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "[JOB_ID]", "sequence_id": 0, "state": "{\"state\":{\"deployment\":{\"kind\":\"BUNDLE\",\"metadata_file_path\":\"/Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/state/metadata.json\"},\"edit_mode\":\"UI_LOCKED\",\"format\":\"MULTI_TASK\",\"max_concurrent_runs\":1,\"name\":\"foo\",\"queue\":{\"enabled\":true}}}", "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/8/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
        -
        -=== A subsequent deploy continues managing the same job
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/files...
        -Created jobs.foo.permissions
        -Files: 0 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 0 deleted, 1 unchanged
        -
        ->>> jq {features, resource_id: .state["resources.jobs.foo"].__id__} .databricks/bundle/default/resources.json
        -{
        -  "features": {
        -    "deployment_history_shadow": {}
        -  },
        -  "resource_id": "[JOB_ID]"
        -}
        -
        -=== A rejected duplicate bind creates no history version
        ->>> musterr [CLI] bundle deployment bind foo [JOB_ID] --auto-approve
        -Error: Resource already managed
        -
        -The bundle is already managing resource for resources.jobs.foo and it is bound to the requested ID [JOB_ID].
        -
        -
        ->>> print_requests.py --dms //versions
        +Exit code: 1
        
    acceptance_test.go:1123: 
        LOG.cleanup: The following resources will be deleted:
        LOG.cleanup:   delete resources.jobs.foo
        LOG.cleanup: 
        LOG.cleanup: All files and directories at the following location will be deleted: /Workspace/Users/[email protected]/.bundle/dms-bind-unbind-hqhhuzl7ffgofovpgngibpr44a/default
        LOG.cleanup: 
        LOG.cleanup: Destroy: 1 deleted
        LOG.cleanup: Error: Not Found
        LOG.cleanup: 
        LOG.cleanup: Exit code: 1

latest (v1.25.0):

acceptance_test.go:1345: Diff:
        --- bundle/dms/bind-unbind/output.txt
        +++ /tmp/TestAcceptbundledmsbind-unbindDMS=true2078480589/002/output.txt
        @@ -19,109 +19,12 @@
         Run 'bundle deploy' to deploy changes to your workspace
         
         >>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "3"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_BIND", "resource_key": "jobs.foo"}], "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/3/operations/jobs.foo", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "[JOB_ID]", "sequence_id": 0, "state": "{\"state\":{\"deployment\":{\"kind\":\"BUNDLE\",\"metadata_file_path\":\"/Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/state/metadata.json\"},\"edit_mode\":\"UI_LOCKED\",\"format\":\"MULTI_TASK\",\"max_concurrent_runs\":1,\"name\":\"foo\",\"queue\":{\"enabled\":true}}}", "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/3/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
         
         >>> [CLI] jobs get [JOB_ID]
         {
           "job_id": [JOB_ID],
           "name": "bind-target-[UNIQUE_NAME]"
         }
        +Error: Path (/Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/state/resources.deployment.json) doesn't exist.
         
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
        -[
        -  {
        -    "last_action_type": "OPERATION_ACTION_TYPE_BIND",
        -    "resource_id": "[JOB_ID]",
        -    "resource_key": "jobs.foo"
        -  }
        -]
        -
        -=== Deploy keeps the bound job ID with recorded history
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/files...
        -Created jobs.foo.permissions
        -Updated jobs.foo
        -Files: 2 uploaded, 0 deleted
        -Resources: 1 created, 1 changed, 0 deleted, 0 unchanged
        -
        ->>> jq {features, resource_id: .state["resources.jobs.foo"].__id__} .databricks/bundle/default/resources.json
        -{
        -  "features": {
        -    "deployment_history_shadow": {}
        -  },
        -  "resource_id": "[JOB_ID]"
        -}
        -
        -=== Unbind preserves the shadow history marker and leaves the job intact
        ->>> [CLI] bundle deployment unbind foo
        -
        ->>> print_requests.py --dms //versions //jobs //permissions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "5"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.foo"}, {"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.foo.permissions"}], "previous_version_id": "4", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/5/operations/jobs.foo", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/5/operations/jobs.foo.permissions", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/5/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
        -
        ->>> jq {features, state} .databricks/bundle/default/resources.json
        -{
        -  "features": {
        -    "deployment_history_shadow": {}
        -  },
        -  "state": {}
        -}
        -
        ->>> [CLI] jobs get [JOB_ID]
        -{
        -  "job_id": [JOB_ID]
        -}
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
        -[]
        -
        -=== Unbinding an already unbound resource creates no history version
        ->>> [CLI] bundle deployment unbind foo
        -
        ->>> print_requests.py --dms //versions
        -
        -=== Rebind preserves the existing shadow history marker
        ->>> [CLI] bundle deployment bind foo [JOB_ID] --auto-approve
        -Successfully bound job with an id '[JOB_ID]'
        -Run 'bundle deploy' to deploy changes to your workspace
        -
        ->>> jq {features, resource_id: .state["resources.jobs.foo"].__id__} .databricks/bundle/default/resources.json
        -{
        -  "features": {
        -    "deployment_history_shadow": {}
        -  },
        -  "resource_id": "[JOB_ID]"
        -}
        -
        ->>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "8"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_BIND", "resource_key": "jobs.foo"}], "previous_version_id": "5", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/8/operations/jobs.foo", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "[JOB_ID]", "sequence_id": 0, "state": "{\"state\":{\"deployment\":{\"kind\":\"BUNDLE\",\"metadata_file_path\":\"/Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/state/metadata.json\"},\"edit_mode\":\"UI_LOCKED\",\"format\":\"MULTI_TASK\",\"max_concurrent_runs\":1,\"name\":\"foo\",\"queue\":{\"enabled\":true}}}", "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/8/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
        -
        -=== A subsequent deploy continues managing the same job
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-bind-unbind-[UNIQUE_NAME]/default/files...
        -Created jobs.foo.permissions
        -Files: 0 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 0 deleted, 1 unchanged
        -
        ->>> jq {features, resource_id: .state["resources.jobs.foo"].__id__} .databricks/bundle/default/resources.json
        -{
        -  "features": {
        -    "deployment_history_shadow": {}
        -  },
        -  "resource_id": "[JOB_ID]"
        -}
        -
        -=== A rejected duplicate bind creates no history version
        ->>> musterr [CLI] bundle deployment bind foo [JOB_ID] --auto-approve
        -Error: Resource already managed
        -
        -The bundle is already managing resource for resources.jobs.foo and it is bound to the requested ID [JOB_ID].
        -
        -
        ->>> print_requests.py --dms //versions
        +Exit code: 1
        
    acceptance_test.go:1123: 
        LOG.cleanup: The following resources will be deleted:
        LOG.cleanup:   delete resources.jobs.foo
        LOG.cleanup: 
        LOG.cleanup: All files and directories at the following location will be deleted: /Workspace/Users/[email protected]/.bundle/dms-bind-unbind-d64jxsghb5fizm5oj5hjkkoo5i/default
        LOG.cleanup: 
        LOG.cleanup: Destroy: 1 deleted
        LOG.cleanup: Error: Not Found
        LOG.cleanup: 
        LOG.cleanup: Exit code: 1
TestAccept/bundle/dms/unbind-history-failures/DMS=true: branch ✅ | main (33bcce3) ❌ | latest (v1.25.0) ❌

main (33bcce3):

acceptance_test.go:1345: Diff:
        --- bundle/dms/unbind-history-failures/output.txt
        +++ /tmp/TestAcceptbundledmsunbind-history-failuresDMS=true3296314330/002/output.txt
        @@ -3,19 +3,16 @@
         >>> fault.py POST /api/2.0/bundle/deployments/*/versions 400 0 1
         
         >>> [CLI] bundle deployment unbind create_failed
        -Warn: Failed to record unbind history: failed to create deployment version: Fault injected by test.
         
         === An operation recording failure does not prevent unbinding
         >>> fault.py PATCH /api/2.0/bundle/** 400 0 1
         
         >>> [CLI] bundle deployment unbind record_failed
        -Warn: Failed to record unbind history: Fault injected by test.
         
         === A version completion failure does not prevent unbinding
         >>> fault.py POST /api/2.0/bundle/deployments/*/versions/*/complete 400 0 1
         
         >>> [CLI] bundle deployment unbind complete_failed
        -Warn: Failed to complete deployment history version: Fault injected by test.
         
         === Successful unbindings survived the remote state round trips and the job still exists
         >>> jq .state .databricks/bundle/default/resources.json
        @@ -45,13 +32,6 @@
         }
         
         >>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "10"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.create_failed"}], "previous_version_id": "9", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "11"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.record_failed"}], "previous_version_id": "9", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/11/operations/jobs.record_failed", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/11/complete", "body": {"completion_reason": "VERSION_COMPLETE_FAILURE"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "12"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.complete_failed"}], "previous_version_id": "11", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/12/operations/jobs.complete_failed", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/12/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
         
         === A remote state upload failure fails the unbind version
         >>> fault.py POST /api/2.0/workspace-files/import-file/**resources.json 400 0 1
        @@ -66,9 +46,6 @@
         
         
         >>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "13"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.upload_failed"}], "previous_version_id": "12", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/13/operations/jobs.upload_failed", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/13/complete", "body": {"completion_reason": "VERSION_COMPLETE_FAILURE"}}
         
         === Remote state still contains the binding whose upload failed
         >>> [CLI] bundle summary -o json
        
    acceptance_test.go:1123: 
        LOG.setup: Successfully bound job with an id '8794313103588299846'
        LOG.setup: Run 'bundle deploy' to deploy changes to your workspace
        LOG.setup: Successfully bound job with an id '8794313103588299846'
        LOG.setup: Run 'bundle deploy' to deploy changes to your workspace
        LOG.setup: Successfully bound job with an id '8794313103588299846'
        LOG.setup: Run 'bundle deploy' to deploy changes to your workspace
        LOG.setup: Successfully bound job with an id '8794313103588299846'
        LOG.setup: Run 'bundle deploy' to deploy changes to your workspace

latest (v1.25.0):

acceptance_test.go:1345: Diff:
        --- bundle/dms/unbind-history-failures/output.txt
        +++ /tmp/TestAcceptbundledmsunbind-history-failuresDMS=true2368319820/002/output.txt
        @@ -3,19 +3,16 @@
         >>> fault.py POST /api/2.0/bundle/deployments/*/versions 400 0 1
         
         >>> [CLI] bundle deployment unbind create_failed
        -Warn: Failed to record unbind history: failed to create deployment version: Fault injected by test.
         
         === An operation recording failure does not prevent unbinding
         >>> fault.py PATCH /api/2.0/bundle/** 400 0 1
         
         >>> [CLI] bundle deployment unbind record_failed
        -Warn: Failed to record unbind history: Fault injected by test.
         
         === A version completion failure does not prevent unbinding
         >>> fault.py POST /api/2.0/bundle/deployments/*/versions/*/complete 400 0 1
         
         >>> [CLI] bundle deployment unbind complete_failed
        -Warn: Failed to complete deployment history version: Fault injected by test.
         
         === Successful unbindings survived the remote state round trips and the job still exists
         >>> jq .state .databricks/bundle/default/resources.json
        @@ -45,13 +32,6 @@
         }
         
         >>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "10"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.create_failed"}], "previous_version_id": "9", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "11"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.record_failed"}], "previous_version_id": "9", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/11/operations/jobs.record_failed", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/11/complete", "body": {"completion_reason": "VERSION_COMPLETE_FAILURE"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "12"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.complete_failed"}], "previous_version_id": "11", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/12/operations/jobs.complete_failed", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/12/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
         
         === A remote state upload failure fails the unbind version
         >>> fault.py POST /api/2.0/workspace-files/import-file/**resources.json 400 0 1
        @@ -66,9 +46,6 @@
         
         
         >>> print_requests.py --dms //versions --oneline
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "13"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_UNBIND", "resource_key": "jobs.upload_failed"}], "previous_version_id": "12", "version_type": "VERSION_TYPE_DEPLOY"}}
        -{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/13/operations/jobs.upload_failed", "q": {"update_mask": "state,error_message,status"}, "body": {"error_message": "", "sequence_id": 0, "status": "OPERATION_STATUS_SUCCEEDED"}}
        -{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/13/complete", "body": {"completion_reason": "VERSION_COMPLETE_FAILURE"}}
         
         === Remote state still contains the binding whose upload failed
         >>> [CLI] bundle summary -o json
        
    acceptance_test.go:1123: 
        LOG.setup: Successfully bound job with an id '8794313115129707118'
        LOG.setup: Run 'bundle deploy' to deploy changes to your workspace
        LOG.setup: Successfully bound job with an id '8794313115129707118'
        LOG.setup: Run 'bundle deploy' to deploy changes to your workspace
        LOG.setup: Successfully bound job with an id '8794313115129707118'
        LOG.setup: Run 'bundle deploy' to deploy changes to your workspace
        LOG.setup: Successfully bound job with an id '8794313115129707118'
        LOG.setup: Run 'bundle deploy' to deploy changes to your workspace

@shreyas-goenka
shreyas-goenka force-pushed the shreyas-goenka_data/shreyas-goenka/record-unbind-history branch 19 times, most recently from 624cf32 to 2017d94 Compare November 10, 2026 11:10
@shreyas-goenka
shreyas-goenka force-pushed the shreyas-goenka_data/shreyas-goenka/record-unbind-history branch from 2017d94 to 6f52001 Compare November 10, 2026 12:10

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants