Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
84608d6
internal: Refactor immutable folder upload as an internal resource in…
andrewnester Jul 28, 2026
a5ab287
fix test and tidy up
andrewnester Jul 28, 2026
4a0d4bd
updated the output
andrewnester Jul 28, 2026
c869f1d
Merge branch 'main' into feat/upload-resource
andrewnester Jul 28, 2026
aa73e34
fix lint
andrewnester Jul 28, 2026
b4f272f
make snapshot a normal resource
andrewnester Jul 31, 2026
f31a466
clean up
andrewnester Jul 31, 2026
5943979
fix test
andrewnester Aug 3, 2026
195974d
fixes
andrewnester Aug 3, 2026
b6bb79f
refschema
andrewnester Aug 3, 2026
2b7b48f
lint fixes
andrewnester Aug 3, 2026
54a7dc0
use HasInternalResources
andrewnester Aug 3, 2026
fbd5229
fix test and etc
andrewnester Aug 4, 2026
7825722
stable bundle id
andrewnester Aug 4, 2026
b1e0be1
fix tests
andrewnester Aug 4, 2026
b8cdcec
fix typo
andrewnester Aug 5, 2026
f32d7fc
update refschema
andrewnester Aug 5, 2026
94eefc7
remove deadcode + update test output
andrewnester Aug 5, 2026
769c0c3
adr feedback
andrewnester Aug 5, 2026
c5cbf31
refschema
andrewnester Aug 5, 2026
af7f3ab
fix invariant coverage test
andrewnester Aug 6, 2026
5a05041
fix fmt
andrewnester Aug 6, 2026
6c74b0b
Merge branch 'main' into feat/upload-resource
andrewnester Aug 6, 2026
e816436
fmt
andrewnester Aug 10, 2026
0c8c6b6
Merge branch 'main' into feat/upload-resource
andrewnester Aug 13, 2026
03d79bd
update out.test.toml
andrewnester Aug 13, 2026
d879631
Merge branch 'main' into feat/upload-resource
andrewnester Aug 18, 2026
ba0d5b8
fix build
andrewnester Aug 18, 2026
4ac3aed
fix lint
andrewnester Aug 18, 2026
d07f4f8
fix output
andrewnester Aug 18, 2026
2408d9a
fix delete count
andrewnester Aug 18, 2026
8f2b500
fixes
andrewnester Aug 19, 2026
2060f2b
Merge branch 'main' into feat/upload-resource
andrewnester Aug 19, 2026
82f2714
updated output
andrewnester Aug 20, 2026
92ec65c
fix output
andrewnester Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions acceptance/bundle/deploy/immutable-no-artifacts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.txt
*.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

103 changes: 98 additions & 5 deletions acceptance/bundle/deploy/immutable-no-artifacts/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,114 @@ Workspace:

Validation OK!

>>> [CLI] bundle plan -o json

>>> jq .plan["resources.jobs.my_job"].new_state.value.tasks plan.json
[
{
"notebook_task": {
"base_parameters": {
"path": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/some_path"
},
"notebook_path": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/notebook"
},
"task_key": "notebook_task"
},
{
"environment_key": "env",
"spark_python_task": {
"python_file": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/main.py"
},
"task_key": "spark_python_task"
}
]

>>> jq .plan["resources.internal_immutable_snapshots.immutable"] plan.json
{
"action": "create",
"new_state": {
"value": {
"remote_root": "/Workspace/Users/[UUID]/.snapshots",
"relative_path": "[UUID]/[SNAPSHOT_HASH]",
"full_path": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]",
"bundle_id": "[UUID]",
"acl": [
{
"user_name": "[USERNAME]",
"permission_level": "CAN_READ"
}
]
}
}
}

>>> [CLI] bundle deploy
Uploading immutable bundle snapshot...
Created internal_immutable_snapshots.immutable
Created jobs.my_job
Files: 0 uploaded, 0 deleted
Resources: 1 created, 0 changed, 0 deleted, 0 unchanged
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> [CLI] jobs get [NUMID]
>>> jq .settings.tasks job.json
"/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/main.py"

>>> [CLI] jobs get [NUMID]
>>> jq .settings.tasks job.json
"/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/notebook"

>>> [CLI] jobs get [NUMID]
>>> jq .settings.tasks job.json
"/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/some_path"

>>> title Check snapshot plan after deploy:

=== Check snapshot plan after deploy:
>>> [CLI] bundle plan -o json

