Skip to content

Commit 4b732a1

Browse files
authored
Fix bind to always pull remote state before modifying (#4892)
## Changes - Fix `bundle deployment bind` to always pull remote state before modifying it, instead of only reading local state. Previously, if local state was stale (e.g. another deploy happened from a different machine), bind could silently operate on outdated data. - Same fix applied to the `apps import` bind path. ## Why When local state is stale, bind could corrupt deployment state or fail in confusing ways. Always pulling remote state ensures bind sees the latest deployed resources. ## Tests - Added acceptance test `bind/job/stale-state` that reproduces the bug: deploys, saves stale local state, deploys again with new resources, restores stale state, then attempts bind — verifying it pulls fresh remote state. Covers both terraform and direct engines. - Added acceptance test `bind/cross-engine` that tests binding resources when switching between terraform and direct engines, verifying cross-engine lineage mismatch handling.
1 parent ff9afcc commit 4b732a1

11 files changed

Lines changed: 105 additions & 2 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* engine/direct: Fix bind and unbind for non-Terraform resources ([#4850](https://github.com/databricks/cli/pull/4850))
1717
* engine/direct: Fix deploying removed principals ([#4824](https://github.com/databricks/cli/pull/4824))
1818
* engine/direct: Fix secret scope permissions migration from Terraform to Direct engine ([#4866](https://github.com/databricks/cli/pull/4866))
19+
* Fix `bundle deployment bind` to always pull remote state before modifying ([#4892](https://github.com/databricks/cli/pull/4892))
1920

2021
### Dependency updates
2122

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
bundle:
2+
name: stale_state_test
3+
4+
resources:
5+
jobs:
6+
job_1:
7+
name: Job 1
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
bundle:
2+
name: stale_state_test
3+
4+
resources:
5+
jobs:
6+
job_1:
7+
name: Job 1
8+
9+
job_2:
10+
name: Job 2
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
>>> errcode [CLI] bundle deployment bind job_2 [EXTERNAL_JOB_ID] --auto-approve
3+
Error: Resource already managed
4+
5+
The bundle is already managing a resource for resources.jobs.job_2 with ID '[JOB_2_ID]'.
6+
To bind to a different resource with ID '[EXTERNAL_JOB_ID]', you must first unbind the existing resource.
7+
8+
9+
Exit code: 1
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
>>> errcode [CLI] bundle deployment bind job_2 [EXTERNAL_JOB_ID] --auto-approve
3+
Error: terraform import: exit status 1
4+
5+
Error: Resource already managed by Terraform
6+
7+
Terraform is already managing a remote object for databricks_job.job_2. To
8+
import to this address you must first remove the existing object from the
9+
state.
10+
11+
12+
13+
14+
Exit code: 1

acceptance/bundle/deployment/bind/job/stale-state/out.test.toml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
=== Step 1: Deploy with job_1 only
3+
>>> [CLI] bundle deploy
4+
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/stale_state_test/default/files...
5+
Deploying resources...
6+
Updating deployment state...
7+
Deployment complete!
8+
9+
=== Step 2: Save stale local state (has only job_1, serial=1)
10+
=== Step 3: Add job_2 to config and deploy again
11+
>>> [CLI] bundle deploy
12+
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/stale_state_test/default/files...
13+
Deploying resources...
14+
Updating deployment state...
15+
Deployment complete!
16+
17+
=== Step 4: Record deployed job_2 ID from stateDeployed job_2 ID: [JOB_2_ID]
18+
19+
=== Step 5: Restore stale local state (only job_1, serial=1)
20+
=== Step 6: Create external job and try to bind (should fail: remote state already has job_2)
21+
External job ID: [EXTERNAL_JOB_ID]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
if [ "$DATABRICKS_BUNDLE_ENGINE" = "direct" ]; then
2+
state_file=".databricks/bundle/default/resources.json"
3+
else
4+
state_file=".databricks/bundle/default/terraform/terraform.tfstate"
5+
fi
6+
7+
title "Step 1: Deploy with job_1 only"
8+
trace $CLI bundle deploy
9+
10+
title "Step 2: Save stale local state (has only job_1, serial=1)"
11+
cp "$state_file" stale_state.json
12+
13+
title "Step 3: Add job_2 to config and deploy again"
14+
cp databricks_v2.yml databricks.yml
15+
trace $CLI bundle deploy
16+
17+
title "Step 4: Record deployed job_2 ID from state"
18+
echo "Deployed job_2 ID: $(read_id.py job_2)"
19+
20+
title "Step 5: Restore stale local state (only job_1, serial=1)"
21+
cp stale_state.json "$state_file"
22+
23+
title "Step 6: Create external job and try to bind (should fail: remote state already has job_2)\n"
24+
job_id=$($CLI jobs create --json '{"name": "External Job"}' | jq -r '.job_id')
25+
add_repl.py "$job_id" EXTERNAL_JOB_ID
26+
echo "External job ID: $job_id"
27+
trace errcode $CLI bundle deployment bind job_2 $job_id --auto-approve &> out.bind.$DATABRICKS_BUNDLE_ENGINE.txt
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Cloud = false
2+
3+
Ignore = [
4+
"databricks_v2.yml",
5+
"stale_state.json",
6+
]
7+
8+
[EnvMatrix]
9+
DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"]

cmd/apps/import.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ func runImport(ctx context.Context, w *databricks.WorkspaceClient, appName, outp
300300
var err error
301301
b, stateDesc, err = bundleutils.ProcessBundleRet(bindCmd, bundleutils.ProcessOptions{
302302
SkipInitContext: true,
303-
ReadState: true,
303+
AlwaysPull: true,
304304
InitFunc: func(b *bundle.Bundle) {
305305
b.Config.Bundle.Deployment.Lock.Force = false
306306
},

0 commit comments

Comments
 (0)