Skip to content

Support DMS migration during bundle deploy - #6989

Open
shreyas-goenka wants to merge 25 commits into
mainfrom
shreyas-goenka_data/shreyas-goenka/dms-deploy-migration
Open

shreyas-goenka wants to merge 25 commits into
mainfrom
shreyas-goenka_data/shreyas-goenka/dms-deploy-migration

Conversation

@shreyas-goenka

@shreyas-goenka shreyas-goenka commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Enabling experimental.deployment_history for an existing bundle target will automatically migrate the bundle.

  • Run migration as a separate, committed phase before normal deployment: select state, register resources, upload the history marker, then reopen state from DMS before planning.
  • Back up remote resources.json to resources.json.backup before migration.
  • Register resource IDs, state, and dependencies from the normally selected state with INITIAL_REGISTER in a dedicated version, then apply deployment changes in a subsequent version.
  • Start registration at max(state serial, latest DMS version) + 1. Re-register when the selected state has no deployment-history marker, including repeated migration.
  • Print separate migration and deployment version links. Saved direct-state plans remain unsupported during migration.
  • Terraform state must first be migrated to direct with deployment history disabled. Combined Terraform-to-direct-to-DMS migration is deferred to GA.

Migration uses the already-selected, open state and acquires its own deployment lock only when migration is needed. Once committed, it remains committed even if the subsequent deployment fails planning or is declined. Normal deployment has no pending-migration object or migration-specific plan patching. Read-only plan commands do not commit migration.

Tests

  • Acceptance coverage for initial/repeated migration, backups, IDs/dependencies, rereading DMS before planning, and independent migration commit across planning failure or declined approval.
  • Scoped Go tests, lint, and the full local DMS, migration, and state acceptance groups pass.

@github-actions github-actions Bot added the DABs DABs related issues label Oct 8, 2026
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: d97a698

Run: 39418374637

Env ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 284 17 4:26
✅​ aws-windows-latest-4core-16gb 286 15 6:39
✅​ azure linux-2core-8gb 283 17 4:22
✅​ azure-windows-latest-4core-16gb 285 15 6:47
✅​ gcp linux-2core-8gb 284 17 4:26
✅​ gcp-windows-latest-4core-16gb 286 15 6:01
Top 3 slowest tests (at least 2 minutes):
duration env testname
6:02 aws-windows-latest-4core-16gb TestAccept
6:02 azure-windows-latest-4core-16gb TestAccept
5:11 gcp-windows-latest-4core-16gb TestAccept

@shreyas-goenka
shreyas-goenka marked this pull request as ready for review October 12, 2026 15:27
@shreyas-goenka
shreyas-goenka requested review from a team as code owners October 12, 2026 15:27
Comment thread cmd/bundle/debug/states.go Outdated
@denik

denik commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Adds support for disabling DMS when experimental.deployment_history: false is set, same as bundle.direct.

This seems a bit narrow? In direct we have env var and config setting. Config setting is consulted first, then env var, then state-derived, then the default.

Important also that state is sticky, so if you are on DMS and neither config nor env var are set, you still remain on DMS and not migrated back. Do we have that?

The config setting/env var/stickiness is complex enough so that it warrants its own PR, what do you think?

Comment thread bundle/statemgmt/state_pull.go
@shreyas-goenka
shreyas-goenka changed the base branch from main to shreyas-goenka_data/shreyas-goenka/dms-serial-stack October 14, 2026 12:22
@shreyas-goenka
shreyas-goenka added this pull request to stack #7047 October 14, 2026 12:22
Base automatically changed from shreyas-goenka_data/shreyas-goenka/dms-serial-stack to main October 19, 2026 11:33
@shreyas-goenka
shreyas-goenka force-pushed the shreyas-goenka_data/shreyas-goenka/dms-deploy-migration branch from f90499d to bc86051 Compare October 19, 2026 12:30
@shreyas-goenka
shreyas-goenka requested a review from denik October 19, 2026 13:22
Comment thread bundle/phases/deploy.go Outdated
musterr trace DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true $CLI bundle deploy
trace print_requests.py --dms //api/2.0/bundle --oneline
title "Normal state selection prefers local serial 2 over remote serial 1"
trace update_file.py .databricks/bundle/default/resources.json '"serial": 1' '"serial": 2'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we have this serial update here? what its purpose, does migration below not work without it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it absolutely does. This just simulates that if local state is ahead, the local state is used and thus the version created us at version 3.

I can see this test being a bit hard to read, let me simplify this just to the core assertions.

Comment thread acceptance/bundle/dms/repeated-migration/script Outdated
Comment thread bundle/phases/deploy.go
// Upgrade from read (opened by process.go, or by Migrate) to write mode. After approval
// and the migration commit above, so a declined deploy never opens a WAL it must discard,
// and the migration's tf+1 push lands before this advances the WAL header to tf+2.
if err := b.DeploymentBundle.StateDB.UpgradeToWrite(); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this if we're on DMS?

@shreyas-goenka shreyas-goenka Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah because of the db.AssertOpenedForWrite() assertions we have at other places (like SaveState). I presume this is only meant to be an in memory guard in resources.json based deployments?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we create empty WAL file here but don't write to it?

Perhaps AssertOpenedForWrite should handle both write mode and recording history mode.

Related, we should probably merge these into enum (reading, writingWAL, DMS)

    // openedForWrite records write mode explicitly. It used to be implied by walFile, but a recorded
    // deployment writes no WAL, so the two are no longer the same question.
    openedForWrite bool

    // recordsHistory is whether this deployment records history, decided by Open from the state's
    // features. Cached because it outlives the features themselves: Finalize resets Data, and
    // CompleteVersion runs after that.
    recordsHistory bool