>>> jq .plan["resources.internal_immutable_snapshots.immutable"] plan.json
{
"action": "skip",
"remote_state": {
"relative_path": "[UUID]/[SNAPSHOT_HASH]",
"full_path": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]"
},
"changes": {
"acl": {
"action": "skip",
"reason": "missing_in_remote",
"old": [
{
"user_name": "[USERNAME]",
"permission_level": "CAN_READ"
}
],
"new": [
{
"user_name": "[USERNAME]",
"permission_level": "CAN_READ"
}
],
"remote": null
},
"bundle_id": {
"action": "skip",
"reason": "missing_in_remote",
"old": "[UUID]",
"new": "[UUID]",
"remote": ""
},
"remote_root": {
"action": "skip",
"reason": "missing_in_remote",
"old": "/Workspace/Users/[UUID]/.snapshots",
"new": "/Workspace/Users/[UUID]/.snapshots",
"remote": ""
}
}
}

>>> [CLI] bundle deploy
Files: 0 uploaded, 0 deleted
Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
Snapshot did not change as expected

>>> [CLI] bundle destroy --auto-approve
The following resources will be deleted:
delete resources.jobs.my_job
Expand Down
28 changes: 24 additions & 4 deletions acceptance/bundle/deploy/immutable-no-artifacts/script
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,33 @@ cleanup() {
trap cleanup EXIT

trace $CLI bundle validate
trace $CLI bundle deploy
trace $CLI bundle plan -o json > plan.json
trace jq '.plan["resources.jobs.my_job"].new_state.value.tasks' plan.json
trace jq '.plan["resources.internal_immutable_snapshots.immutable"]' plan.json

trace $CLI bundle deploy $(readplanarg plan.json)

# Get a job and check that task paths point into the snapshot
JOB_ID=$($CLI bundle summary -o json | jq -r '.resources.jobs.my_job.id')
trace $CLI jobs get $JOB_ID | jq '.settings.tasks' | jq '.[] | select(.spark_python_task != null) | .spark_python_task.python_file'
trace $CLI jobs get $JOB_ID | jq '.settings.tasks' | jq '.[] | select(.notebook_task != null) | .notebook_task.notebook_path'
trace $CLI jobs get $JOB_ID | jq '.settings.tasks' | jq '.[] | select(.notebook_task != null) | .notebook_task.base_parameters.path'
$CLI jobs get $JOB_ID > job.json
trace jq '.settings.tasks' job.json | jq '.[] | select(.spark_python_task != null) | .spark_python_task.python_file'
trace jq '.settings.tasks' job.json | jq '.[] | select(.notebook_task != null) | .notebook_task.notebook_path'
trace jq '.settings.tasks' job.json | jq '.[] | select(.notebook_task != null) | .notebook_task.base_parameters.path'
PYTHON_FILE_1=$(jq '.settings.tasks' job.json | jq '.[] | select(.spark_python_task != null) | .spark_python_task.python_file')

trace title "Check snapshot plan after deploy: "
trace $CLI bundle plan -o json > plan.json
trace jq '.plan["resources.internal_immutable_snapshots.immutable"]' plan.json

trace $CLI bundle deploy $(readplanarg plan.json)
rm plan.json
rm job.json

PYTHON_FILE_2=$($CLI jobs get $JOB_ID | jq '.settings.tasks' | jq '.[] | select(.spark_python_task != null) | .spark_python_task.python_file')
if [ "$PYTHON_FILE_1" == "$PYTHON_FILE_2" ]; then
echo "Snapshot did not change as expected"
else
echo "Snapshot changed"
exit 1
fi
trace $CLI bundle destroy --auto-approve
7 changes: 7 additions & 0 deletions acceptance/bundle/deploy/immutable-no-artifacts/test.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Cloud = false # Temporary disable cloud tests until the API is fully available
RecordRequests = true
EnvMatrix.READPLAN = ["", "1"]

# immutable_folder only works with the direct engine.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
Expand All @@ -17,3 +18,9 @@ Ignore = [
[[Repls]]
Old = '[0-9a-f]{64}'
New = '[SNAPSHOT_HASH]'

# When READPLAN=1, "bundle deploy" is called as "bundle deploy --plan plan.json".
# Normalize so both variants produce identical output.
[[Repls]]
Old = ' --plan plan.json'
New = ''
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

=== Deploy without permissions
>>> [CLI] bundle deploy
Uploading immutable bundle snapshot...
Created internal_immutable_snapshots.immutable
Created jobs.my_job
Files: 0 uploaded, 0 deleted
Resources: 1 created, 0 changed, 0 deleted, 0 unchanged
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged
First snapshot path: /Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/main.py

=== Add permissions and redeploy
Expand All @@ -21,11 +21,11 @@ Consider using a adding a top-level permissions section such as the following:
See https://docs.databricks.com/dev-tools/bundles/permissions.html to learn more about permission configuration.
in databricks.yml:22:3

Uploading immutable bundle snapshot...
Recreated internal_immutable_snapshots.immutable
Updated jobs.my_job
Created jobs.my_job.permissions
Files: 0 uploaded, 0 deleted
Resources: 1 created, 1 changed, 0 deleted, 0 unchanged
Resources: 2 created, 1 changed, 1 deleted, 0 unchanged
Second snapshot path: /Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/main.py

=== Verify snapshot path changed after permissions change
Expand Down
133 changes: 126 additions & 7 deletions acceptance/bundle/deploy/immutable/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ Validation OK!

>>> [CLI] bundle plan -o json
Building python_artifact...

>>> jq .plan["resources.jobs.my_job"].new_state.value.tasks plan.json
[
{
"notebook_task": {
"notebook_path": "${workspace.snapshot_path}/files/src/notebook"
"notebook_path": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/notebook"
},
"task_key": "notebook_task"
},
Expand All @@ -28,30 +30,147 @@ Building python_artifact...
{
"environment_key": "env",
"spark_python_task": {
"python_file": "${workspace.snapshot_path}/files/src/main.py"
"python_file": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/main.py"
},
"task_key": "spark_python_task"
}
]

>>> jq .plan["resources.jobs.my_job"].new_state.value.environments plan.json
[
{
"environment_key": "env",
"spec": {
"dependencies": [
"/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/artifacts/.internal/immutable-0.0.1-py3-none-any.whl"
],
"environment_version": "4"
}
}
]

>>> jq .plan["resources.internal_immutable_snapshots.immutable"] plan.json
{
"action": "create",
"new_state": {
"value": {
"remote_root": "/Workspace/Users/[UUID]/.snapshots",
"relative_path": "[UUID]/[SNAPSHOT_HASH]",
"full_path": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]",
"bundle_id": "[UUID]",
"acl": [
{
"user_name": "[USERNAME]",
"permission_level": "CAN_READ"
}
]
}
}
}

>>> [CLI] bundle deploy
Building python_artifact...
Uploading immutable bundle snapshot...
Created internal_immutable_snapshots.immutable
Created jobs.my_job
Files: 0 uploaded, 0 deleted
Resources: 1 created, 0 changed, 0 deleted, 0 unchanged
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> [CLI] jobs get [NUMID]
>>> jq .settings.tasks job.json
"/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/main.py"

>>> [CLI] jobs get [NUMID]
>>> jq .settings.tasks job.json
"/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/files/src/notebook"

>>> [CLI] jobs get [NUMID]
>>> jq .settings.environments[0].spec.dependencies job.json
[
"/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]/artifacts/.internal/immutable-0.0.1-py3-none-any.whl"
]

>>> title Check snapshot plan after deploy

=== Check snapshot plan after deploy
>>> [CLI] bundle plan -o json
Building python_artifact...

>>> jq .plan["resources.internal_immutable_snapshots.immutable"] plan.json
{
"action": "recreate",
"new_state": {
"value": {
"remote_root": "/Workspace/Users/[UUID]/.snapshots",
"relative_path": "[UUID]/[SNAPSHOT_HASH]",
"full_path": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]",
"bundle_id": "[UUID]",
"acl": [
{
"user_name": "[USERNAME]",
"permission_level": "CAN_READ"
}
]
}
},
"remote_state": {
"relative_path": "[UUID]/[SNAPSHOT_HASH]",
"full_path": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]"
},
"changes": {
"acl": {
"action": "skip",
"reason": "missing_in_remote",
"old": [
{
"user_name": "[USERNAME]",
"permission_level": "CAN_READ"
}
],
"new": [
{
"user_name": "[USERNAME]",
"permission_level": "CAN_READ"
}
],
"remote": null
},
"bundle_id": {
"action": "skip",
"reason": "missing_in_remote",
"old": "[UUID]",
"new": "[UUID]",
"remote": ""
},
"full_path": {
"action": "recreate",
"reason": "immutable",
"old": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]",
"new": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]",
"remote": "/Workspace/Users/[UUID]/.snapshots/[UUID]/[SNAPSHOT_HASH]"
},
"relative_path": {
"action": "recreate",
"reason": "immutable",
"old": "[UUID]/[SNAPSHOT_HASH]",
"new": "[UUID]/[SNAPSHOT_HASH]",
"remote": "[UUID]/[SNAPSHOT_HASH]"
},
"remote_root": {
"action": "skip",
"reason": "missing_in_remote",
"old": "/Workspace/Users/[UUID]/.snapshots",
"new": "/Workspace/Users/[UUID]/.snapshots",
"remote": ""
}
}
}

>>> [CLI] bundle deploy
Building python_artifact...
Recreated internal_immutable_snapshots.immutable
Updated jobs.my_job
Files: 0 uploaded, 0 deleted
Resources: 1 created, 1 changed, 1 deleted, 0 unchanged

=== Check that job uses a new snapshot: Snapshot changed as expected

>>> [CLI] bundle destroy --auto-approve
The following resources will be deleted:
delete resources.jobs.my_job
Expand Down
Loading
Loading