We don't want to open WAL when using DMS but SaveState should still work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah makes sense. We'll like to have a readonly mode for DMS as well (for things like summary / validate). Can be a followup though.

Comment thread bundle/phases/deploy.go Outdated
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Commit: d97a6988a | Workflow run

Regression test report

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

4 tests: regression=4 unreleased=0 coverage=0 failing=0

test branch main (a1cf984) latest (v1.21.0)
TestAccept/bundle/dms/existing-state/DMS= ✅ ❌ ❌
TestAccept/bundle/dms/migration-before-deploy/DMS=true ✅ ❌ ❌
TestAccept/bundle/dms/repeated-migration/DMS=true ✅ ❌ ❌
TestAccept/bundle/migrate/auto/dms-migration/DMS= ✅ ❌ ❌
TestAccept/bundle/dms/existing-state/DMS=: branch ✅ | main (a1cf984) ❌ | latest (v1.21.0) ❌

main (a1cf984):

acceptance_test.go:1308: Diff:
        --- bundle/dms/existing-state/output.txt
        +++ /tmp/TestAcceptbundledmsexisting-stateDMS=4076160897/002/output.txt
        @@ -18,103 +18,15 @@
         >>> update_file.py databricks.yml name: one name: updated
         
         >>> [CLI] bundle deploy
        -Enabling deployment history...
        -Deployment history enabled: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=2
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/files...
        -Current Deployment Version: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=3
        -Updated jobs.one
        -Updated jobs.two
        -Files: 1 uploaded, 0 deleted
        -Resources: 0 created, 2 changed, 0 deleted, 0 unchanged
        +Error: deployment history setting (true) does not match the existing state (false)
         
        -=== 3. Check local state
        -Expect: serial=3; history enabled; no resource state or WAL.
        ->>> find.py resources[.]json[.]wal --expect 0
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
         
        ->>> jq {serial, features, state} .databricks/bundle/default/resources.json
        -{
        -  "serial": 3,
        -  "features": {
        -    "deployment_history": {}
        -  },
        -  "state": {}
        -}
        +contains error: '?version=2' not found in the output.
        +contains error: '?version=3' not found in the output.
        +contains error: 'Current Deployment Version: ' not found in the output.
        +contains error: 'Deployment history enabled: ' not found in the output.
        +contains error: 'Enabling deployment history...' not found in the output.
        +contains error: 'Resources: 0 created' not found in the output.
         
        -=== 4. Check backup
        -Expect: original IDs, names and dependencies; exact pre-migration bytes.
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/resources.json.backup --format AUTO --file .databricks/resources.backup.first.json
        -
        ->>> cat .databricks/resources.backup.first.json
        -{
        - "state_version": 3,
        - "cli_version": "[CLI_VERSION]",
        - "lineage": "[UUID]",
        - "serial": 1,
        - "state": {
        -  "resources.jobs.one": {
        -   "__id__": "[ONE_ID]",
        -   "state": {
        -    "deployment": {
        -     "kind": "BUNDLE",
        -     "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/metadata.json"
        -    },
        -    "edit_mode": "UI_LOCKED",
        -    "format": "MULTI_TASK",
        -    "max_concurrent_runs": 1,
        -    "name": "one",
        -    "queue": {
        -     "enabled": true
        -    }
        -   }
        -  },
        -  "resources.jobs.two": {
        -   "__id__": "[TWO_ID]",
        -   "state": {
        -    "deployment": {
        -     "kind": "BUNDLE",
        -     "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/metadata.json"
        -    },
        -    "edit_mode": "UI_LOCKED",
        -    "format": "MULTI_TASK",
        -    "max_concurrent_runs": 1,
        -    "name": "two",
        -    "queue": {
        -     "enabled": true
        -    },
        -    "tasks": [
        -     {
        -      "run_job_task": {
        -       "job_id": [ONE_ID]
        -      },
        -      "task_key": "run_one"
        -     }
        -    ]
        -   },
        -   "depends_on": [
        -    {
        -     "node": "resources.jobs.one",
        -     "label": "${resources.jobs.one.id}"
        -    }
        -   ]
        -  }
        - }
        -}
        ->>> cmp .databricks/resources.before-dms.json .databricks/resources.backup.first.json
        -
        -=== 5. Check DMS records
        -Expect: completed versions 2 and 3; original job IDs and dependency preserved.
        -=== Version history: migration 2 completes before deployment 3
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","previous_version_id":null,"version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"3","previous_version_id":"2","version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        -
        -=== Migration 2: register the original job IDs (INITIAL_REGISTER)
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/2 -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"},{"resource_key":"jobs.two","resource_id":"[TWO_ID]","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== Deployment 3: record updates separately
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/3 -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_UPDATE","status":"OPERATION_STATUS_SUCCEEDED"},{"resource_key":"jobs.two","resource_id":"[TWO_ID]","action_type":"OPERATION_ACTION_TYPE_UPDATE","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== Current resources: new name, original IDs, and job two still depends on job one
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","last_action_type":"OPERATION_ACTION_TYPE_UPDATE","last_version_id":"3","state":{"name":"updated","tasks":[],"depends_on":[]}},{"resource_key":"jobs.two","resource_id":"[TWO_ID]","last_action_type":"OPERATION_ACTION_TYPE_UPDATE","last_version_id":"3","state":{"name":"two","tasks":[{"task_key":"run_one","job_id":[ONE_ID]}],"depends_on":[{"node":"resources.jobs.one","label":"${resources.jobs.one.id}"}]}}]
        +Exit code: 1

latest (v1.21.0):

acceptance_test.go:1308: Diff:
        --- bundle/dms/existing-state/output.txt
        +++ /tmp/TestAcceptbundledmsexisting-stateDMS=1007379838/002/output.txt
        @@ -18,103 +18,15 @@
         >>> update_file.py databricks.yml name: one name: updated
         
         >>> [CLI] bundle deploy
        -Enabling deployment history...
        -Deployment history enabled: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=2
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/files...
        -Current Deployment Version: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=3
        -Updated jobs.one
        -Updated jobs.two
        -Files: 1 uploaded, 0 deleted
        -Resources: 0 created, 2 changed, 0 deleted, 0 unchanged
        +Error: deployment history setting (true) does not match the existing state (false)
         
        -=== 3. Check local state
        -Expect: serial=3; history enabled; no resource state or WAL.
        ->>> find.py resources[.]json[.]wal --expect 0
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
         
        ->>> jq {serial, features, state} .databricks/bundle/default/resources.json
        -{
        -  "serial": 3,
        -  "features": {
        -    "deployment_history": {}
        -  },
        -  "state": {}
        -}
        +contains error: '?version=2' not found in the output.
        +contains error: '?version=3' not found in the output.
        +contains error: 'Current Deployment Version: ' not found in the output.
        +contains error: 'Deployment history enabled: ' not found in the output.
        +contains error: 'Enabling deployment history...' not found in the output.
        +contains error: 'Resources: 0 created' not found in the output.
         
        -=== 4. Check backup
        -Expect: original IDs, names and dependencies; exact pre-migration bytes.
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/resources.json.backup --format AUTO --file .databricks/resources.backup.first.json
        -
        ->>> cat .databricks/resources.backup.first.json
        -{
        - "state_version": 3,
        - "cli_version": "[CLI_VERSION]",
        - "lineage": "[UUID]",
        - "serial": 1,
        - "state": {
        -  "resources.jobs.one": {
        -   "__id__": "[ONE_ID]",
        -   "state": {
        -    "deployment": {
        -     "kind": "BUNDLE",
        -     "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/metadata.json"
        -    },
        -    "edit_mode": "UI_LOCKED",
        -    "format": "MULTI_TASK",
        -    "max_concurrent_runs": 1,
        -    "name": "one",
        -    "queue": {
        -     "enabled": true
        -    }
        -   }
        -  },
        -  "resources.jobs.two": {
        -   "__id__": "[TWO_ID]",
        -   "state": {
        -    "deployment": {
        -     "kind": "BUNDLE",
        -     "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/metadata.json"
        -    },
        -    "edit_mode": "UI_LOCKED",
        -    "format": "MULTI_TASK",
        -    "max_concurrent_runs": 1,
        -    "name": "two",
        -    "queue": {
        -     "enabled": true
        -    },
        -    "tasks": [
        -     {
        -      "run_job_task": {
        -       "job_id": [ONE_ID]
        -      },
        -      "task_key": "run_one"
        -     }
        -    ]
        -   },
        -   "depends_on": [
        -    {
        -     "node": "resources.jobs.one",
        -     "label": "${resources.jobs.one.id}"
        -    }
        -   ]
        -  }
        - }
        -}
        ->>> cmp .databricks/resources.before-dms.json .databricks/resources.backup.first.json
        -
        -=== 5. Check DMS records
        -Expect: completed versions 2 and 3; original job IDs and dependency preserved.
        -=== Version history: migration 2 completes before deployment 3
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","previous_version_id":null,"version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"3","previous_version_id":"2","version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        -
        -=== Migration 2: register the original job IDs (INITIAL_REGISTER)
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/2 -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"},{"resource_key":"jobs.two","resource_id":"[TWO_ID]","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== Deployment 3: record updates separately
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/3 -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_UPDATE","status":"OPERATION_STATUS_SUCCEEDED"},{"resource_key":"jobs.two","resource_id":"[TWO_ID]","action_type":"OPERATION_ACTION_TYPE_UPDATE","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== Current resources: new name, original IDs, and job two still depends on job one
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","last_action_type":"OPERATION_ACTION_TYPE_UPDATE","last_version_id":"3","state":{"name":"updated","tasks":[],"depends_on":[]}},{"resource_key":"jobs.two","resource_id":"[TWO_ID]","last_action_type":"OPERATION_ACTION_TYPE_UPDATE","last_version_id":"3","state":{"name":"two","tasks":[{"task_key":"run_one","job_id":[ONE_ID]}],"depends_on":[{"node":"resources.jobs.one","label":"${resources.jobs.one.id}"}]}}]
        +Exit code: 1
TestAccept/bundle/dms/migration-before-deploy/DMS=true: branch ✅ | main (a1cf984) ❌ | latest (v1.21.0) ❌

main (a1cf984):

acceptance_test.go:1308: Diff:
        --- bundle/dms/migration-before-deploy/output.txt
        +++ /tmp/TestAcceptbundledmsmigration-before-deployDMS=true3001899813/002/output.txt
        @@ -6,75 +6,10 @@
         >>> fault.py GET /api/2.2/jobs/get* 403 0 1
         
         >>> musterr [CLI] bundle deploy -qq
        -Enabling deployment history...
        -Deployment history enabled: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=2
        -Error: cannot plan resources.jobs.job: reading id="[JOB_ID]": Fault injected by test. (403 INJECTED)
        +Error: deployment history setting (true) does not match the existing state (false)
         
        -Endpoint: GET [DATABRICKS_URL]/api/2.2/jobs/get?job_id=[JOB_ID]
        -HTTP Status: 403 Forbidden
        -API error_code: INJECTED
        -API message: Fault injected by test.
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
         
        -Error: planning failed
        +Error: Path (/Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.deployment.json) doesn't exist.
         
        -
        -=== Request order: complete migration -> upload marker -> read DMS -> plan jobs
        ->>> print_requests.py --dms --get //api/2.0/bundle //jobs/get //workspace/import //workspace-files/import-file --oneline
        -{"method":"POST","path":"/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/2/complete"}
        -{"method":"POST","path":"/Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.json"}
        -{"method":"GET","path":"/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/resources"}
        -{"method":"GET","path":"/api/2.2/jobs/get"}
        -
        -=== Remote state: version 2 marker; original resources preserved in the backup
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.json --format AUTO --file .databricks/resources.marker.json
        -
        ->>> jq -c {serial, features, state} .databricks/resources.marker.json
        -{"serial":2,"features":{"deployment_history":{}},"state":{}}
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.json.backup --format AUTO --file .databricks/resources.backup.json
        -
        ->>> cmp .databricks/resources.before-migration.json .databricks/resources.backup.json
        -
        -=== Migration result: only completed version 2, with INITIAL_REGISTER operations
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/2 -o json
        -[{"resource_key":"jobs.job","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"},{"resource_key":"schemas.schema","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== 2. Retry without approval
        -Expect: deployment declined; migration stays at version 2 and is not repeated.
        ->>> musterr [CLI] bundle deploy -qq
        -
        -This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost:
        -  recreate resources.schemas.schema
        -Error: the deployment requires destructive actions, but the current console does not support prompting.
        -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.
        -
        -
        -=== Local and remote markers must stay byte-for-byte unchanged
        ->>> cmp .databricks/resources.marker.json .databricks/bundle/default/resources.json
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.json --format AUTO --file .databricks/resources.after-decline.json
        -
        ->>> cmp .databricks/resources.marker.json .databricks/resources.after-decline.json
        -
        -=== No DMS writes and no extra version on the declined retry
        ->>> print_requests.py --dms //api/2.0/bundle
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        -
        -=== 3. Approve deployment
        -Expect: ordinary deployment version 3; version 2 remains the only migration.
        ->>> [CLI] bundle deploy --auto-approve -qq
        -
        -This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost:
        -  recreate resources.schemas.schema
        -
        ->>> [CLI] bundle summary -o json
        -{"latest_version_id":3}
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"3","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        +Exit code: 1
        
    acceptance_test.go:1115: 
        LOG.cleanup: Warn: planning resources.jobs.job: reading resources.jobs.job id="8792508028994623424": Fault injected by test.
        LOG.cleanup: The following resources will be deleted:
        LOG.cleanup:   delete resources.jobs.job
        LOG.cleanup:   delete resources.schemas.schema
        LOG.cleanup: 
        LOG.cleanup: This action will result in the deletion of the following UC schemas. Any underlying data may be lost:
        LOG.cleanup:   delete resources.schemas.schema
        LOG.cleanup: 
        LOG.cleanup: All files and directories at the following location will be deleted: /Workspace/Users/tester@databricks.com/.bundle/dms-migration-before-deploy-qzaxgmiuerflbpvvyt5yivpkt4/default
        LOG.cleanup: 
        LOG.cleanup: Destroy: 2 deleted
    acceptance_test.go:1115: 
        LOG.setup: {
        LOG.setup:   "catalog_type": "MANAGED_CATALOG",
        LOG.setup:   "created_at": 1792508029093,
        LOG.setup:   "created_by": "tester@databricks.com",
        LOG.setup:   "full_name": "dms_other_qzaxgmiuerflbpvvyt5yivpkt4",
        LOG.setup:   "metastore_id": "7a054377-bedd-f5fe-7a05-4377beddf703",
        LOG.setup:   "name": "dms_other_qzaxgmiuerflbpvvyt5yivpkt4",
        LOG.setup:   "owner": "tester@databricks.com",
        LOG.setup:   "updated_at": 1792508029093,
        LOG.setup:   "updated_by": "tester@databricks.com"
        LOG.setup: }

latest (v1.21.0):

acceptance_test.go:1308: Diff:
        --- bundle/dms/migration-before-deploy/output.txt
        +++ /tmp/TestAcceptbundledmsmigration-before-deployDMS=true3780687590/002/output.txt
        @@ -6,75 +6,10 @@
         >>> fault.py GET /api/2.2/jobs/get* 403 0 1
         
         >>> musterr [CLI] bundle deploy -qq
        -Enabling deployment history...
        -Deployment history enabled: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=2
        -Error: cannot plan resources.jobs.job: reading id="[JOB_ID]": Fault injected by test. (403 INJECTED)
        +Error: deployment history setting (true) does not match the existing state (false)
         
        -Endpoint: GET [DATABRICKS_URL]/api/2.2/jobs/get?job_id=[JOB_ID]
        -HTTP Status: 403 Forbidden
        -API error_code: INJECTED
        -API message: Fault injected by test.
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
         
        -Error: planning failed
        +Error: Path (/Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.deployment.json) doesn't exist.
         
        -
        -=== Request order: complete migration -> upload marker -> read DMS -> plan jobs
        ->>> print_requests.py --dms --get //api/2.0/bundle //jobs/get //workspace/import //workspace-files/import-file --oneline
        -{"method":"POST","path":"/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/2/complete"}
        -{"method":"POST","path":"/Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.json"}
        -{"method":"GET","path":"/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/resources"}
        -{"method":"GET","path":"/api/2.2/jobs/get"}
        -
        -=== Remote state: version 2 marker; original resources preserved in the backup
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.json --format AUTO --file .databricks/resources.marker.json
        -
        ->>> jq -c {serial, features, state} .databricks/resources.marker.json
        -{"serial":2,"features":{"deployment_history":{}},"state":{}}
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.json.backup --format AUTO --file .databricks/resources.backup.json
        -
        ->>> cmp .databricks/resources.before-migration.json .databricks/resources.backup.json
        -
        -=== Migration result: only completed version 2, with INITIAL_REGISTER operations
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/2 -o json
        -[{"resource_key":"jobs.job","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"},{"resource_key":"schemas.schema","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== 2. Retry without approval
        -Expect: deployment declined; migration stays at version 2 and is not repeated.
        ->>> musterr [CLI] bundle deploy -qq
        -
        -This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost:
        -  recreate resources.schemas.schema
        -Error: the deployment requires destructive actions, but the current console does not support prompting.
        -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.
        -
        -
        -=== Local and remote markers must stay byte-for-byte unchanged
        ->>> cmp .databricks/resources.marker.json .databricks/bundle/default/resources.json
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-migration-before-deploy-[UNIQUE_NAME]/default/state/resources.json --format AUTO --file .databricks/resources.after-decline.json
        -
        ->>> cmp .databricks/resources.marker.json .databricks/resources.after-decline.json
        -
        -=== No DMS writes and no extra version on the declined retry
        ->>> print_requests.py --dms //api/2.0/bundle
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        -
        -=== 3. Approve deployment
        -Expect: ordinary deployment version 3; version 2 remains the only migration.
        ->>> [CLI] bundle deploy --auto-approve -qq
        -
        -This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost:
        -  recreate resources.schemas.schema
        -
        ->>> [CLI] bundle summary -o json
        -{"latest_version_id":3}
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"3","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        +Exit code: 1
        
    acceptance_test.go:1115: 
        LOG.cleanup: Warn: planning resources.jobs.job: reading resources.jobs.job id="8792508033317709968": Fault injected by test.
        LOG.cleanup: The following resources will be deleted:
        LOG.cleanup:   delete resources.jobs.job
        LOG.cleanup:   delete resources.schemas.schema
        LOG.cleanup: 
        LOG.cleanup: This action will result in the deletion of the following UC schemas. Any underlying data may be lost:
        LOG.cleanup:   delete resources.schemas.schema
        LOG.cleanup: 
        LOG.cleanup: All files and directories at the following location will be deleted: /Workspace/Users/tester@databricks.com/.bundle/dms-migration-before-deploy-2ptzhcj2azaa7ftqnlh3qhgtnq/default
        LOG.cleanup: 
        LOG.cleanup: Destroy: 2 deleted
    acceptance_test.go:1115: 
        LOG.setup: {
        LOG.setup:   "catalog_type": "MANAGED_CATALOG",
        LOG.setup:   "created_at": 1792508033685,
        LOG.setup:   "created_by": "tester@databricks.com",
        LOG.setup:   "full_name": "dms_other_2ptzhcj2azaa7ftqnlh3qhgtnq",
        LOG.setup:   "metastore_id": "7a054378-d091-533b-7a05-4378d0915421",
        LOG.setup:   "name": "dms_other_2ptzhcj2azaa7ftqnlh3qhgtnq",
        LOG.setup:   "owner": "tester@databricks.com",
        LOG.setup:   "updated_at": 1792508033685,
        LOG.setup:   "updated_by": "tester@databricks.com"
        LOG.setup: }
TestAccept/bundle/dms/repeated-migration/DMS=true: branch ✅ | main (a1cf984) ❌ | latest (v1.21.0) ❌

main (a1cf984):

acceptance_test.go:1308: Diff:
        --- bundle/dms/repeated-migration/output.txt
        +++ /tmp/TestAcceptbundledmsrepeated-migrationDMS=true3204770456/002/output.txt
        @@ -2,105 +2,4 @@
         === Setup (LOG.setup)
         Current: job=updated; DMS version=3; old backup=one.
         
        -=== 1. Compare the old backup with the latest saved state
        -Expect: old backup name=one; latest state name=updated.
        -
        ->>> gron.py .databricks/resources.backup.first.json
        -json.state.resources.jobs.one.state.name = "one";
        -
        ->>> gron.py .databricks/resources.before-repeat.json
        -json.state.resources.jobs.one.state.name = "updated";
        -
        -=== 2. Restore saved state without its history flag and deploy again
        -Expect: migration=4; deployment=5; name=repeated; no new jobs.
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace import /Workspace/Users/[USERNAME]/.bundle/dms-repeated-migration-[UNIQUE_NAME]/default/state/resources.json --file .databricks/resources.before-repeat.json --format AUTO --overwrite
        -
        ->>> cp .databricks/resources.before-repeat.json .databricks/bundle/default/resources.json
        -
        ->>> update_file.py databricks.yml name: updated name: repeated
        -
        ->>> [CLI] bundle deploy
        -Enabling deployment history...
        -Deployment history enabled: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=4
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-repeated-migration-[UNIQUE_NAME]/default/files...
        -Current Deployment Version: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=5
        -Updated jobs.one
        -Files: 1 uploaded, 0 deleted
        -Resources: 0 created, 1 changed, 0 deleted, 0 unchanged
        -
        -=== 3. Check DMS records
        -Expect: completed migration 4 and deployment 5; original ID preserved; name=repeated.
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","previous_version_id":null,"version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"3","previous_version_id":"2","version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"4","previous_version_id":"3","version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"5","previous_version_id":"4","version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        -
        -=== Migration 4: register the existing job ID again (INITIAL_REGISTER)
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/4 -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== Deployment 5: update the job name without creating a replacement
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/5 -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_UPDATE","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== Current resource: original job ID, name=repeated, last version=5
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","last_version_id":"5","name":"repeated"}]
        -
        -=== 4. Check backup was overwritten
        -Expect: name=updated (before this deploy), not one or repeated.
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-repeated-migration-[UNIQUE_NAME]/default/state/resources.json.backup --format AUTO --file .databricks/resources.backup.repeat.json
        -
        ->>> cat .databricks/resources.backup.repeat.json
        -{
        - "state_version": 3,
        - "cli_version": "[CLI_VERSION]",
        - "lineage": "[UUID]",
        - "serial": 3,
        - "state": {
        -  "resources.jobs.one": {
        -   "__id__": "[ONE_ID]",
        -   "state": {
        -    "deployment": {
        -     "kind": "BUNDLE",
        -     "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-repeated-migration-[UNIQUE_NAME]/default/state/metadata.json"
        -    },
        -    "edit_mode": "UI_LOCKED",
        -    "format": "MULTI_TASK",
        -    "max_concurrent_runs": 1,
        -    "name": "updated",
        -    "queue": {
        -     "enabled": true
        -    }
        -   }
        -  }
        - }
        -}
        -
        -=== Backup matches the restored snapshot byte for byte
        ->>> cmp .databricks/resources.before-repeat.json .databricks/resources.backup.repeat.json
        -
        -=== Compare backups: serial 1 -> 3 and job name one -> updated
        ->>> diff.py .databricks/resources.backup.first.json .databricks/resources.backup.repeat.json
        ---- .databricks/resources.backup.first.json
        -+++ .databricks/resources.backup.repeat.json
        -@@ -3,5 +3,5 @@
        -  "cli_version": "[CLI_VERSION]",
        -  "lineage": "[UUID]",
        -- "serial": 1,
        -+ "serial": 3,
        -  "state": {
        -   "resources.jobs.one": {
        -@@ -15,5 +15,5 @@
        -     "format": "MULTI_TASK",
        -     "max_concurrent_runs": 1,
        --    "name": "one",
        -+    "name": "updated",
        -     "queue": {
        -      "enabled": true
        -@@ -22,3 +22,3 @@
        -   }
        -  }
        --}
        -+}
        +Exit code: 1
        
    acceptance_test.go:1115: 
        LOG.setup: 
        LOG.setup: >>> /tmp/TestAccept2564922754/001/databricks bundle deploy
        LOG.setup: Uploading bundle files to /Workspace/Users/tester@databricks.com/.bundle/dms-repeated-migration-olskz4vgxbemtdz7w7rff47ftq/default/files...
        LOG.setup: Created jobs.one
        LOG.setup: Files: 2 uploaded, 0 deleted
        LOG.setup: Resources: 1 created, 0 changed, 0 deleted, 0 unchanged
        LOG.setup: 
        LOG.setup: >>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=false replace_ids.py
        LOG.setup: 
        LOG.setup: >>> update_file.py databricks.yml deployment_history: false deployment_history: true
        LOG.setup: 
        LOG.setup: >>> update_file.py databricks.yml name: one name: updated
        LOG.setup: 
        LOG.setup: >>> /tmp/TestAccept2564922754/001/databricks bundle deploy
        LOG.setup: Error: deployment history setting (true) does not match the existing state (false)
        LOG.setup: 
        LOG.setup: Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over

latest (v1.21.0):

acceptance_test.go:1308: Diff:
        --- bundle/dms/repeated-migration/output.txt
        +++ /tmp/TestAcceptbundledmsrepeated-migrationDMS=true3252793132/002/output.txt
        @@ -2,105 +2,4 @@
         === Setup (LOG.setup)
         Current: job=updated; DMS version=3; old backup=one.
         
        -=== 1. Compare the old backup with the latest saved state
        -Expect: old backup name=one; latest state name=updated.
        -
        ->>> gron.py .databricks/resources.backup.first.json
        -json.state.resources.jobs.one.state.name = "one";
        -
        ->>> gron.py .databricks/resources.before-repeat.json
        -json.state.resources.jobs.one.state.name = "updated";
        -
        -=== 2. Restore saved state without its history flag and deploy again
        -Expect: migration=4; deployment=5; name=repeated; no new jobs.
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace import /Workspace/Users/[USERNAME]/.bundle/dms-repeated-migration-[UNIQUE_NAME]/default/state/resources.json --file .databricks/resources.before-repeat.json --format AUTO --overwrite
        -
        ->>> cp .databricks/resources.before-repeat.json .databricks/bundle/default/resources.json
        -
        ->>> update_file.py databricks.yml name: updated name: repeated
        -
        ->>> [CLI] bundle deploy
        -Enabling deployment history...
        -Deployment history enabled: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=4
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-repeated-migration-[UNIQUE_NAME]/default/files...
        -Current Deployment Version: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=5
        -Updated jobs.one
        -Files: 1 uploaded, 0 deleted
        -Resources: 0 created, 1 changed, 0 deleted, 0 unchanged
        -
        -=== 3. Check DMS records
        -Expect: completed migration 4 and deployment 5; original ID preserved; name=repeated.
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
        -[{"version_id":"2","previous_version_id":null,"version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"3","previous_version_id":"2","version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"4","previous_version_id":"3","version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"5","previous_version_id":"4","version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]
        -
        -=== Migration 4: register the existing job ID again (INITIAL_REGISTER)
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/4 -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== Deployment 5: update the job name without creating a replacement
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/5 -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_UPDATE","status":"OPERATION_STATUS_SUCCEEDED"}]
        -
        -=== Current resource: original job ID, name=repeated, last version=5
        ->>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
        -[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","last_version_id":"5","name":"repeated"}]
        -
        -=== 4. Check backup was overwritten
        -Expect: name=updated (before this deploy), not one or repeated.
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-repeated-migration-[UNIQUE_NAME]/default/state/resources.json.backup --format AUTO --file .databricks/resources.backup.repeat.json
        -
        ->>> cat .databricks/resources.backup.repeat.json
        -{
        - "state_version": 3,
        - "cli_version": "[CLI_VERSION]",
        - "lineage": "[UUID]",
        - "serial": 3,
        - "state": {
        -  "resources.jobs.one": {
        -   "__id__": "[ONE_ID]",
        -   "state": {
        -    "deployment": {
        -     "kind": "BUNDLE",
        -     "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-repeated-migration-[UNIQUE_NAME]/default/state/metadata.json"
        -    },
        -    "edit_mode": "UI_LOCKED",
        -    "format": "MULTI_TASK",
        -    "max_concurrent_runs": 1,
        -    "name": "updated",
        -    "queue": {
        -     "enabled": true
        -    }
        -   }
        -  }
        - }
        -}
        -
        -=== Backup matches the restored snapshot byte for byte
        ->>> cmp .databricks/resources.before-repeat.json .databricks/resources.backup.repeat.json
        -
        -=== Compare backups: serial 1 -> 3 and job name one -> updated
        ->>> diff.py .databricks/resources.backup.first.json .databricks/resources.backup.repeat.json
        ---- .databricks/resources.backup.first.json
        -+++ .databricks/resources.backup.repeat.json
        -@@ -3,5 +3,5 @@
        -  "cli_version": "[CLI_VERSION]",
        -  "lineage": "[UUID]",
        -- "serial": 1,
        -+ "serial": 3,
        -  "state": {
        -   "resources.jobs.one": {
        -@@ -15,5 +15,5 @@
        -     "format": "MULTI_TASK",
        -     "max_concurrent_runs": 1,
        --    "name": "one",
        -+    "name": "updated",
        -     "queue": {
        -      "enabled": true
        -@@ -22,3 +22,3 @@
        -   }
        -  }
        --}
        -+}
        +Exit code: 1
        
    acceptance_test.go:1115: 
        LOG.setup: 
        LOG.setup: >>> /home/runner/work/cli/cli/acceptance/build/linux_amd64/1.21.0/databricks bundle deploy
        LOG.setup: Uploading bundle files to /Workspace/Users/tester@databricks.com/.bundle/dms-repeated-migration-7zycxz3vzjg7hnv5m6qzppde2e/default/files...
        LOG.setup: Created jobs.one
        LOG.setup: Files: 2 uploaded, 0 deleted
        LOG.setup: Resources: 1 created, 0 changed, 0 deleted, 0 unchanged
        LOG.setup: 
        LOG.setup: >>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=false replace_ids.py
        LOG.setup: 
        LOG.setup: >>> update_file.py databricks.yml deployment_history: false deployment_history: true
        LOG.setup: 
        LOG.setup: >>> update_file.py databricks.yml name: one name: updated
        LOG.setup: 
        LOG.setup: >>> /home/runner/work/cli/cli/acceptance/build/linux_amd64/1.21.0/databricks bundle deploy
        LOG.setup: Error: deployment history setting (true) does not match the existing state (false)
        LOG.setup: 
        LOG.setup: Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
TestAccept/bundle/migrate/auto/dms-migration/DMS=: branch ✅ | main (a1cf984) ❌ | latest (v1.21.0) ❌

main (a1cf984):

acceptance_test.go:1308: Diff:
        --- bundle/migrate/auto/dms-migration/output.txt
        +++ /tmp/TestAcceptbundlemigrateautodms-migrationDMS=207734725/002/output.txt
        @@ -4,11 +4,19 @@
         
         === Deployment history rejects Terraform state before conversion, for both plan and deploy
         >>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true musterr [CLI] bundle plan
        -Error: deployment history is not supported with Terraform state; first migrate to direct deployment with deployment history disabled
        +Notice: migrating your bundle to direct deployment engine (https://github.com/databricks/cli/issues/6765).
        +Error: deployment history setting (true) does not match the existing state (false)
         
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
        +
        +
         >>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true musterr [CLI] bundle deploy
        -Error: deployment history is not supported with Terraform state; first migrate to direct deployment with deployment history disabled
        +Notice: migrating your bundle to direct deployment engine (https://github.com/databricks/cli/issues/6765).
        +Error: deployment history setting (true) does not match the existing state (false)
         
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
        +
        +
         >>> find.py resources[.]json|tfstate[.]backup --expect 0
         
         >>> print_requests.py --dms //api/2.0/bundle //jobs
        @@ -23,21 +28,9 @@
         
         === Then enable deployment history on the direct state, preserving the job ID
         >>> DATABRICKS_BUNDLE_ENGINE=direct DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle deploy
        -Enabling deployment history...
        -Deployment history enabled: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=4
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Updated jobs.test_job
        -Files: 0 uploaded, 0 deleted
        -Resources: 0 created, 1 changed, 0 deleted, 0 unchanged
        +Error: deployment history setting (true) does not match the existing state (false)
         
        ->>> find.py \.databricks/.*(\.tfstate|resources.*\.json|\.wal)
        -.databricks/bundle/default/resources.json
        -.databricks/bundle/default/terraform/terraform.tfstate.backup
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
         
        ->>> find.py resources[.]json[.]wal --expect 0
         
        ->>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle summary -o json
        -{"job_id":"[TEST_JOB_ID]","version":5}
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json --format AUTO
        -{"features":{"deployment_history":{}},"state":{}}
        +Exit code: 1
        
FAIL

latest (v1.21.0):

acceptance_test.go:1308: Diff:
        --- bundle/migrate/auto/dms-migration/output.txt
        +++ /tmp/TestAcceptbundlemigrateautodms-migrationDMS=1328841734/002/output.txt
        @@ -4,11 +4,19 @@
         
         === Deployment history rejects Terraform state before conversion, for both plan and deploy
         >>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true musterr [CLI] bundle plan
        -Error: deployment history is not supported with Terraform state; first migrate to direct deployment with deployment history disabled
        +Notice: migrating your bundle to direct deployment engine (https://github.com/databricks/cli/issues/6765).
        +Error: deployment history setting (true) does not match the existing state (false)
         
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
        +
        +
         >>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true musterr [CLI] bundle deploy
        -Error: deployment history is not supported with Terraform state; first migrate to direct deployment with deployment history disabled
        +Notice: migrating your bundle to direct deployment engine (https://github.com/databricks/cli/issues/6765).
        +Error: deployment history setting (true) does not match the existing state (false)
         
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
        +
        +
         >>> find.py resources[.]json|tfstate[.]backup --expect 0
         
         >>> print_requests.py --dms //api/2.0/bundle //jobs
        @@ -23,21 +28,9 @@
         
         === Then enable deployment history on the direct state, preserving the job ID
         >>> DATABRICKS_BUNDLE_ENGINE=direct DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle deploy
        -Enabling deployment history...
        -Deployment history enabled: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=4
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Updated jobs.test_job
        -Files: 0 uploaded, 0 deleted
        -Resources: 0 created, 1 changed, 0 deleted, 0 unchanged
        +Error: deployment history setting (true) does not match the existing state (false)
         
        ->>> find.py \.databricks/.*(\.tfstate|resources.*\.json|\.wal)
        -.databricks/bundle/default/resources.json
        -.databricks/bundle/default/terraform/terraform.tfstate.backup
        +Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over
         
        ->>> find.py resources[.]json[.]wal --expect 0
         
        ->>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle summary -o json
        -{"job_id":"[TEST_JOB_ID]","version":5}
        -
        ->>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json --format AUTO
        -{"features":{"deployment_history":{}},"state":{}}
        +Exit code: 1
        
FAIL

@shreyas-goenka
shreyas-goenka requested a review from denik October 20, 2026 13:01
github-merge-queue Bot pushed a commit that referenced this pull request Oct 20, 2026
Follow-up to #7105. The comments
on #6989 and
#7109 were 34-44 KB, close to the
60 KB cap, mostly because a new test fails the same way on main and on
the latest release and the same diff was printed twice.

- A failing run whose output equals an earlier column's (ignoring the
random temp dir) now says "same output as <column>".
- When the comment is over the cap, whole failure details are dropped
(with a count) instead of cutting one in the middle and leaving its
markup open.

This pull request and its description were written by Isaac.

---------

Co-authored-by: Isaac <no-reply@databricks.com>
trace print_requests.py --dms //api/2.0/bundle --oneline
trace jq '{state_version, features}' .databricks/bundle/default/resources.json
title "1. Create two jobs without history (direct state serial 1)\nConfig disables history even though the environment enables it."
trace DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true $CLI bundle deploy 2>&1 | contains.py '!Current Deployment Version:' '!Enabling deployment history'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why enable DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true here? Do you test env + config combination there? Was it not tested in #7045 ? If there is missing coverage for that feature, perhaps it can be done as separate PR?

@@ -1,38 +1,40 @@
envsubst < databricks.yml.tmpl > databricks.yml

title "Deploy without recording: ordinary direct-engine state tracking one job, unknown to DMS"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

general observation, this test is rewritten so much it is practically a different test. Maybe worth adding this new test separately and record output changes for the original test (with minimal script changes).

other_catalog="dms_other_${UNIQUE_NAME}"

title "Setup (LOG.setup): create a job and schema without history\nNext deploy enables history and recreates the schema in another catalog, requiring approval."
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to group it? cannot we record the command + output with trace? This makes output easier to read. As far as I can see the commands below have no or very little output.

title "Setup (LOG.setup): create a job and schema without history\nNext deploy enables history and recreates the schema in another catalog, requiring approval."
{
$CLI bundle deploy -qq
DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=false replace_ids.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it significant to have env var here that matches config?

Comment thread bundle/phases/deploy.go
// Upgrade from read (opened by process.go, or by Migrate) to write mode. After approval
// and the migration commit above, so a declined deploy never opens a WAL it must discard,
// and the migration's tf+1 push lands before this advances the WAL header to tf+2.
if err := b.DeploymentBundle.StateDB.UpgradeToWrite(); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we create empty WAL file here but don't write to it?

Perhaps AssertOpenedForWrite should handle both write mode and recording history mode.

Related, we should probably merge these into enum (reading, writingWAL, DMS)

    // openedForWrite records write mode explicitly. It used to be implied by walFile, but a recorded
    // deployment writes no WAL, so the two are no longer the same question.
    openedForWrite bool

    // recordsHistory is whether this deployment records history, decided by Open from the state's
    // features. Cached because it outlives the features themselves: Finalize resets Data, and
    // CompleteVersion runs after that.
    recordsHistory bool

We don't want to open WAL when using DMS but SaveState should still work.

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

DABs DABs related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants