diff --git a/.nextchanges/bundles/7366.md b/.nextchanges/bundles/7366.md new file mode 100644 index 00000000000..8098357bd09 --- /dev/null +++ b/.nextchanges/bundles/7366.md @@ -0,0 +1 @@ +* Add `lifecycle.bind` to resources to bring an existing workspace resource under bundle management at deploy time instead of creating a new one. ([#7366](https://github.com/databricks/cli/pull/7366)) diff --git a/acceptance/bundle/deploy/bind/basic/databricks.yml b/acceptance/bundle/deploy/bind/basic/databricks.yml new file mode 100644 index 00000000000..4d627f32c87 --- /dev/null +++ b/acceptance/bundle/deploy/bind/basic/databricks.yml @@ -0,0 +1,10 @@ +bundle: + name: test-bind-basic-UNIQUE_NAME + +resources: + experiments: + foo: + name: /Shared/test-bind-basic-UNIQUE_NAME + lifecycle: + bind: + id: "PLACEHOLDER_EXPERIMENT_ID" diff --git a/acceptance/bundle/deploy/bind/basic/out.test.toml b/acceptance/bundle/deploy/bind/basic/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/basic/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/basic/output.txt b/acceptance/bundle/deploy/bind/basic/output.txt new file mode 100644 index 00000000000..68d4a22f609 --- /dev/null +++ b/acceptance/bundle/deploy/bind/basic/output.txt @@ -0,0 +1,42 @@ + +>>> [CLI] bundle plan +Bind: + experiments.foo (id: [NEW_EXPERIMENT_ID]) + +Deploy: + No changes + +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged, 1 to bind + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-basic-[UNIQUE_NAME]/default/files... +Bound experiments.foo +Files: 1 uploaded, 0 deleted +Resources: 0 created, 0 changed, 0 deleted, 1 unchanged, 1 bound + +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged + +>>> print_state.py +{ + "state_version": 3, + "cli_version": "[CLI_VERSION]", + "lineage": "[UUID]", + "serial": 1, + "state": { + "resources.experiments.foo": { + "__id__": "[NEW_EXPERIMENT_ID]", + "state": { + "name": "/Shared/test-bind-basic-[UNIQUE_NAME]" + } + } + } +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.experiments.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-basic-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/basic/script b/acceptance/bundle/deploy/bind/basic/script new file mode 100644 index 00000000000..21a52688afc --- /dev/null +++ b/acceptance/bundle/deploy/bind/basic/script @@ -0,0 +1,25 @@ +# Bundle names carry $UNIQUE_NAME so concurrent runs on the same workspace do not share +# a root path and its deployment lock. +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create an experiment whose configuration already matches the bundle. +NEW_EXPERIMENT_ID=$(MSYS_NO_PATHCONV=1 $CLI experiments create-experiment "/Shared/test-bind-basic-$UNIQUE_NAME" | jq -r .experiment_id) +add_repl "$NEW_EXPERIMENT_ID" NEW_EXPERIMENT_ID + +# Update the databricks.yml with the actual experiment ID +update_file.py databricks.yml 'PLACEHOLDER_EXPERIMENT_ID' "$NEW_EXPERIMENT_ID" + +# Run plan - should show import action +trace $CLI bundle plan + +# Deploy with auto-approve +trace $CLI bundle deploy --auto-approve + +# Plan again - should show no changes (skip) +trace $CLI bundle plan + +# Verify state file contains the imported ID +trace print_state.py | contains.py "$NEW_EXPERIMENT_ID" + +# Cleanup: destroy is allowed even with an active bind block +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/bind-and-resize/databricks.yml.tmpl b/acceptance/bundle/deploy/bind/bind-and-resize/databricks.yml.tmpl new file mode 100644 index 00000000000..24d298d5272 --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-resize/databricks.yml.tmpl @@ -0,0 +1,20 @@ +bundle: + name: test-bind-resize + +workspace: + root_path: ~/.bundle/$UNIQUE_NAME + +sync: + exclude: + - LOG.* + +resources: + clusters: + test_cluster: + cluster_name: test-cluster-$UNIQUE_NAME + spark_version: $DEFAULT_SPARK_VERSION + instance_pool_id: $TEST_INSTANCE_POOL_ID + num_workers: 3 + lifecycle: + bind: + id: "PLACEHOLDER_CLUSTER_ID" diff --git a/acceptance/bundle/deploy/bind/bind-and-resize/out.test.toml b/acceptance/bundle/deploy/bind/bind-and-resize/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-resize/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/bind-and-resize/output.txt b/acceptance/bundle/deploy/bind/bind-and-resize/output.txt new file mode 100644 index 00000000000..a65b550a8a1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-resize/output.txt @@ -0,0 +1,46 @@ + +=== Existing cluster has 2 workers; bundle configuration requests 3 +>>> [CLI] clusters get [CLUSTER_ID] +json.num_workers = 2; + +>>> [CLI] bundle plan +Bind: + clusters.test_cluster (id: [CLUSTER_ID]) + +Deploy: + resize clusters.test_cluster + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged, 1 to bind + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Bound clusters.test_cluster +Resized clusters.test_cluster +Files: 2 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +>>> print_requests.py //clusters/resize //clusters/edit +{ + "method": "POST", + "path": "/api/2.1/clusters/resize", + "body": { + "cluster_id": "[CLUSTER_ID]", + "num_workers": 3 + } +} + +=== Cluster should have new num_workers + +>>> [CLI] clusters get [CLUSTER_ID] +{ + "cluster_name": "test-cluster-[UNIQUE_NAME]", + "num_workers": 3 +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.clusters.test_cluster + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME] + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/bind-and-resize/script b/acceptance/bundle/deploy/bind/bind-and-resize/script new file mode 100644 index 00000000000..5779afb0c4b --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-resize/script @@ -0,0 +1,45 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +# The shared AWS pool uses Spot in a fixed AZ and can exhaust capacity before +# this test reaches bind planning. Use on-demand capacity with automatic AZ selection. +COMPUTE=$(jq -n --arg pool "$TEST_INSTANCE_POOL_ID" '{instance_pool_id: $pool}') +if [[ "$CLOUD_ENV_BASE" == aws ]]; then + COMPUTE=$(jq -n --arg node "$NODE_TYPE_ID" '{node_type_id: $node, aws_attributes: {availability: "ON_DEMAND", zone_id: "auto"}}') + update_file.py databricks.yml "instance_pool_id: $TEST_INSTANCE_POOL_ID" "node_type_id: $NODE_TYPE_ID + aws_attributes: + availability: ON_DEMAND + zone_id: auto" +fi +jq -n --arg name "test-cluster-$UNIQUE_NAME" --arg spark "$DEFAULT_SPARK_VERSION" --argjson compute "$COMPUTE" \ + '$compute + {cluster_name: $name, spark_version: $spark, num_workers: 2, autotermination_minutes: 60}' > LOG.create.json +CLUSTER_ID=$($CLI clusters create --no-wait --json @LOG.create.json | jq -r .cluster_id) +add_repl "$CLUSTER_ID" CLUSTER_ID +update_file.py databricks.yml 'PLACEHOLDER_CLUSTER_ID' "$CLUSTER_ID" + +cleanup() { + if [[ -n "$CLUSTER_ID" ]]; then + $CLI clusters permanent-delete "$CLUSTER_ID" &> LOG.cluster-cleanup + fi + rm -f "$OUT_REQUESTS" +} +trap cleanup EXIT + +RETRY_MAX_ATTEMPTS=120 RETRY_INTERVAL_MS=10000 retry --until '"state": "RUNNING"' $CLI clusters get "$CLUSTER_ID" > LOG.wait + +title "Existing cluster has 2 workers; bundle configuration requests 3" +trace $CLI clusters get "$CLUSTER_ID" | gron.py | grep '^json.num_workers = ' + +# Plan: config differs only in num_workers, so the bound running cluster is +# planned as bind_and_update backed by a resize. +trace $CLI bundle plan + +# Deploy: bind_and_update must go through the resize API, not clusters/edit. +trace $CLI bundle deploy --auto-approve +trace print_requests.py //clusters/resize //clusters/edit + +title "Cluster should have new num_workers\n" +trace $CLI clusters get "$CLUSTER_ID" | jq '{cluster_name,num_workers}' + +# Cleanup +trace $CLI bundle destroy --auto-approve +CLUSTER_ID="" diff --git a/acceptance/bundle/deploy/bind/bind-and-resize/test.toml b/acceptance/bundle/deploy/bind/bind-and-resize/test.toml new file mode 100644 index 00000000000..e7593c38d40 --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-resize/test.toml @@ -0,0 +1,3 @@ +RecordRequests = true + +Ignore = [".databricks", "databricks.yml"] diff --git a/acceptance/bundle/deploy/bind/bind-and-update/databricks.yml b/acceptance/bundle/deploy/bind/bind-and-update/databricks.yml new file mode 100644 index 00000000000..0c5a6a9fa9e --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-update/databricks.yml @@ -0,0 +1,19 @@ +bundle: + name: test-bind-update-UNIQUE_NAME + +resources: + jobs: + foo: + name: updated-job-name + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" diff --git a/acceptance/bundle/deploy/bind/bind-and-update/hello.py b/acceptance/bundle/deploy/bind/bind-and-update/hello.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-update/hello.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/deploy/bind/bind-and-update/out.test.toml b/acceptance/bundle/deploy/bind/bind-and-update/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-update/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/bind-and-update/output.txt b/acceptance/bundle/deploy/bind/bind-and-update/output.txt new file mode 100644 index 00000000000..666d0226942 --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-update/output.txt @@ -0,0 +1,41 @@ + +>>> [CLI] bundle plan +Bind: + jobs.foo (id: [NEW_JOB_ID]) + +Deploy: + update jobs.foo + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged, 1 to bind + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-update-[UNIQUE_NAME]/default/files... +Bound jobs.foo +Updated jobs.foo +Files: 2 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +>>> [CLI] jobs get [NEW_JOB_ID] +updated-job-name + +>>> [CLI] bundle plan +update jobs.foo + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-update-[UNIQUE_NAME]/default/files... +Updated jobs.foo +Files: 1 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged + +>>> [CLI] jobs get [NEW_JOB_ID] +second-update-name + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-update-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/bind-and-update/script b/acceptance/bundle/deploy/bind/bind-and-update/script new file mode 100644 index 00000000000..60a64f29814 --- /dev/null +++ b/acceptance/bundle/deploy/bind/bind-and-update/script @@ -0,0 +1,30 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create a job in the workspace with a different name +NEW_JOB_ID=$($CLI jobs create --json '{"name": "original-job-name", "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID + +# Update the databricks.yml with the actual job ID +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$NEW_JOB_ID" + +# Run plan - should show bind action (name differs from config) +trace $CLI bundle plan + +# Deploy with auto-approve +trace $CLI bundle deploy --auto-approve + +# Verify the job was updated +trace $CLI jobs get $NEW_JOB_ID | jq -r .settings.name + +# Now update the job name again in the config and deploy again. +# This time the action should be "update", not "bind", since the resource +# is already bound in state. +update_file.py databricks.yml 'updated-job-name' 'second-update-name' +trace $CLI bundle plan +trace $CLI bundle deploy --auto-approve + +# Verify the job was updated with the second name +trace $CLI jobs get $NEW_JOB_ID | jq -r .settings.name + +# Cleanup: destroy is allowed even with an active bind block +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/block-removed/databricks.yml b/acceptance/bundle/deploy/bind/block-removed/databricks.yml new file mode 100644 index 00000000000..cb7b2234af8 --- /dev/null +++ b/acceptance/bundle/deploy/bind/block-removed/databricks.yml @@ -0,0 +1,14 @@ +bundle: + name: test-bind-block-removed-UNIQUE_NAME + +resources: + pipelines: + foo: + name: test-pipeline + storage: /old/storage/path + libraries: + - notebook: + path: ./nb.sql + lifecycle: + bind: + id: "PLACEHOLDER_PIPELINE_ID" diff --git a/acceptance/bundle/deploy/bind/block-removed/databricks_unbound.yml b/acceptance/bundle/deploy/bind/block-removed/databricks_unbound.yml new file mode 100644 index 00000000000..99670d4f8c3 --- /dev/null +++ b/acceptance/bundle/deploy/bind/block-removed/databricks_unbound.yml @@ -0,0 +1,11 @@ +bundle: + name: test-bind-block-removed-UNIQUE_NAME + +resources: + pipelines: + foo: + name: test-pipeline + storage: /old/storage/path + libraries: + - notebook: + path: ./nb.sql diff --git a/acceptance/bundle/deploy/bind/block-removed/nb.sql b/acceptance/bundle/deploy/bind/block-removed/nb.sql new file mode 100644 index 00000000000..199ff507884 --- /dev/null +++ b/acceptance/bundle/deploy/bind/block-removed/nb.sql @@ -0,0 +1,2 @@ +-- Databricks notebook source +select 1 diff --git a/acceptance/bundle/deploy/bind/block-removed/out.test.toml b/acceptance/bundle/deploy/bind/block-removed/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/block-removed/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/block-removed/output.txt b/acceptance/bundle/deploy/bind/block-removed/output.txt new file mode 100644 index 00000000000..9ede5d99f4f --- /dev/null +++ b/acceptance/bundle/deploy/bind/block-removed/output.txt @@ -0,0 +1,37 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-block-removed-[UNIQUE_NAME]/default/files... +Bound pipelines.foo +Updated pipelines.foo +Files: 3 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +=== Removing only the bind block is a no-op +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-block-removed-[UNIQUE_NAME]/default/files... +Files: 1 uploaded, 0 deleted +Resources: 0 created, 0 changed, 0 deleted, 1 unchanged + +>>> print_state.py +json.state.resources.pipelines.foo.__id__ = "[NEW_PIPELINE_ID]"; + +=== Without the bind block, a storage change plans a recreate +>>> [CLI] bundle plan +recreate pipelines.foo + +Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.pipelines.foo + +This action will result in the deletion of the following Lakeflow Spark Declarative Pipelines along with the +Streaming Tables (STs) and Materialized Views (MVs) managed by them. Set 'cascade_on_destroy: false' on a pipeline to retain datasets on pipeline deletion: + delete resources.pipelines.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-block-removed-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/block-removed/script b/acceptance/bundle/deploy/bind/block-removed/script new file mode 100644 index 00000000000..a2d229b2980 --- /dev/null +++ b/acceptance/bundle/deploy/bind/block-removed/script @@ -0,0 +1,22 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" +update_file.py databricks_unbound.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create a pipeline whose storage matches the config and bind it +NEW_PIPELINE_ID=$($CLI pipelines create --json '{"name": "test-pipeline", "storage": "/old/storage/path", "allow_duplicate_names": true, "libraries": [{"notebook": {"path": "/Workspace/test"}}]}' | jq -r .pipeline_id) +add_repl.py $NEW_PIPELINE_ID NEW_PIPELINE_ID +update_file.py databricks.yml 'PLACEHOLDER_PIPELINE_ID' "$NEW_PIPELINE_ID" + +trace $CLI bundle deploy --auto-approve + +title "Removing only the bind block is a no-op" +cp databricks_unbound.yml databricks.yml +trace $CLI bundle plan +trace $CLI bundle deploy --auto-approve +trace print_state.py | gron.py | grep __id__ | contains.py "$NEW_PIPELINE_ID" + +title "Without the bind block, a storage change plans a recreate" +update_file.py databricks.yml "storage: /old/storage/path" "storage: /new/storage/path" +trace $CLI bundle plan + +# Cleanup +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/block-removed/test.toml b/acceptance/bundle/deploy/bind/block-removed/test.toml new file mode 100644 index 00000000000..a65df196368 --- /dev/null +++ b/acceptance/bundle/deploy/bind/block-removed/test.toml @@ -0,0 +1 @@ +Ignore = [".databricks", "databricks_unbound.yml"] diff --git a/acceptance/bundle/deploy/bind/delete-and-bind-conflict/databricks.yml b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/databricks.yml new file mode 100644 index 00000000000..ae172299eed --- /dev/null +++ b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/databricks.yml @@ -0,0 +1,16 @@ +bundle: + name: test-bind-delete-conflict-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-delete-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py diff --git a/acceptance/bundle/deploy/bind/delete-and-bind-conflict/databricks_conflict.yml b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/databricks_conflict.yml new file mode 100644 index 00000000000..0232d1f092b --- /dev/null +++ b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/databricks_conflict.yml @@ -0,0 +1,19 @@ +bundle: + name: test-bind-delete-conflict-UNIQUE_NAME + +resources: + jobs: + bar: + name: test-bind-delete-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" diff --git a/acceptance/bundle/deploy/bind/delete-and-bind-conflict/hello.py b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/hello.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/hello.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/deploy/bind/delete-and-bind-conflict/out.test.toml b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/delete-and-bind-conflict/output.txt b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/output.txt new file mode 100644 index 00000000000..065a7f6cbc7 --- /dev/null +++ b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/output.txt @@ -0,0 +1,22 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-delete-conflict-[UNIQUE_NAME]/default/files... +Created jobs.foo +Files: 3 uploaded, 0 deleted +Resources: 1 created, 0 changed, 0 deleted, 0 unchanged + +>>> musterr [CLI] bundle plan +Error: resources.jobs.bar has the same lifecycle.bind ID "[FOO_ID]" as existing resource "resources.jobs.foo"; remove lifecycle.bind or the conflicting resource + at resources.jobs.bar.lifecycle.bind.id + in databricks.yml:19:15 + +Error: bind validation failed + + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-delete-conflict-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/delete-and-bind-conflict/script b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/script new file mode 100644 index 00000000000..df869e60a36 --- /dev/null +++ b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/script @@ -0,0 +1,21 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" +update_file.py databricks_conflict.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Deploy foo to create it in state +trace $CLI bundle deploy --auto-approve + +# Get the job ID from state +JOB_ID=$(read_id.py foo) + +# Switch to a config that renames foo->bar and adds a bind block for bar +# with the same job ID. This creates a conflict: foo is being deleted +# (still in state) while bar is being bound with the same ID. +cp databricks.yml databricks.yml.bak +cp databricks_conflict.yml databricks.yml +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$JOB_ID" + +trace musterr $CLI bundle plan + +# Cleanup: restore original config and destroy +cp databricks.yml.bak databricks.yml +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/delete-and-bind-conflict/test.toml b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/test.toml new file mode 100644 index 00000000000..a07a7675610 --- /dev/null +++ b/acceptance/bundle/deploy/bind/delete-and-bind-conflict/test.toml @@ -0,0 +1 @@ +Ignore = [".databricks", "databricks.yml.bak", "databricks_conflict.yml"] diff --git a/acceptance/bundle/deploy/bind/destroy-allowed/databricks.yml b/acceptance/bundle/deploy/bind/destroy-allowed/databricks.yml new file mode 100644 index 00000000000..59f39207f6a --- /dev/null +++ b/acceptance/bundle/deploy/bind/destroy-allowed/databricks.yml @@ -0,0 +1,19 @@ +bundle: + name: test-bind-destroy-allowed-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" diff --git a/acceptance/bundle/deploy/bind/destroy-allowed/hello.py b/acceptance/bundle/deploy/bind/destroy-allowed/hello.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/deploy/bind/destroy-allowed/hello.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/deploy/bind/destroy-allowed/out.test.toml b/acceptance/bundle/deploy/bind/destroy-allowed/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/destroy-allowed/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/destroy-allowed/output.txt b/acceptance/bundle/deploy/bind/destroy-allowed/output.txt new file mode 100644 index 00000000000..3dd7baaa8d8 --- /dev/null +++ b/acceptance/bundle/deploy/bind/destroy-allowed/output.txt @@ -0,0 +1,15 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-destroy-allowed-[UNIQUE_NAME]/default/files... +Bound jobs.foo +Updated jobs.foo +Files: 2 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-destroy-allowed-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/destroy-allowed/script b/acceptance/bundle/deploy/bind/destroy-allowed/script new file mode 100644 index 00000000000..2ca53089a89 --- /dev/null +++ b/acceptance/bundle/deploy/bind/destroy-allowed/script @@ -0,0 +1,15 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create a job in the workspace +NEW_JOB_ID=$($CLI jobs create --json '{"name": "test-bind-job", "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID + +# Update the databricks.yml with the actual job ID +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$NEW_JOB_ID" + +# Deploy to bind the resource into state +trace $CLI bundle deploy --auto-approve + +# Destroy is allowed even with an active bind block: naming destroy explicitly +# captures the intent to tear the deployment down. +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/dev-mode/databricks.yml b/acceptance/bundle/deploy/bind/dev-mode/databricks.yml new file mode 100644 index 00000000000..723742f381a --- /dev/null +++ b/acceptance/bundle/deploy/bind/dev-mode/databricks.yml @@ -0,0 +1,26 @@ +bundle: + name: test-bind-dev-mode + +resources: + jobs: + foo: + name: test-bind-job + schedule: + quartz_cron_expression: "0 0 12 * * ?" + timezone_id: UTC + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" + +targets: + default: + mode: development diff --git a/acceptance/bundle/deploy/bind/dev-mode/out.test.toml b/acceptance/bundle/deploy/bind/dev-mode/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/dev-mode/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/dev-mode/output.txt b/acceptance/bundle/deploy/bind/dev-mode/output.txt new file mode 100644 index 00000000000..60e446adb60 --- /dev/null +++ b/acceptance/bundle/deploy/bind/dev-mode/output.txt @@ -0,0 +1,14 @@ + +>>> [CLI] bundle plan +Bind: + jobs.foo (id: [NEW_JOB_ID]) + +Deploy: + update jobs.foo + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged, 1 to bind + +>>> [CLI] bundle plan -o json +json.plan.resources.jobs.foo.changes.name.new = "[dev [USERNAME]] test-bind-job"; +json.plan.resources.jobs.foo.changes.schedule.pause_status.new = "PAUSED"; +json.plan.resources.jobs.foo.changes.tags.new.dev = "[USERNAME]"; diff --git a/acceptance/bundle/deploy/bind/dev-mode/script b/acceptance/bundle/deploy/bind/dev-mode/script new file mode 100644 index 00000000000..63f6344a41c --- /dev/null +++ b/acceptance/bundle/deploy/bind/dev-mode/script @@ -0,0 +1,11 @@ +# Binding in development mode applies the mode's transformations to the adopted +# resource: it is renamed with the [dev ] prefix, its schedule is paused and +# it is tagged. The plan is the only place that shows this before it happens. +# Only the new values are listed: the remote side differs between the test server +# and a real workspace (which reports pause_status: UNPAUSED). +NEW_JOB_ID=$($CLI jobs create --json '{"name": "test-bind-job", "schedule": {"quartz_cron_expression": "0 0 12 * * ?", "timezone_id": "UTC"}, "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$NEW_JOB_ID" + +trace $CLI bundle plan +trace $CLI bundle plan -o json | gron.py | grep -E 'changes\.(name|schedule|tags)\S*\.new' diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id-config/databricks.yml b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/databricks.yml new file mode 100644 index 00000000000..3820ee5551e --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/databricks.yml @@ -0,0 +1,33 @@ +bundle: + name: test-bind-duplicate-id-config + +resources: + jobs: + foo: + name: test-bind-dup-config-foo + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py + lifecycle: + bind: + id: "12345" + bar: + name: test-bind-dup-config-bar + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py + lifecycle: + bind: + id: "12345" diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id-config/hello.py b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/hello.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/hello.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id-config/out.test.toml b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id-config/output.txt b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/output.txt new file mode 100644 index 00000000000..6f22c9a60cb --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/output.txt @@ -0,0 +1,12 @@ + +>>> musterr [CLI] bundle plan +Error: resources.jobs.bar has the same lifecycle.bind ID "12345" as resources.jobs.foo; each workspace resource can only be bound once + at resources.jobs.bar.lifecycle.bind.id + in databricks.yml:33:15 + +Error: resources.jobs.foo has the same lifecycle.bind ID "12345" as resources.jobs.bar; each workspace resource can only be bound once + at resources.jobs.foo.lifecycle.bind.id + in databricks.yml:19:15 + +Error: bind validation failed + diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id-config/script b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/script new file mode 100644 index 00000000000..6c1284521fe --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id-config/script @@ -0,0 +1,3 @@ +# Two first-time bind blocks pointing at the same workspace ID must be rejected; +# otherwise both would create state entries managing one remote resource. +trace musterr $CLI bundle plan diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id/databricks.yml b/acceptance/bundle/deploy/bind/duplicate-bind-id/databricks.yml new file mode 100644 index 00000000000..6db6bc7d2ed --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id/databricks.yml @@ -0,0 +1,16 @@ +bundle: + name: test-bind-duplicate-id-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-dup-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id/databricks_with_bind.yml b/acceptance/bundle/deploy/bind/duplicate-bind-id/databricks_with_bind.yml new file mode 100644 index 00000000000..234b22fd66b --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id/databricks_with_bind.yml @@ -0,0 +1,30 @@ +bundle: + name: test-bind-duplicate-id-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-dup-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py + bar: + name: test-bind-dup-bar + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id/hello.py b/acceptance/bundle/deploy/bind/duplicate-bind-id/hello.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id/hello.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id/out.test.toml b/acceptance/bundle/deploy/bind/duplicate-bind-id/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id/output.txt b/acceptance/bundle/deploy/bind/duplicate-bind-id/output.txt new file mode 100644 index 00000000000..744bb060309 --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id/output.txt @@ -0,0 +1,22 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-duplicate-id-[UNIQUE_NAME]/default/files... +Created jobs.foo +Files: 3 uploaded, 0 deleted +Resources: 1 created, 0 changed, 0 deleted, 0 unchanged + +>>> musterr [CLI] bundle plan +Error: resources.jobs.bar has the same lifecycle.bind ID "[FOO_ID]" as existing resource "resources.jobs.foo"; remove lifecycle.bind or the conflicting resource + at resources.jobs.bar.lifecycle.bind.id + in databricks.yml:30:15 + +Error: bind validation failed + + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-duplicate-id-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id/script b/acceptance/bundle/deploy/bind/duplicate-bind-id/script new file mode 100644 index 00000000000..4ca237bffb0 --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id/script @@ -0,0 +1,21 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" +update_file.py databricks_with_bind.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Deploy foo to create it in state +trace $CLI bundle deploy --auto-approve + +# Get foo's job ID from state +JOB_ID=$(read_id.py foo) + +# Switch to a config that keeps foo AND adds bar with a bind block +# pointing to foo's ID. This is a conflict: foo is still managed in +# state and bar tries to bind the same resource ID. +cp databricks.yml databricks.yml.bak +cp databricks_with_bind.yml databricks.yml +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$JOB_ID" + +trace musterr $CLI bundle plan + +# Cleanup +cp databricks.yml.bak databricks.yml +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/duplicate-bind-id/test.toml b/acceptance/bundle/deploy/bind/duplicate-bind-id/test.toml new file mode 100644 index 00000000000..f70b5c78fff --- /dev/null +++ b/acceptance/bundle/deploy/bind/duplicate-bind-id/test.toml @@ -0,0 +1 @@ +Ignore = [".databricks", "databricks.yml.bak", "databricks_with_bind.yml"] diff --git a/acceptance/bundle/deploy/bind/gone-in-state/databricks.yml b/acceptance/bundle/deploy/bind/gone-in-state/databricks.yml new file mode 100644 index 00000000000..297233c545e --- /dev/null +++ b/acceptance/bundle/deploy/bind/gone-in-state/databricks.yml @@ -0,0 +1,19 @@ +bundle: + name: test-bind-gone-in-state-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" diff --git a/acceptance/bundle/deploy/bind/gone-in-state/out.test.toml b/acceptance/bundle/deploy/bind/gone-in-state/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/gone-in-state/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/gone-in-state/output.txt b/acceptance/bundle/deploy/bind/gone-in-state/output.txt new file mode 100644 index 00000000000..a9cb132d9ba --- /dev/null +++ b/acceptance/bundle/deploy/bind/gone-in-state/output.txt @@ -0,0 +1,29 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-gone-in-state-[UNIQUE_NAME]/default/files... +Bound jobs.foo +Updated jobs.foo +Files: 1 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +=== Delete the bound job out-of-band: the bind block pins that ID, so the deploy must not create a replacement +>>> [CLI] jobs delete [NEW_JOB_ID] + +>>> musterr [CLI] bundle plan +Error: cannot plan resources.jobs.foo: bound resource with ID "[NEW_JOB_ID]" no longer exists in the workspace; remove lifecycle.bind to create a new resource + +Error: planning failed + + +>>> musterr [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-gone-in-state-[UNIQUE_NAME]/default/files... +Error: cannot plan resources.jobs.foo: bound resource with ID "[NEW_JOB_ID]" no longer exists in the workspace; remove lifecycle.bind to create a new resource + +Error: planning failed + +Files: 0 uploaded, 0 deleted + +>>> [CLI] bundle destroy --auto-approve +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-gone-in-state-[UNIQUE_NAME]/default + +Destroy: 0 deleted diff --git a/acceptance/bundle/deploy/bind/gone-in-state/script b/acceptance/bundle/deploy/bind/gone-in-state/script new file mode 100644 index 00000000000..049e1cce043 --- /dev/null +++ b/acceptance/bundle/deploy/bind/gone-in-state/script @@ -0,0 +1,16 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create a job in the workspace and bind it +NEW_JOB_ID=$($CLI jobs create --json '{"name": "test-bind-job", "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$NEW_JOB_ID" + +trace $CLI bundle deploy --auto-approve + +title "Delete the bound job out-of-band: the bind block pins that ID, so the deploy must not create a replacement" +trace $CLI jobs delete $NEW_JOB_ID +trace musterr $CLI bundle plan +trace musterr $CLI bundle deploy --auto-approve + +# Cleanup: the job is already gone, destroy only drops the state entry +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/id-collision-different-type/databricks.yml b/acceptance/bundle/deploy/bind/id-collision-different-type/databricks.yml new file mode 100644 index 00000000000..8e5f17f4c89 --- /dev/null +++ b/acceptance/bundle/deploy/bind/id-collision-different-type/databricks.yml @@ -0,0 +1,16 @@ +bundle: + name: test-bind-id-collision-type-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-collision-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py diff --git a/acceptance/bundle/deploy/bind/id-collision-different-type/databricks_with_bind.yml b/acceptance/bundle/deploy/bind/id-collision-different-type/databricks_with_bind.yml new file mode 100644 index 00000000000..06987427d19 --- /dev/null +++ b/acceptance/bundle/deploy/bind/id-collision-different-type/databricks_with_bind.yml @@ -0,0 +1,22 @@ +bundle: + name: test-bind-id-collision-type-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-collision-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + experiments: + exp: + name: /Shared/test-bind-collision-exp + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" diff --git a/acceptance/bundle/deploy/bind/id-collision-different-type/out.test.toml b/acceptance/bundle/deploy/bind/id-collision-different-type/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/id-collision-different-type/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/id-collision-different-type/output.txt b/acceptance/bundle/deploy/bind/id-collision-different-type/output.txt new file mode 100644 index 00000000000..1d9c5cf80df --- /dev/null +++ b/acceptance/bundle/deploy/bind/id-collision-different-type/output.txt @@ -0,0 +1,18 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-id-collision-type-[UNIQUE_NAME]/default/files... +Created jobs.foo +Files: 2 uploaded, 0 deleted +Resources: 1 created, 0 changed, 0 deleted, 0 unchanged + +>>> musterr [CLI] bundle plan +Error: cannot bind resources.experiments.exp: resource with ID "[FOO_ID]" does not exist in workspace + + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-id-collision-type-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/id-collision-different-type/script b/acceptance/bundle/deploy/bind/id-collision-different-type/script new file mode 100644 index 00000000000..ca4191ec385 --- /dev/null +++ b/acceptance/bundle/deploy/bind/id-collision-different-type/script @@ -0,0 +1,19 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" +update_file.py databricks_with_bind.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Deploy foo to create it in state +trace $CLI bundle deploy --auto-approve + +JOB_ID=$(read_id.py foo) + +# Bind an experiment whose ID happens to equal the job's ID. IDs are only unique +# per resource type, so this is not a collision with jobs.foo; the bind proceeds and +# fails only because no experiment with that ID exists. +cp databricks.yml databricks.yml.bak +cp databricks_with_bind.yml databricks.yml +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$JOB_ID" +trace musterr $CLI bundle plan + +# Cleanup +cp databricks.yml.bak databricks.yml +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/id-collision-different-type/test.toml b/acceptance/bundle/deploy/bind/id-collision-different-type/test.toml new file mode 100644 index 00000000000..f70b5c78fff --- /dev/null +++ b/acceptance/bundle/deploy/bind/id-collision-different-type/test.toml @@ -0,0 +1 @@ +Ignore = [".databricks", "databricks.yml.bak", "databricks_with_bind.yml"] diff --git a/acceptance/bundle/deploy/bind/int-id/databricks.yml b/acceptance/bundle/deploy/bind/int-id/databricks.yml new file mode 100644 index 00000000000..b8d623b657c --- /dev/null +++ b/acceptance/bundle/deploy/bind/int-id/databricks.yml @@ -0,0 +1,19 @@ +bundle: + name: test-bind-int-id + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + lifecycle: + bind: + id: PLACEHOLDER_JOB_ID diff --git a/acceptance/bundle/deploy/bind/int-id/out.test.toml b/acceptance/bundle/deploy/bind/int-id/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/int-id/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/int-id/output.txt b/acceptance/bundle/deploy/bind/int-id/output.txt new file mode 100644 index 00000000000..72a4123aefd --- /dev/null +++ b/acceptance/bundle/deploy/bind/int-id/output.txt @@ -0,0 +1,9 @@ + +>>> [CLI] bundle plan +Bind: + jobs.foo (id: [NEW_JOB_ID]) + +Deploy: + update jobs.foo + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged, 1 to bind diff --git a/acceptance/bundle/deploy/bind/int-id/script b/acceptance/bundle/deploy/bind/int-id/script new file mode 100644 index 00000000000..b1c6700e046 --- /dev/null +++ b/acceptance/bundle/deploy/bind/int-id/script @@ -0,0 +1,6 @@ +# An unquoted ID is parsed by YAML as an integer and must normalize to the string ID. +NEW_JOB_ID=$($CLI jobs create --json '{"name": "test-bind-job", "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$NEW_JOB_ID" + +trace $CLI bundle plan diff --git a/acceptance/bundle/deploy/bind/missing-after-bind/databricks.yml b/acceptance/bundle/deploy/bind/missing-after-bind/databricks.yml new file mode 100644 index 00000000000..58094ef1fcb --- /dev/null +++ b/acceptance/bundle/deploy/bind/missing-after-bind/databricks.yml @@ -0,0 +1,14 @@ +bundle: + name: test-missing-after-bind-UNIQUE_NAME + +variables: + experiment_id: + description: Existing experiment to bind + +resources: + experiments: + existing: + name: /Shared/test-missing-after-bind-UNIQUE_NAME + lifecycle: + bind: + id: ${var.experiment_id} diff --git a/acceptance/bundle/deploy/bind/missing-after-bind/out.test.toml b/acceptance/bundle/deploy/bind/missing-after-bind/out.test.toml new file mode 100644 index 00000000000..a927a5fbc06 --- /dev/null +++ b/acceptance/bundle/deploy/bind/missing-after-bind/out.test.toml @@ -0,0 +1,2 @@ +Cloud = false +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/missing-after-bind/output.txt b/acceptance/bundle/deploy/bind/missing-after-bind/output.txt new file mode 100644 index 00000000000..125ce7a8845 --- /dev/null +++ b/acceptance/bundle/deploy/bind/missing-after-bind/output.txt @@ -0,0 +1,53 @@ + +=== Missing after binding: fresh deployment +>>> musterr [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-missing-after-bind-[UNIQUE_NAME]/default/files... +Bound experiments.existing +Error: cannot deploy resources.experiments.existing: bound resource with ID "[EXPERIMENT_ID]" no longer exists in the workspace; remove lifecycle.bind to create a new resource + +Files: 2 uploaded, 0 deleted + +>>> print_state.py +json.state.resources.experiments.existing.__id__ = "[EXPERIMENT_ID]"; + +=== The failed deployment has already uploaded its binding +>>> [CLI] bundle summary -o json +json.resources.experiments.existing.id = "[EXPERIMENT_ID]"; + +>>> [CLI] bundle deployment unbind existing +Warning: resources.experiments.existing.lifecycle.bind re-binds the resource on the next deploy; remove it to keep the resource unbound + at resources.experiments.existing.lifecycle.bind + in databricks.yml:14:11 + + +=== Missing after binding: saved deployment plan with a skip action +>>> [CLI] bundle plan -o json + +>>> jq .plan | with_entries(.value |= {action, prevent_id_change}) tmp.plan.json +{ + "resources.experiments.existing": { + "action": "skip", + "prevent_id_change": true + } +} + +>>> musterr [CLI] bundle deploy --plan tmp.plan.json --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-missing-after-bind-[UNIQUE_NAME]/default/files... +Bound experiments.existing +Error: cannot deploy resources.experiments.existing: bound resource with ID "[EXPERIMENT_ID]" no longer exists in the workspace; remove lifecycle.bind to create a new resource + +Files: 1 uploaded, 0 deleted + +>>> print_state.py +json.state.resources.experiments.existing.__id__ = "[EXPERIMENT_ID]"; + +=== Neither failed deploy creates or deletes an experiment +>>> print_requests.py //api/2.0/mlflow/experiments/create //api/2.0/mlflow/experiments/delete + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.experiments.existing + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-missing-after-bind-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/missing-after-bind/script b/acceptance/bundle/deploy/bind/missing-after-bind/script new file mode 100644 index 00000000000..baaf04f2abb --- /dev/null +++ b/acceptance/bundle/deploy/bind/missing-after-bind/script @@ -0,0 +1,27 @@ +update_file.py databricks.yml UNIQUE_NAME "$UNIQUE_NAME" +EXPERIMENT_ID=$(MSYS_NO_PATHCONV=1 $CLI experiments create-experiment "/Shared/test-missing-after-bind-$UNIQUE_NAME" | jq -r .experiment_id) +add_repl "$EXPERIMENT_ID" EXPERIMENT_ID +export BUNDLE_VAR_experiment_id=$EXPERIMENT_ID +print_requests.py //api/2.0/mlflow/experiments/create > LOG.setup + +title "Missing after binding: fresh deployment" +fault.py 'GET /api/2.0/mlflow/experiments/get' 404 2 1 RESOURCE_DOES_NOT_EXIST +trace musterr $CLI bundle deploy --auto-approve +trace print_state.py | gron.py | grep __id__ + +title "The failed deployment has already uploaded its binding" +rm .databricks/bundle/default/resources.json +trace $CLI bundle summary -o json | gron.py | grep 'experiments.existing.id' +trace $CLI bundle deployment unbind existing + +title "Missing after binding: saved deployment plan with a skip action" +trace $CLI bundle plan -o json > tmp.plan.json +trace jq '.plan | with_entries(.value |= {action, prevent_id_change})' tmp.plan.json +fault.py 'GET /api/2.0/mlflow/experiments/get' 404 1 1 RESOURCE_DOES_NOT_EXIST +trace musterr $CLI bundle deploy --plan tmp.plan.json --auto-approve +trace print_state.py | gron.py | grep __id__ + +title "Neither failed deploy creates or deletes an experiment" +trace print_requests.py //api/2.0/mlflow/experiments/create //api/2.0/mlflow/experiments/delete | contains.py '!POST' +trace $CLI bundle destroy --auto-approve +rm "$OUT_REQUESTS" diff --git a/acceptance/bundle/deploy/bind/missing-after-bind/test.toml b/acceptance/bundle/deploy/bind/missing-after-bind/test.toml new file mode 100644 index 00000000000..642b257795e --- /dev/null +++ b/acceptance/bundle/deploy/bind/missing-after-bind/test.toml @@ -0,0 +1,3 @@ +Cloud = false +RecordRequests = true +Ignore = ["tmp.*.json"] diff --git a/acceptance/bundle/deploy/bind/pure-bind/databricks.yml b/acceptance/bundle/deploy/bind/pure-bind/databricks.yml new file mode 100644 index 00000000000..1dc0f3557c2 --- /dev/null +++ b/acceptance/bundle/deploy/bind/pure-bind/databricks.yml @@ -0,0 +1,22 @@ +bundle: + name: test-bind-pure-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + permissions: + - group_name: users + level: CAN_VIEW + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" diff --git a/acceptance/bundle/deploy/bind/pure-bind/out.test.toml b/acceptance/bundle/deploy/bind/pure-bind/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/pure-bind/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/pure-bind/output.txt b/acceptance/bundle/deploy/bind/pure-bind/output.txt new file mode 100644 index 00000000000..aa7c1be7a20 --- /dev/null +++ b/acceptance/bundle/deploy/bind/pure-bind/output.txt @@ -0,0 +1,85 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-pure-[UNIQUE_NAME]/default/files... +Bound jobs.foo +Created jobs.foo.permissions +Updated jobs.foo +Files: 1 uploaded, 0 deleted +Resources: 1 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +=== Unbind the job. The bind block is still in config, so the next deploy re-binds it +>>> [CLI] bundle deployment unbind foo +Warning: resources.jobs.foo.lifecycle.bind re-binds the resource on the next deploy; remove it to keep the resource unbound + at resources.jobs.foo.lifecycle.bind + in databricks.yml:22:11 + + +=== Imperative bind warns that the declarative bind block is redundant +>>> [CLI] bundle deployment bind foo [NEW_JOB_ID] --auto-approve +Warning: resources.jobs.foo.lifecycle.bind already declares the resource ID; use 'bundle deploy' to bind it declaratively + at resources.jobs.foo.lifecycle.bind + in databricks.yml:22:11 + +Successfully bound job with an id '[NEW_JOB_ID]' +Run 'bundle deploy' to deploy changes to your workspace + +>>> [CLI] bundle deployment unbind foo +Warning: resources.jobs.foo.lifecycle.bind re-binds the resource on the next deploy; remove it to keep the resource unbound + at resources.jobs.foo.lifecycle.bind + in databricks.yml:22:11 + + +=== The job now matches the config, so this is a pure bind +>>> [CLI] bundle plan +Bind: + jobs.foo (id: [NEW_JOB_ID]) + +Deploy: + create jobs.foo.permissions + +Plan: 1 to add, 0 to change, 0 to delete, 1 unchanged, 1 to bind + +>>> [CLI] bundle plan -o json +json.bind.resources.jobs.foo.id = "[NEW_JOB_ID]"; +json.plan.resources.jobs.foo.id = "[NEW_JOB_ID]"; +json.plan.resources.jobs.foo.action = "skip"; +json.plan.resources.jobs.foo.permissions.depends_on[0].label = "${resources.jobs.foo.id}"; + +>>> print_requests.py --get --oneline //jobs/get +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-pure-[UNIQUE_NAME]/default/files... +Bound jobs.foo +Created jobs.foo.permissions +Files: 0 uploaded, 0 deleted +Resources: 1 created, 0 changed, 0 deleted, 1 unchanged, 1 bound + +=== Binding and deployment both verify that the job exists +>>> print_requests.py --get --oneline //jobs/get +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} +{"method": "GET", "path": "/api/2.2/jobs/get", "q": {"job_id": "[NEW_JOB_ID]"}} + +>>> print_state.py +json.state.resources.jobs.foo.__id__ = "[NEW_JOB_ID]"; +json.state.resources.jobs.foo.permissions.__id__ = "/jobs/[NEW_JOB_ID]"; + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-pure-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/pure-bind/script b/acceptance/bundle/deploy/bind/pure-bind/script new file mode 100644 index 00000000000..1cc6103013a --- /dev/null +++ b/acceptance/bundle/deploy/bind/pure-bind/script @@ -0,0 +1,31 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create a job in the workspace and bind it; deployment then updates fields such +# as deployment and edit_mode that the bundle sets on every job. +NEW_JOB_ID=$($CLI jobs create --json '{"name": "test-bind-job", "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$NEW_JOB_ID" + +trace $CLI bundle deploy --auto-approve + +title "Unbind the job. The bind block is still in config, so the next deploy re-binds it" +trace $CLI bundle deployment unbind foo + +title "Imperative bind warns that the declarative bind block is redundant" +trace $CLI bundle deployment bind foo "$NEW_JOB_ID" --auto-approve +trace $CLI bundle deployment unbind foo + +title "The job now matches the config, so this is a pure bind" +trace $CLI bundle plan +trace $CLI bundle plan -o json | gron.py | grep -E 'jobs\.foo\.(action|id)' + +trace print_requests.py --get --oneline //jobs/get +trace $CLI bundle deploy --auto-approve + +title "Binding and deployment both verify that the job exists" +trace print_requests.py --get --oneline //jobs/get +trace print_state.py | gron.py | grep __id__ + +# Cleanup +trace $CLI bundle destroy --auto-approve +rm -f out.requests.txt diff --git a/acceptance/bundle/deploy/bind/pure-bind/test.toml b/acceptance/bundle/deploy/bind/pure-bind/test.toml new file mode 100644 index 00000000000..159efe02696 --- /dev/null +++ b/acceptance/bundle/deploy/bind/pure-bind/test.toml @@ -0,0 +1 @@ +RecordRequests = true diff --git a/acceptance/bundle/deploy/bind/rebind-different-id/databricks.yml b/acceptance/bundle/deploy/bind/rebind-different-id/databricks.yml new file mode 100644 index 00000000000..34c1ecf3a82 --- /dev/null +++ b/acceptance/bundle/deploy/bind/rebind-different-id/databricks.yml @@ -0,0 +1,16 @@ +bundle: + name: test-bind-rebind-different-id-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py diff --git a/acceptance/bundle/deploy/bind/rebind-different-id/databricks_with_bind.yml b/acceptance/bundle/deploy/bind/rebind-different-id/databricks_with_bind.yml new file mode 100644 index 00000000000..40e85d37a0e --- /dev/null +++ b/acceptance/bundle/deploy/bind/rebind-different-id/databricks_with_bind.yml @@ -0,0 +1,19 @@ +bundle: + name: test-bind-rebind-different-id-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + lifecycle: + bind: + id: "999999999" diff --git a/acceptance/bundle/deploy/bind/rebind-different-id/out.test.toml b/acceptance/bundle/deploy/bind/rebind-different-id/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/rebind-different-id/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/rebind-different-id/output.txt b/acceptance/bundle/deploy/bind/rebind-different-id/output.txt new file mode 100644 index 00000000000..1bbd0c4cc7b --- /dev/null +++ b/acceptance/bundle/deploy/bind/rebind-different-id/output.txt @@ -0,0 +1,23 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-rebind-different-id-[UNIQUE_NAME]/default/files... +Created jobs.foo +Files: 2 uploaded, 0 deleted +Resources: 1 created, 0 changed, 0 deleted, 0 unchanged + +=== A bind block for a resource already in state must carry the state ID; a different ID is not a re-point +>>> musterr [CLI] bundle plan +Error: resources.jobs.foo: resource already bound to ID "[FOO_ID]", cannot bind as "999999999"; remove lifecycle.bind or unbind the existing resource + at resources.jobs.foo.lifecycle.bind.id + in databricks.yml:19:15 + +Error: bind validation failed + + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-rebind-different-id-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/rebind-different-id/script b/acceptance/bundle/deploy/bind/rebind-different-id/script new file mode 100644 index 00000000000..c5d08c6a07c --- /dev/null +++ b/acceptance/bundle/deploy/bind/rebind-different-id/script @@ -0,0 +1,15 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" +update_file.py databricks_with_bind.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Deploy foo to create it in state +trace $CLI bundle deploy --auto-approve +JOB_ID=$(read_id.py foo) + +title "A bind block for a resource already in state must carry the state ID; a different ID is not a re-point" +cp databricks.yml databricks.yml.bak +cp databricks_with_bind.yml databricks.yml +trace musterr $CLI bundle plan + +# Cleanup +cp databricks.yml.bak databricks.yml +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/rebind-different-id/test.toml b/acceptance/bundle/deploy/bind/rebind-different-id/test.toml new file mode 100644 index 00000000000..f70b5c78fff --- /dev/null +++ b/acceptance/bundle/deploy/bind/rebind-different-id/test.toml @@ -0,0 +1 @@ +Ignore = [".databricks", "databricks.yml.bak", "databricks_with_bind.yml"] diff --git a/acceptance/bundle/deploy/bind/recorded-phases/databricks.yml.tmpl b/acceptance/bundle/deploy/bind/recorded-phases/databricks.yml.tmpl new file mode 100644 index 00000000000..e1eb9693eb8 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recorded-phases/databricks.yml.tmpl @@ -0,0 +1,17 @@ +bundle: + name: test-bind-phases-$UNIQUE_NAME + +experimental: + deployment_history: true + +variables: + job_id: + description: Existing job + +resources: + jobs: + existing: + name: after-bind + lifecycle: + bind: + id: ${var.job_id} diff --git a/acceptance/bundle/deploy/bind/recorded-phases/out.test.toml b/acceptance/bundle/deploy/bind/recorded-phases/out.test.toml new file mode 100644 index 00000000000..c51c6ac1c26 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recorded-phases/out.test.toml @@ -0,0 +1,3 @@ +Cloud = false +EnvMatrix.DMS = ["true"] +EnvMatrix.MODE = ["shadow", "authoritative"] diff --git a/acceptance/bundle/deploy/bind/recorded-phases/output.txt b/acceptance/bundle/deploy/bind/recorded-phases/output.txt new file mode 100644 index 00000000000..160bc31e974 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recorded-phases/output.txt @@ -0,0 +1,167 @@ + +=== Planning neither records a version nor binds a resource +>>> [CLI] bundle plan -o json + +>>> print_requests.py --dms //api/2.0/bundle + +=== Bind completes before a failing apply starts its own version +>>> musterr [CLI] bundle deploy --plan tmp.plan.json --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-phases-[UNIQUE_NAME]/default/files... +Bound jobs.existing +Error: cannot update resources.jobs.existing: updating id=[JOB_ID]: Fault injected by test. (400 INVALID_PARAMETER_VALUE) + +Endpoint: POST [DATABRICKS_URL]/api/2.2/jobs/reset +HTTP Status: 400 Bad Request +API error_code: INVALID_PARAMETER_VALUE +API message: Fault injected by test. + +Files: 4 uploaded, 0 deleted + +>>> print_state.py +json.state.resources.jobs.existing.__id__ = "[JOB_ID]"; +json.state.resources.jobs.existing.state.name = "before-bind"; + +>>> print_requests.py --dms --keep --method PATCH //api/2.0/bundle/deployments ^//versions + +>>> print_requests.py --dms //versions //api/2.2/jobs/reset +{ + "method": "POST", + "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", + "version": "1", + "operations": [ + { + "action_type": "OPERATION_ACTION_TYPE_BIND", + "resource_key": "jobs.existing" + } + ], + "status": null, + "completion_reason": null, + "state_name": null +} +{ + "method": "PATCH", + "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/1/operations/jobs.existing", + "version": null, + "operations": null, + "status": "OPERATION_STATUS_SUCCEEDED", + "completion_reason": null, + "state_name": "before-bind" +} +{ + "method": "POST", + "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/1/complete", + "version": null, + "operations": null, + "status": null, + "completion_reason": "VERSION_COMPLETE_SUCCESS", + "state_name": null +} +{ + "method": "POST", + "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", + "version": "2", + "operations": [ + { + "action_type": "OPERATION_ACTION_TYPE_UPDATE", + "resource_key": "jobs.existing" + } + ], + "status": null, + "completion_reason": null, + "state_name": null +} +{ + "method": "POST", + "path": "/api/2.2/jobs/reset", + "version": null, + "operations": null, + "status": null, + "completion_reason": null, + "state_name": null +} +{ + "method": "PATCH", + "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/2/operations/jobs.existing", + "version": null, + "operations": null, + "status": "OPERATION_STATUS_FAILED", + "completion_reason": null, + "state_name": "before-bind" +} +{ + "method": "POST", + "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/2/complete", + "version": null, + "operations": null, + "status": null, + "completion_reason": "VERSION_COMPLETE_FAILURE", + "state_name": null +} + +=== Retrying plans only the unapplied update +>>> [CLI] bundle plan -o json + +>>> jq {has_bind: has("bind"), action: .plan["resources.jobs.existing"].action} tmp.retry.json +{ + "has_bind": false, + "action": "update" +} + +>>> [CLI] bundle deploy --plan tmp.retry.json --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-phases-[UNIQUE_NAME]/default/files... +Updated jobs.existing +Files: 1 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged + +>>> [CLI] jobs get [JOB_ID] +{ + "name": "after-bind", + "version": "3" +} + +>>> print_requests.py --dms //versions //api/2.2/jobs/reset +{ + "method": "POST", + "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", + "version": "3", + "operations": [ + { + "action_type": "OPERATION_ACTION_TYPE_UPDATE", + "resource_key": "jobs.existing" + } + ], + "status": null, + "completion_reason": null +} +{ + "method": "POST", + "path": "/api/2.2/jobs/reset", + "version": null, + "operations": null, + "status": null, + "completion_reason": null +} +{ + "method": "PATCH", + "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/3/operations/jobs.existing", + "version": null, + "operations": null, + "status": "OPERATION_STATUS_SUCCEEDED", + "completion_reason": null +} +{ + "method": "POST", + "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/3/complete", + "version": null, + "operations": null, + "status": null, + "completion_reason": "VERSION_COMPLETE_SUCCESS" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.existing + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-phases-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/recorded-phases/resources.dms.json b/acceptance/bundle/deploy/bind/recorded-phases/resources.dms.json new file mode 100644 index 00000000000..bf68eb50168 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recorded-phases/resources.dms.json @@ -0,0 +1,8 @@ +{ + "state_version": 3, + "features": {"deployment_history": {}}, + "cli_version": "0.0.0-dev", + "lineage": "test-lineage", + "serial": 0, + "state": {} +} diff --git a/acceptance/bundle/deploy/bind/recorded-phases/script b/acceptance/bundle/deploy/bind/recorded-phases/script new file mode 100644 index 00000000000..8d67dc15d4d --- /dev/null +++ b/acceptance/bundle/deploy/bind/recorded-phases/script @@ -0,0 +1,28 @@ +envsubst < databricks.yml.tmpl > databricks.yml +JOB_ID=$($CLI jobs create --json '{"name":"before-bind"}' | jq -r .job_id) +add_repl "$JOB_ID" JOB_ID +export BUNDLE_VAR_job_id=$JOB_ID +if [[ "$MODE" == authoritative ]]; then + mkdir -p .databricks/bundle/default + cp resources.dms.json .databricks/bundle/default/resources.json +fi + +title "Planning neither records a version nor binds a resource" +trace $CLI bundle plan -o json > tmp.plan.json +trace print_requests.py --dms //api/2.0/bundle + +title "Bind completes before a failing apply starts its own version" +fault.py 'POST /api/2.2/jobs/reset' 400 0 1 INVALID_PARAMETER_VALUE +trace musterr $CLI bundle deploy --plan tmp.plan.json --auto-approve +trace print_state.py | gron.py | grep -E '__id__|\.name =' +trace print_requests.py --dms --keep --method PATCH //api/2.0/bundle/deployments ^//versions +trace print_requests.py --dms //versions //api/2.2/jobs/reset | jq '{method, path, version: .q.version_id, operations: .body.operations, status: .body.status, completion_reason: .body.completion_reason, state_name: (if .body.state then (.body.state | fromjson | .state.name) else null end)}' + +title "Retrying plans only the unapplied update" +trace $CLI bundle plan -o json > tmp.retry.json +trace jq '{has_bind: has("bind"), action: .plan["resources.jobs.existing"].action}' tmp.retry.json +trace $CLI bundle deploy --plan tmp.retry.json --auto-approve +trace $CLI jobs get "$JOB_ID" | jq '.settings | {name, version: .deployment.version_id}' +trace print_requests.py --dms //versions //api/2.2/jobs/reset | jq '{method, path, version: .q.version_id, operations: .body.operations, status: .body.status, completion_reason: .body.completion_reason}' +trace $CLI bundle destroy --auto-approve +rm "$OUT_REQUESTS" diff --git a/acceptance/bundle/deploy/bind/recorded-phases/test.toml b/acceptance/bundle/deploy/bind/recorded-phases/test.toml new file mode 100644 index 00000000000..813ce95ace0 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recorded-phases/test.toml @@ -0,0 +1,5 @@ +Cloud = false +RecordRequests = true +Ignore = ["databricks.yml", "tmp.*.json"] +EnvMatrix.DMS = ["true"] +EnvMatrix.MODE = ["shadow", "authoritative"] diff --git a/acceptance/bundle/deploy/bind/recreate-blocked-in-state/databricks.yml b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/databricks.yml new file mode 100644 index 00000000000..9bec38b6a6b --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/databricks.yml @@ -0,0 +1,14 @@ +bundle: + name: test-bind-recreate-in-state-UNIQUE_NAME + +resources: + pipelines: + foo: + name: test-pipeline + storage: /old/storage/path + libraries: + - notebook: + path: ./nb.sql + lifecycle: + bind: + id: "PLACEHOLDER_PIPELINE_ID" diff --git a/acceptance/bundle/deploy/bind/recreate-blocked-in-state/nb.sql b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/nb.sql new file mode 100644 index 00000000000..199ff507884 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/nb.sql @@ -0,0 +1,2 @@ +-- Databricks notebook source +select 1 diff --git a/acceptance/bundle/deploy/bind/recreate-blocked-in-state/out.test.toml b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/recreate-blocked-in-state/output.txt b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/output.txt new file mode 100644 index 00000000000..b0cb58be5a6 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/output.txt @@ -0,0 +1,30 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-recreate-in-state-[UNIQUE_NAME]/default/files... +Bound pipelines.foo +Updated pipelines.foo +Files: 2 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +>>> musterr [CLI] bundle plan +Error: cannot plan resources.pipelines.foo: lifecycle.bind prevents recreating the bound resource + +Fields requiring resource recreation: + - storage (immutable) + +Match these fields to the existing resource to preserve its ID; remove lifecycle.bind to allow the ID to change + +Error: planning failed + + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.pipelines.foo + +This action will result in the deletion of the following Lakeflow Spark Declarative Pipelines along with the +Streaming Tables (STs) and Materialized Views (MVs) managed by them. Set 'cascade_on_destroy: false' on a pipeline to retain datasets on pipeline deletion: + delete resources.pipelines.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-recreate-in-state-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/recreate-blocked-in-state/script b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/script new file mode 100644 index 00000000000..e770f80d3cf --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked-in-state/script @@ -0,0 +1,22 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create a pipeline whose storage matches the config, so the first deploy binds +# it cleanly (no recreate). +NEW_PIPELINE_ID=$($CLI pipelines create --json '{"name": "test-pipeline", "storage": "/old/storage/path", "allow_duplicate_names": true, "libraries": [{"notebook": {"path": "/Workspace/test"}}]}' | jq -r .pipeline_id) +add_repl.py $NEW_PIPELINE_ID NEW_PIPELINE_ID + +# Update the databricks.yml with the actual pipeline ID +update_file.py databricks.yml 'PLACEHOLDER_PIPELINE_ID' "$NEW_PIPELINE_ID" + +# Deploy to bind the resource into state +trace $CLI bundle deploy --auto-approve + +# Change storage (immutable) while the bind block is still defined. The bind +# block must keep guarding against recreate even after the resource is in state. +# No leading slash in the arguments: Git Bash on Windows would rewrite it as a path. +update_file.py databricks.yml 'old/storage/path' 'new/storage/path' +trace musterr $CLI bundle plan + +# Cleanup +update_file.py databricks.yml 'new/storage/path' 'old/storage/path' +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/recreate-blocked/databricks.yml b/acceptance/bundle/deploy/bind/recreate-blocked/databricks.yml new file mode 100644 index 00000000000..99fa3d63fc0 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked/databricks.yml @@ -0,0 +1,14 @@ +bundle: + name: test-bind-recreate-UNIQUE_NAME + +resources: + pipelines: + foo: + name: test-pipeline + storage: /new/storage/path + libraries: + - notebook: + path: ./nb.sql + lifecycle: + bind: + id: "PLACEHOLDER_PIPELINE_ID" diff --git a/acceptance/bundle/deploy/bind/recreate-blocked/nb.sql b/acceptance/bundle/deploy/bind/recreate-blocked/nb.sql new file mode 100644 index 00000000000..199ff507884 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked/nb.sql @@ -0,0 +1,2 @@ +-- Databricks notebook source +select 1 diff --git a/acceptance/bundle/deploy/bind/recreate-blocked/out.test.toml b/acceptance/bundle/deploy/bind/recreate-blocked/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/recreate-blocked/output.txt b/acceptance/bundle/deploy/bind/recreate-blocked/output.txt new file mode 100644 index 00000000000..d64d64d6bb6 --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked/output.txt @@ -0,0 +1,42 @@ + +>>> musterr [CLI] bundle plan +Error: cannot plan resources.pipelines.foo: lifecycle.bind prevents recreating the bound resource + +Fields requiring resource recreation: + - storage (immutable) + +Match these fields to the existing resource to preserve its ID; remove lifecycle.bind to allow the ID to change + +Error: planning failed + + +=== Deploy commits the bind phase before rejecting recreation +>>> musterr [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-recreate-[UNIQUE_NAME]/default/files... +Bound pipelines.foo +Error: cannot plan resources.pipelines.foo: lifecycle.bind prevents recreating the bound resource + +Fields requiring resource recreation: + - storage (immutable) + +Match these fields to the existing resource to preserve its ID; remove lifecycle.bind to allow the ID to change + +Error: planning failed + +Files: 2 uploaded, 0 deleted + +>>> print_state.py +json.state.resources.pipelines.foo.__id__ = "[NEW_PIPELINE_ID]"; + +=== Destroy still deletes the adopted resource +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.pipelines.foo + +This action will result in the deletion of the following Lakeflow Spark Declarative Pipelines along with the +Streaming Tables (STs) and Materialized Views (MVs) managed by them. Set 'cascade_on_destroy: false' on a pipeline to retain datasets on pipeline deletion: + delete resources.pipelines.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-recreate-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/recreate-blocked/script b/acceptance/bundle/deploy/bind/recreate-blocked/script new file mode 100644 index 00000000000..192af0ca4ee --- /dev/null +++ b/acceptance/bundle/deploy/bind/recreate-blocked/script @@ -0,0 +1,18 @@ +update_file.py databricks.yml UNIQUE_NAME "$UNIQUE_NAME" + +# Create a pipeline with a different storage path +NEW_PIPELINE_ID=$($CLI pipelines create --json '{"name": "test-pipeline", "storage": "/old/storage/path", "allow_duplicate_names": true, "libraries": [{"notebook": {"path": "/Workspace/test"}}]}' | jq -r .pipeline_id) +add_repl.py $NEW_PIPELINE_ID NEW_PIPELINE_ID + +# Update the databricks.yml with the actual pipeline ID +update_file.py databricks.yml 'PLACEHOLDER_PIPELINE_ID' "$NEW_PIPELINE_ID" + +# Run plan - should fail because changing storage requires recreate which is blocked for binds +trace musterr $CLI bundle plan + +title "Deploy commits the bind phase before rejecting recreation" +trace musterr $CLI bundle deploy --auto-approve +trace print_state.py | gron.py | grep __id__ + +title "Destroy still deletes the adopted resource" +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/ref-to-bound/databricks.yml b/acceptance/bundle/deploy/bind/ref-to-bound/databricks.yml new file mode 100644 index 00000000000..5218a0c2a62 --- /dev/null +++ b/acceptance/bundle/deploy/bind/ref-to-bound/databricks.yml @@ -0,0 +1,25 @@ +bundle: + name: test-bind-ref-to-bound-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" + bar: + name: test-bind-runner + tasks: + - task_key: run_foo + run_job_task: + job_id: ${resources.jobs.foo.id} diff --git a/acceptance/bundle/deploy/bind/ref-to-bound/out.test.toml b/acceptance/bundle/deploy/bind/ref-to-bound/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/ref-to-bound/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/ref-to-bound/output.txt b/acceptance/bundle/deploy/bind/ref-to-bound/output.txt new file mode 100644 index 00000000000..2d74d5450d0 --- /dev/null +++ b/acceptance/bundle/deploy/bind/ref-to-bound/output.txt @@ -0,0 +1,34 @@ + +>>> [CLI] bundle plan +Bind: + jobs.foo (id: [NEW_JOB_ID]) + +Deploy: + create jobs.bar + update jobs.foo + +Plan: 1 to add, 1 to change, 0 to delete, 0 unchanged, 1 to bind + +=== The bound ID is known at plan time, so the reference is already resolved in the plan +>>> [CLI] bundle plan -o json +json.plan.resources.jobs.bar.new_state.value.tasks[0].run_job_task.job_id = [NEW_JOB_ID]; + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-ref-to-bound-[UNIQUE_NAME]/default/files... +Bound jobs.foo +Created jobs.bar +Updated jobs.foo +Files: 1 uploaded, 0 deleted +Resources: 1 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +>>> [CLI] jobs get [BAR_ID] +[NEW_JOB_ID] + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.bar + delete resources.jobs.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-ref-to-bound-[UNIQUE_NAME]/default + +Destroy: 2 deleted diff --git a/acceptance/bundle/deploy/bind/ref-to-bound/script b/acceptance/bundle/deploy/bind/ref-to-bound/script new file mode 100644 index 00000000000..e033312fe5e --- /dev/null +++ b/acceptance/bundle/deploy/bind/ref-to-bound/script @@ -0,0 +1,18 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create a job in the workspace; bar refers to it through ${resources.jobs.foo.id} +NEW_JOB_ID=$($CLI jobs create --json '{"name": "test-bind-job", "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$NEW_JOB_ID" + +trace $CLI bundle plan + +title "The bound ID is known at plan time, so the reference is already resolved in the plan" +trace $CLI bundle plan -o json | gron.py | grep run_job_task + +trace $CLI bundle deploy --auto-approve +BAR_ID=$(read_id.py bar) +trace $CLI jobs get $BAR_ID | jq '.settings.tasks[0].run_job_task.job_id' + +# Cleanup +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/removed-from-config/databricks.yml b/acceptance/bundle/deploy/bind/removed-from-config/databricks.yml new file mode 100644 index 00000000000..b9058bbe0c6 --- /dev/null +++ b/acceptance/bundle/deploy/bind/removed-from-config/databricks.yml @@ -0,0 +1,19 @@ +bundle: + name: test-bind-removed-from-config-UNIQUE_NAME + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" diff --git a/acceptance/bundle/deploy/bind/removed-from-config/databricks_nores.yml b/acceptance/bundle/deploy/bind/removed-from-config/databricks_nores.yml new file mode 100644 index 00000000000..9b759d9f81f --- /dev/null +++ b/acceptance/bundle/deploy/bind/removed-from-config/databricks_nores.yml @@ -0,0 +1,2 @@ +bundle: + name: test-bind-removed-from-config-UNIQUE_NAME diff --git a/acceptance/bundle/deploy/bind/removed-from-config/out.test.toml b/acceptance/bundle/deploy/bind/removed-from-config/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/removed-from-config/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/removed-from-config/output.txt b/acceptance/bundle/deploy/bind/removed-from-config/output.txt new file mode 100644 index 00000000000..86f7351c6ff --- /dev/null +++ b/acceptance/bundle/deploy/bind/removed-from-config/output.txt @@ -0,0 +1,27 @@ + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-removed-from-config-[UNIQUE_NAME]/default/files... +Bound jobs.foo +Updated jobs.foo +Files: 2 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +=== Remove the resource from config. Its lifecycle.bind goes with it, so, as with prevent_destroy, the protection ends and the deploy deletes the job +>>> [CLI] bundle plan +delete jobs.foo + +Plan: 0 to add, 0 to change, 1 to delete, 0 unchanged + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-removed-from-config-[UNIQUE_NAME]/default/files... +Deleted jobs.foo +Files: 1 uploaded, 0 deleted +Resources: 0 created, 0 changed, 1 deleted, 0 unchanged + +>>> musterr [CLI] jobs get [NEW_JOB_ID] +Error: Job [NEW_JOB_ID] does not exist. + +>>> [CLI] bundle destroy --auto-approve +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-removed-from-config-[UNIQUE_NAME]/default + +Destroy: 0 deleted diff --git a/acceptance/bundle/deploy/bind/removed-from-config/script b/acceptance/bundle/deploy/bind/removed-from-config/script new file mode 100644 index 00000000000..082c5fa089f --- /dev/null +++ b/acceptance/bundle/deploy/bind/removed-from-config/script @@ -0,0 +1,18 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" +update_file.py databricks_nores.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create a job in the workspace and bind it +NEW_JOB_ID=$($CLI jobs create --json '{"name": "test-bind-job", "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$NEW_JOB_ID" + +trace $CLI bundle deploy --auto-approve + +title "Remove the resource from config. Its lifecycle.bind goes with it, so, as with prevent_destroy, the protection ends and the deploy deletes the job" +cp databricks_nores.yml databricks.yml +trace $CLI bundle plan +trace $CLI bundle deploy --auto-approve +trace musterr $CLI jobs get $NEW_JOB_ID + +# Cleanup +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/removed-from-config/test.toml b/acceptance/bundle/deploy/bind/removed-from-config/test.toml new file mode 100644 index 00000000000..4c1cffe2567 --- /dev/null +++ b/acceptance/bundle/deploy/bind/removed-from-config/test.toml @@ -0,0 +1 @@ +Ignore = [".databricks", "databricks_nores.yml"] diff --git a/acceptance/bundle/deploy/bind/resource-not-found/databricks.yml b/acceptance/bundle/deploy/bind/resource-not-found/databricks.yml new file mode 100644 index 00000000000..02196311478 --- /dev/null +++ b/acceptance/bundle/deploy/bind/resource-not-found/databricks.yml @@ -0,0 +1,19 @@ +bundle: + name: test-bind-not-found + +resources: + jobs: + foo: + name: test-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: ./hello.py + lifecycle: + bind: + id: "999999999" diff --git a/acceptance/bundle/deploy/bind/resource-not-found/hello.py b/acceptance/bundle/deploy/bind/resource-not-found/hello.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/deploy/bind/resource-not-found/hello.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/deploy/bind/resource-not-found/out.test.toml b/acceptance/bundle/deploy/bind/resource-not-found/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/resource-not-found/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/resource-not-found/output.txt b/acceptance/bundle/deploy/bind/resource-not-found/output.txt new file mode 100644 index 00000000000..ce68cb9da32 --- /dev/null +++ b/acceptance/bundle/deploy/bind/resource-not-found/output.txt @@ -0,0 +1,4 @@ + +>>> musterr [CLI] bundle plan +Error: cannot bind resources.jobs.foo: resource with ID "999999999" does not exist in workspace + diff --git a/acceptance/bundle/deploy/bind/resource-not-found/script b/acceptance/bundle/deploy/bind/resource-not-found/script new file mode 100644 index 00000000000..6226cb85916 --- /dev/null +++ b/acceptance/bundle/deploy/bind/resource-not-found/script @@ -0,0 +1,2 @@ +# Try to plan with a non-existent job ID - should fail +trace musterr $CLI bundle plan diff --git a/acceptance/bundle/deploy/bind/saved-plan-recreate/change_storage.py b/acceptance/bundle/deploy/bind/saved-plan-recreate/change_storage.py new file mode 100644 index 00000000000..a439f346cbb --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan-recreate/change_storage.py @@ -0,0 +1 @@ +r["storage"] = "/changed/storage" # noqa: F821 - edit_resource.py provides r diff --git a/acceptance/bundle/deploy/bind/saved-plan-recreate/databricks.yml b/acceptance/bundle/deploy/bind/saved-plan-recreate/databricks.yml new file mode 100644 index 00000000000..a4b0eca9f48 --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan-recreate/databricks.yml @@ -0,0 +1,18 @@ +bundle: + name: test-bind-saved-recreate-UNIQUE_NAME + +variables: + pipeline_id: + description: Existing pipeline + +resources: + pipelines: + existing: + name: existing-pipeline + storage: /old/storage + libraries: + - notebook: + path: /Workspace/test + lifecycle: + bind: + id: ${var.pipeline_id} diff --git a/acceptance/bundle/deploy/bind/saved-plan-recreate/out.test.toml b/acceptance/bundle/deploy/bind/saved-plan-recreate/out.test.toml new file mode 100644 index 00000000000..a927a5fbc06 --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan-recreate/out.test.toml @@ -0,0 +1,2 @@ +Cloud = false +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/saved-plan-recreate/output.txt b/acceptance/bundle/deploy/bind/saved-plan-recreate/output.txt new file mode 100644 index 00000000000..7be6803315b --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan-recreate/output.txt @@ -0,0 +1,45 @@ + +=== Save a plan that preserves the resource +>>> [CLI] bundle plan -o json + +>>> jq .plan | with_entries(.value |= {action, prevent_id_change}) tmp.plan.json +{ + "resources.pipelines.existing": { + "action": "update", + "prevent_id_change": true + } +} + +=== Deploy binds, then rejects a newly required recreation +>>> musterr [CLI] bundle deploy --plan tmp.plan.json --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-saved-recreate-[UNIQUE_NAME]/default/files... +Bound pipelines.existing +Error: cannot deploy resources.pipelines.existing: lifecycle.bind prevents recreating the bound resource + +Fields requiring resource recreation: + - storage (immutable) + +Match these fields to the existing resource to preserve its ID; remove lifecycle.bind to allow the ID to change + +Files: 4 uploaded, 0 deleted + +>>> print_state.py +json.state.resources.pipelines.existing.__id__ = "[PIPELINE_ID]"; + +>>> print_requests.py //api/2.0/pipelines + +=== The committed binding survives pulling state again +>>> [CLI] bundle summary -o json +json.resources.pipelines.existing.id = "[PIPELINE_ID]"; + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.pipelines.existing + +This action will result in the deletion of the following Lakeflow Spark Declarative Pipelines along with the +Streaming Tables (STs) and Materialized Views (MVs) managed by them. Set 'cascade_on_destroy: false' on a pipeline to retain datasets on pipeline deletion: + delete resources.pipelines.existing + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-saved-recreate-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/saved-plan-recreate/script b/acceptance/bundle/deploy/bind/saved-plan-recreate/script new file mode 100644 index 00000000000..e2560971799 --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan-recreate/script @@ -0,0 +1,21 @@ +update_file.py databricks.yml UNIQUE_NAME "$UNIQUE_NAME" +PIPELINE_ID=$($CLI pipelines create --json '{"name":"existing-pipeline","storage":"/old/storage","libraries":[{"notebook":{"path":"/Workspace/test"}}]}' | jq -r .pipeline_id) +add_repl "$PIPELINE_ID" PIPELINE_ID +export BUNDLE_VAR_pipeline_id=$PIPELINE_ID + +title "Save a plan that preserves the resource" +trace $CLI bundle plan -o json > tmp.plan.json +trace jq '.plan | with_entries(.value |= {action, prevent_id_change})' tmp.plan.json +edit_resource.py pipelines "$PIPELINE_ID" < change_storage.py +print_requests.py //api/2.0/pipelines > LOG.setup + +title "Deploy binds, then rejects a newly required recreation" +trace musterr $CLI bundle deploy --plan tmp.plan.json --auto-approve +trace print_state.py | gron.py | grep __id__ +trace print_requests.py //api/2.0/pipelines + +title "The committed binding survives pulling state again" +rm .databricks/bundle/default/resources.json +trace $CLI bundle summary -o json | gron.py | grep 'pipelines.existing.id' +trace $CLI bundle destroy --auto-approve +rm "$OUT_REQUESTS" diff --git a/acceptance/bundle/deploy/bind/saved-plan-recreate/test.toml b/acceptance/bundle/deploy/bind/saved-plan-recreate/test.toml new file mode 100644 index 00000000000..68b3b278dcb --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan-recreate/test.toml @@ -0,0 +1,4 @@ +# The fake server lets this test simulate an immutable field changing after planning. +Cloud = false +RecordRequests = true +Ignore = ["tmp.*.json"] diff --git a/acceptance/bundle/deploy/bind/saved-plan/databricks.yml b/acceptance/bundle/deploy/bind/saved-plan/databricks.yml new file mode 100644 index 00000000000..74df6045d8c --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan/databricks.yml @@ -0,0 +1,22 @@ +bundle: + name: test-bind-saved-plan-UNIQUE_NAME + +variables: + job_id: + description: Existing job to bind + experiment_id: + description: Existing experiment to bind + +resources: + jobs: + updated: + name: saved-plan-job + lifecycle: + bind: + id: ${var.job_id} + experiments: + unchanged: + name: /Shared/test-bind-saved-plan-UNIQUE_NAME + lifecycle: + bind: + id: ${var.experiment_id} diff --git a/acceptance/bundle/deploy/bind/saved-plan/out.test.toml b/acceptance/bundle/deploy/bind/saved-plan/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/saved-plan/output.txt b/acceptance/bundle/deploy/bind/saved-plan/output.txt new file mode 100644 index 00000000000..92c5610a67a --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan/output.txt @@ -0,0 +1,77 @@ + +=== The saved plan contains bindings and ordinary deployment actions +>>> [CLI] bundle plan -o json + +>>> jq {plan_version, bind: (.bind | with_entries(.value |= {id})), plan: (.plan | with_entries(.value |= {action, prevent_id_change}))} tmp.plan.json +{ + "plan_version": [PLAN_VERSION], + "bind": { + "resources.experiments.unchanged": { + "id": "[EXPERIMENT_ID]" + }, + "resources.jobs.updated": { + "id": "[JOB_ID]" + } + }, + "plan": { + "resources.experiments.unchanged": { + "action": "skip", + "prevent_id_change": true + }, + "resources.jobs.updated": { + "action": "update", + "prevent_id_change": true + } + } +} + +>>> find.py resources\.json$ --expect 0 + +>>> print_requests.py //api/2.0/workspace/import + +=== Deploy uses the saved IDs and configuration +>>> [CLI] bundle deploy --plan tmp.plan.json --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-saved-plan-[UNIQUE_NAME]/default/files... +Bound experiments.unchanged +Bound jobs.updated +Updated jobs.updated +Files: 2 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 1 unchanged, 2 bound + +>>> [CLI] jobs get [JOB_ID] +saved-plan-job + +>>> print_state.py +json.state.resources.experiments.unchanged.__id__ = "[EXPERIMENT_ID]"; +json.state.resources.jobs.updated.__id__ = "[JOB_ID]"; + +=== Reusing a plan after state has changed is rejected +>>> musterr [CLI] bundle deploy --plan tmp.plan.json --auto-approve +Error: plan lineage "" does not match state lineage "[UUID]"; the state may have been modified by another process + + +=== A subsequent plan has no bind phase +>>> [CLI] bundle plan -o json + +>>> jq {has_bind: has("bind"), actions: [.plan[].action]} tmp.next.json +{ + "has_bind": false, + "actions": [ + "skip", + "skip" + ] +} + +>>> [CLI] bundle deploy --plan tmp.next.json --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-saved-plan-[UNIQUE_NAME]/default/files... +Files: 2 uploaded, 0 deleted +Resources: 0 created, 0 changed, 0 deleted, 2 unchanged + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.experiments.unchanged + delete resources.jobs.updated + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-saved-plan-[UNIQUE_NAME]/default + +Destroy: 2 deleted diff --git a/acceptance/bundle/deploy/bind/saved-plan/script b/acceptance/bundle/deploy/bind/saved-plan/script new file mode 100644 index 00000000000..048aea82b49 --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan/script @@ -0,0 +1,31 @@ +update_file.py databricks.yml UNIQUE_NAME "$UNIQUE_NAME" +JOB_ID=$($CLI jobs create --json '{"name":"original-job"}' | jq -r .job_id) +EXPERIMENT_ID=$(MSYS_NO_PATHCONV=1 $CLI experiments create-experiment "/Shared/test-bind-saved-plan-$UNIQUE_NAME" | jq -r .experiment_id) +add_repl "$JOB_ID" JOB_ID +add_repl "$EXPERIMENT_ID" EXPERIMENT_ID +export BUNDLE_VAR_job_id=$JOB_ID BUNDLE_VAR_experiment_id=$EXPERIMENT_ID + +title "The saved plan contains bindings and ordinary deployment actions" +trace $CLI bundle plan -o json > tmp.plan.json +trace jq '{plan_version, bind: (.bind | with_entries(.value |= {id})), plan: (.plan | with_entries(.value |= {action, prevent_id_change}))}' tmp.plan.json +trace find.py 'resources\.json$' --expect 0 +trace print_requests.py //api/2.0/workspace/import + +title "Deploy uses the saved IDs and configuration" +update_file.py databricks.yml saved-plan-job ignored-local-name +export BUNDLE_VAR_job_id=not-the-planned-id +trace $CLI bundle deploy --plan tmp.plan.json --auto-approve +trace $CLI jobs get "$JOB_ID" | jq -r .settings.name +trace print_state.py | gron.py | grep __id__ + +title "Reusing a plan after state has changed is rejected" +trace musterr $CLI bundle deploy --plan tmp.plan.json --auto-approve + +title "A subsequent plan has no bind phase" +update_file.py databricks.yml ignored-local-name saved-plan-job +export BUNDLE_VAR_job_id=$JOB_ID +trace $CLI bundle plan -o json > tmp.next.json +trace jq '{has_bind: has("bind"), actions: [.plan[].action]}' tmp.next.json +trace $CLI bundle deploy --plan tmp.next.json --auto-approve +trace $CLI bundle destroy --auto-approve +rm "$OUT_REQUESTS" diff --git a/acceptance/bundle/deploy/bind/saved-plan/test.toml b/acceptance/bundle/deploy/bind/saved-plan/test.toml new file mode 100644 index 00000000000..c5daf70b466 --- /dev/null +++ b/acceptance/bundle/deploy/bind/saved-plan/test.toml @@ -0,0 +1,2 @@ +RecordRequests = true +Ignore = ["tmp.*.json"] diff --git a/acceptance/bundle/deploy/bind/selected/databricks.yml b/acceptance/bundle/deploy/bind/selected/databricks.yml new file mode 100644 index 00000000000..9f3c0a0f838 --- /dev/null +++ b/acceptance/bundle/deploy/bind/selected/databricks.yml @@ -0,0 +1,27 @@ +bundle: + name: test-bind-selected-UNIQUE_NAME + +variables: + parent_id: + description: Existing parent job + other_id: + description: Existing unrelated job + +resources: + jobs: + parent: + name: selected-parent + lifecycle: + bind: + id: ${var.parent_id} + child: + name: selected-child + tasks: + - task_key: parent + run_job_task: + job_id: ${resources.jobs.parent.id} + other: + name: unselected + lifecycle: + bind: + id: ${var.other_id} diff --git a/acceptance/bundle/deploy/bind/selected/out.test.toml b/acceptance/bundle/deploy/bind/selected/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/selected/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/selected/output.txt b/acceptance/bundle/deploy/bind/selected/output.txt new file mode 100644 index 00000000000..0e71665205c --- /dev/null +++ b/acceptance/bundle/deploy/bind/selected/output.txt @@ -0,0 +1,61 @@ + +=== Selecting a child includes its binding dependency +>>> [CLI] bundle plan --select child -o json + +>>> jq {bind: (.bind | keys), plan: (.plan | keys), not_selected} tmp.plan.json +{ + "bind": [ + "resources.jobs.parent" + ], + "plan": [ + "resources.jobs.child", + "resources.jobs.parent" + ], + "not_selected": 1 +} + +=== Applying the selection leaves the unrelated resource unbound +>>> [CLI] bundle deploy --plan tmp.plan.json --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-selected-[UNIQUE_NAME]/default/files... +Bound jobs.parent +Created jobs.child +Updated jobs.parent +Files: 2 uploaded, 0 deleted +Resources: 1 created, 1 changed, 0 deleted, 0 unchanged, 1 bound, 1 not selected + +>>> print_state.py +json.state.resources.jobs.child.__id__ = "[CHILD_ID]"; +json.state.resources.jobs.parent.__id__ = "[PARENT_ID]"; + +=== A fresh selected deploy also leaves it unbound +>>> [CLI] bundle deploy --select child --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-selected-[UNIQUE_NAME]/default/files... +Files: 0 uploaded, 0 deleted +Resources: 0 created, 0 changed, 0 deleted, 2 unchanged, 1 not selected + +>>> print_state.py +json.state.resources.jobs.child.__id__ = "[CHILD_ID]"; +json.state.resources.jobs.parent.__id__ = "[PARENT_ID]"; + +=== The remaining binding can be applied separately +>>> [CLI] bundle deploy --select other --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-selected-[UNIQUE_NAME]/default/files... +Bound jobs.other +Updated jobs.other +Files: 0 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged, 1 bound, 2 not selected + +>>> print_state.py +json.state.resources.jobs.child.__id__ = "[CHILD_ID]"; +json.state.resources.jobs.other.__id__ = "[OTHER_ID]"; +json.state.resources.jobs.parent.__id__ = "[PARENT_ID]"; + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.child + delete resources.jobs.other + delete resources.jobs.parent + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-selected-[UNIQUE_NAME]/default + +Destroy: 3 deleted diff --git a/acceptance/bundle/deploy/bind/selected/script b/acceptance/bundle/deploy/bind/selected/script new file mode 100644 index 00000000000..24c18bed5bf --- /dev/null +++ b/acceptance/bundle/deploy/bind/selected/script @@ -0,0 +1,24 @@ +update_file.py databricks.yml UNIQUE_NAME "$UNIQUE_NAME" +PARENT_ID=$($CLI jobs create --json '{"name":"parent"}' | jq -r .job_id) +OTHER_ID=$($CLI jobs create --json '{"name":"other"}' | jq -r .job_id) +add_repl "$PARENT_ID" PARENT_ID +add_repl "$OTHER_ID" OTHER_ID +export BUNDLE_VAR_parent_id=$PARENT_ID BUNDLE_VAR_other_id=$OTHER_ID + +title "Selecting a child includes its binding dependency" +trace $CLI bundle plan --select child -o json > tmp.plan.json +trace jq '{bind: (.bind | keys), plan: (.plan | keys), not_selected}' tmp.plan.json + +title "Applying the selection leaves the unrelated resource unbound" +trace $CLI bundle deploy --plan tmp.plan.json --auto-approve +add_repl "$(read_id.py child)" CHILD_ID +trace print_state.py | gron.py | grep __id__ + +title "A fresh selected deploy also leaves it unbound" +trace $CLI bundle deploy --select child --auto-approve +trace print_state.py | gron.py | grep __id__ + +title "The remaining binding can be applied separately" +trace $CLI bundle deploy --select other --auto-approve +trace print_state.py | gron.py | grep __id__ +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/bind/selected/test.toml b/acceptance/bundle/deploy/bind/selected/test.toml new file mode 100644 index 00000000000..751ceccb63d --- /dev/null +++ b/acceptance/bundle/deploy/bind/selected/test.toml @@ -0,0 +1 @@ +Ignore = ["tmp.*.json"] diff --git a/acceptance/bundle/deploy/bind/target-override/databricks.yml b/acceptance/bundle/deploy/bind/target-override/databricks.yml new file mode 100644 index 00000000000..f8012429493 --- /dev/null +++ b/acceptance/bundle/deploy/bind/target-override/databricks.yml @@ -0,0 +1,28 @@ +bundle: + name: test-bind-target-override + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + lifecycle: + bind: + id: "999999999" + +targets: + default: + resources: + jobs: + foo: + lifecycle: + bind: + id: "PLACEHOLDER_JOB_ID" diff --git a/acceptance/bundle/deploy/bind/target-override/out.test.toml b/acceptance/bundle/deploy/bind/target-override/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/target-override/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/target-override/output.txt b/acceptance/bundle/deploy/bind/target-override/output.txt new file mode 100644 index 00000000000..14da1960634 --- /dev/null +++ b/acceptance/bundle/deploy/bind/target-override/output.txt @@ -0,0 +1,16 @@ + +>>> [CLI] bundle validate -o json +{ + "bind": { + "id": "[NEW_JOB_ID]" + } +} + +>>> [CLI] bundle plan +Bind: + jobs.foo (id: [NEW_JOB_ID]) + +Deploy: + update jobs.foo + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged, 1 to bind diff --git a/acceptance/bundle/deploy/bind/target-override/script b/acceptance/bundle/deploy/bind/target-override/script new file mode 100644 index 00000000000..f1bf38e0aba --- /dev/null +++ b/acceptance/bundle/deploy/bind/target-override/script @@ -0,0 +1,8 @@ +# The ID of the resource to adopt differs per target, so it is set through the usual +# target override of the resource; the root value is a placeholder here. +NEW_JOB_ID=$($CLI jobs create --json '{"name": "test-bind-job", "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID +update_file.py databricks.yml 'PLACEHOLDER_JOB_ID' "$NEW_JOB_ID" + +trace $CLI bundle validate -o json | jq '.resources.jobs.foo.lifecycle' +trace $CLI bundle plan diff --git a/acceptance/bundle/deploy/bind/test.toml b/acceptance/bundle/deploy/bind/test.toml new file mode 100644 index 00000000000..f59fefe3e02 --- /dev/null +++ b/acceptance/bundle/deploy/bind/test.toml @@ -0,0 +1,2 @@ +Cloud = true +Ignore = [".databricks"] diff --git a/acceptance/bundle/deploy/bind/variable-id/databricks.yml b/acceptance/bundle/deploy/bind/variable-id/databricks.yml new file mode 100644 index 00000000000..69b6ad12019 --- /dev/null +++ b/acceptance/bundle/deploy/bind/variable-id/databricks.yml @@ -0,0 +1,23 @@ +bundle: + name: test-bind-variable-id-UNIQUE_NAME + +variables: + job_id: + description: ID of the existing job to bind + +resources: + jobs: + foo: + name: test-bind-job + environments: + - environment_key: default + spec: + client: "1" + tasks: + - task_key: my_task + environment_key: default + spark_python_task: + python_file: /Workspace/test.py + lifecycle: + bind: + id: ${var.job_id} diff --git a/acceptance/bundle/deploy/bind/variable-id/out.test.toml b/acceptance/bundle/deploy/bind/variable-id/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/deploy/bind/variable-id/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/deploy/bind/variable-id/output.txt b/acceptance/bundle/deploy/bind/variable-id/output.txt new file mode 100644 index 00000000000..4b5235fd3c4 --- /dev/null +++ b/acceptance/bundle/deploy/bind/variable-id/output.txt @@ -0,0 +1,27 @@ + +>>> [CLI] bundle plan --var job_id=[NEW_JOB_ID] +Bind: + jobs.foo (id: [NEW_JOB_ID]) + +Deploy: + update jobs.foo + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged, 1 to bind + +>>> [CLI] bundle deploy --auto-approve --var job_id=[NEW_JOB_ID] +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bind-variable-id-[UNIQUE_NAME]/default/files... +Bound jobs.foo +Updated jobs.foo +Files: 1 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged, 1 bound + +>>> print_state.py +json.state.resources.jobs.foo.__id__ = "[NEW_JOB_ID]"; + +>>> [CLI] bundle destroy --auto-approve --var job_id=[NEW_JOB_ID] +The following resources will be deleted: + delete resources.jobs.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bind-variable-id-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/deploy/bind/variable-id/script b/acceptance/bundle/deploy/bind/variable-id/script new file mode 100644 index 00000000000..21a2b7fd0ee --- /dev/null +++ b/acceptance/bundle/deploy/bind/variable-id/script @@ -0,0 +1,11 @@ +update_file.py databricks.yml 'UNIQUE_NAME' "$UNIQUE_NAME" + +# Create a job in the workspace; its ID reaches the bind block through a variable +NEW_JOB_ID=$($CLI jobs create --json '{"name": "test-bind-job", "environments": [{"environment_key": "default", "spec": {"client": "1"}}], "tasks": [{"task_key": "my_task", "environment_key": "default", "spark_python_task": {"python_file": "/Workspace/test.py"}}]}' | jq -r .job_id) +add_repl.py $NEW_JOB_ID NEW_JOB_ID +trace $CLI bundle plan --var "job_id=$NEW_JOB_ID" +trace $CLI bundle deploy --auto-approve --var "job_id=$NEW_JOB_ID" +trace print_state.py | gron.py | grep __id__ + +# Cleanup +trace $CLI bundle destroy --auto-approve --var "job_id=$NEW_JOB_ID" diff --git a/acceptance/bundle/deploy/readplan/cli-version-mismatch/plan.json b/acceptance/bundle/deploy/readplan/cli-version-mismatch/plan.json index 932edf74f84..bd9561c9ec8 100644 --- a/acceptance/bundle/deploy/readplan/cli-version-mismatch/plan.json +++ b/acceptance/bundle/deploy/readplan/cli-version-mismatch/plan.json @@ -1,5 +1,5 @@ { - "plan_version": 2, + "plan_version": 3, "cli_version": "0.0.0-test", "plan": { "resources.jobs.job": { diff --git a/acceptance/bundle/deploy/readplan/lineage-mismatch/plan.dms.json b/acceptance/bundle/deploy/readplan/lineage-mismatch/plan.dms.json index aa3f25e2d27..7ed9e44220e 100644 --- a/acceptance/bundle/deploy/readplan/lineage-mismatch/plan.dms.json +++ b/acceptance/bundle/deploy/readplan/lineage-mismatch/plan.dms.json @@ -1,5 +1,5 @@ { - "plan_version": 2, + "plan_version": 3, "cli_version": "0.0.0-test", "lineage": "different-lineage", "serial": 0, diff --git a/acceptance/bundle/deploy/readplan/lineage-mismatch/plan.json b/acceptance/bundle/deploy/readplan/lineage-mismatch/plan.json index 42cce15cbfc..80310520edb 100644 --- a/acceptance/bundle/deploy/readplan/lineage-mismatch/plan.json +++ b/acceptance/bundle/deploy/readplan/lineage-mismatch/plan.json @@ -1,5 +1,5 @@ { - "plan_version": 2, + "plan_version": 3, "cli_version": "0.0.0-test", "lineage": "different-lineage", "serial": 0, diff --git a/acceptance/bundle/deploy/readplan/plan-version-mismatch/output.txt b/acceptance/bundle/deploy/readplan/plan-version-mismatch/output.txt index 387aac2335c..3ec4497a23e 100644 --- a/acceptance/bundle/deploy/readplan/plan-version-mismatch/output.txt +++ b/acceptance/bundle/deploy/readplan/plan-version-mismatch/output.txt @@ -1,4 +1,4 @@ -Error: plan version mismatch: plan has version 999 (generated with CLI "0.0.0"), but current version is 2 +Error: plan version mismatch: plan has version 999 (generated with CLI "0.0.0"), but current version is 3 Exit code: 1 diff --git a/acceptance/bundle/refschema/output/alerts.txt b/acceptance/bundle/refschema/output/alerts.txt index 566299e11cd..518dcb5369c 100644 --- a/acceptance/bundle/refschema/output/alerts.txt +++ b/acceptance/bundle/refschema/output/alerts.txt @@ -34,6 +34,8 @@ resources.alerts.*.evaluation.threshold.value.string_value string ALL resources.alerts.*.file_path string INPUT resources.alerts.*.id string ALL resources.alerts.*.lifecycle resources.Lifecycle INPUT +resources.alerts.*.lifecycle.bind *resources.Bind INPUT +resources.alerts.*.lifecycle.bind.id string INPUT resources.alerts.*.lifecycle.depends_on []resources.DependsOn INPUT resources.alerts.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.alerts.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/apps.txt b/acceptance/bundle/refschema/output/apps.txt index 3e1f91151f1..3087fafd5c0 100644 --- a/acceptance/bundle/refschema/output/apps.txt +++ b/acceptance/bundle/refschema/output/apps.txt @@ -91,6 +91,8 @@ resources.apps.*.id string ALL resources.apps.*.lifecycle *dresources.StateLifecycle REMOTE STATE resources.apps.*.lifecycle *resources.LifecycleWithStarted INPUT resources.apps.*.lifecycle resources.Lifecycle INPUT +resources.apps.*.lifecycle.bind *resources.Bind INPUT +resources.apps.*.lifecycle.bind.id string INPUT resources.apps.*.lifecycle.depends_on []resources.DependsOn INPUT resources.apps.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.apps.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/catalogs.txt b/acceptance/bundle/refschema/output/catalogs.txt index feaee00d2ca..e2c0a061582 100644 --- a/acceptance/bundle/refschema/output/catalogs.txt +++ b/acceptance/bundle/refschema/output/catalogs.txt @@ -14,6 +14,8 @@ resources.catalogs.*.full_name string REMOTE resources.catalogs.*.id string INPUT resources.catalogs.*.isolation_mode catalog.CatalogIsolationMode REMOTE resources.catalogs.*.lifecycle resources.Lifecycle INPUT +resources.catalogs.*.lifecycle.bind *resources.Bind INPUT +resources.catalogs.*.lifecycle.bind.id string INPUT resources.catalogs.*.lifecycle.depends_on []resources.DependsOn INPUT resources.catalogs.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.catalogs.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/cluster_policies.txt b/acceptance/bundle/refschema/output/cluster_policies.txt index da5be2b6b9b..bb8f08ad455 100644 --- a/acceptance/bundle/refschema/output/cluster_policies.txt +++ b/acceptance/bundle/refschema/output/cluster_policies.txt @@ -23,6 +23,8 @@ resources.cluster_policies.*.libraries[*].pypi.repo string ALL resources.cluster_policies.*.libraries[*].requirements string ALL resources.cluster_policies.*.libraries[*].whl string ALL resources.cluster_policies.*.lifecycle resources.Lifecycle INPUT +resources.cluster_policies.*.lifecycle.bind *resources.Bind INPUT +resources.cluster_policies.*.lifecycle.bind.id string INPUT resources.cluster_policies.*.lifecycle.depends_on []resources.DependsOn INPUT resources.cluster_policies.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.cluster_policies.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/clusters.txt b/acceptance/bundle/refschema/output/clusters.txt index ad9c1e333a5..e672cb77771 100644 --- a/acceptance/bundle/refschema/output/clusters.txt +++ b/acceptance/bundle/refschema/output/clusters.txt @@ -140,6 +140,8 @@ resources.clusters.*.libraries[*].whl string ALL resources.clusters.*.lifecycle *dresources.StateLifecycle REMOTE STATE resources.clusters.*.lifecycle *resources.LifecycleWithStarted INPUT resources.clusters.*.lifecycle resources.Lifecycle INPUT +resources.clusters.*.lifecycle.bind *resources.Bind INPUT +resources.clusters.*.lifecycle.bind.id string INPUT resources.clusters.*.lifecycle.depends_on []resources.DependsOn INPUT resources.clusters.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.clusters.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/dashboards.txt b/acceptance/bundle/refschema/output/dashboards.txt index 300a7d9c3a7..6a5a1356938 100644 --- a/acceptance/bundle/refschema/output/dashboards.txt +++ b/acceptance/bundle/refschema/output/dashboards.txt @@ -8,6 +8,8 @@ resources.dashboards.*.etag string ALL resources.dashboards.*.file_path string INPUT resources.dashboards.*.id string INPUT resources.dashboards.*.lifecycle resources.Lifecycle INPUT +resources.dashboards.*.lifecycle.bind *resources.Bind INPUT +resources.dashboards.*.lifecycle.bind.id string INPUT resources.dashboards.*.lifecycle.depends_on []resources.DependsOn INPUT resources.dashboards.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.dashboards.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/data_quality_monitors.txt b/acceptance/bundle/refschema/output/data_quality_monitors.txt index c8868667fae..2faf3d71439 100644 --- a/acceptance/bundle/refschema/output/data_quality_monitors.txt +++ b/acceptance/bundle/refschema/output/data_quality_monitors.txt @@ -74,6 +74,8 @@ resources.data_quality_monitors.*.data_profiling_config.time_series.timestamp_co resources.data_quality_monitors.*.data_profiling_config.warehouse_id string ALL resources.data_quality_monitors.*.id string INPUT resources.data_quality_monitors.*.lifecycle resources.Lifecycle INPUT +resources.data_quality_monitors.*.lifecycle.bind *resources.Bind INPUT +resources.data_quality_monitors.*.lifecycle.bind.id string INPUT resources.data_quality_monitors.*.lifecycle.depends_on []resources.DependsOn INPUT resources.data_quality_monitors.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.data_quality_monitors.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/database_catalogs.txt b/acceptance/bundle/refschema/output/database_catalogs.txt index e23c5246e31..f36b1a79eb5 100644 --- a/acceptance/bundle/refschema/output/database_catalogs.txt +++ b/acceptance/bundle/refschema/output/database_catalogs.txt @@ -3,6 +3,8 @@ resources.database_catalogs.*.database_instance_name string ALL resources.database_catalogs.*.database_name string ALL resources.database_catalogs.*.id string INPUT resources.database_catalogs.*.lifecycle resources.Lifecycle INPUT +resources.database_catalogs.*.lifecycle.bind *resources.Bind INPUT +resources.database_catalogs.*.lifecycle.bind.id string INPUT resources.database_catalogs.*.lifecycle.depends_on []resources.DependsOn INPUT resources.database_catalogs.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.database_catalogs.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/database_instances.txt b/acceptance/bundle/refschema/output/database_instances.txt index d09ebf65dc2..ad4308b7245 100644 --- a/acceptance/bundle/refschema/output/database_instances.txt +++ b/acceptance/bundle/refschema/output/database_instances.txt @@ -27,6 +27,8 @@ resources.database_instances.*.enable_pg_native_login bool ALL resources.database_instances.*.enable_readable_secondaries bool ALL resources.database_instances.*.id string INPUT resources.database_instances.*.lifecycle resources.Lifecycle INPUT +resources.database_instances.*.lifecycle.bind *resources.Bind INPUT +resources.database_instances.*.lifecycle.bind.id string INPUT resources.database_instances.*.lifecycle.depends_on []resources.DependsOn INPUT resources.database_instances.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.database_instances.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/experiments.txt b/acceptance/bundle/refschema/output/experiments.txt index a85aafdd5d0..85384fd3e5f 100644 --- a/acceptance/bundle/refschema/output/experiments.txt +++ b/acceptance/bundle/refschema/output/experiments.txt @@ -4,6 +4,8 @@ resources.experiments.*.experiment_id string REMOTE resources.experiments.*.id string INPUT resources.experiments.*.last_update_time int64 REMOTE resources.experiments.*.lifecycle resources.Lifecycle INPUT +resources.experiments.*.lifecycle.bind *resources.Bind INPUT +resources.experiments.*.lifecycle.bind.id string INPUT resources.experiments.*.lifecycle.depends_on []resources.DependsOn INPUT resources.experiments.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.experiments.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/external_locations.txt b/acceptance/bundle/refschema/output/external_locations.txt index 4e058354db2..5950751b3e8 100644 --- a/acceptance/bundle/refschema/output/external_locations.txt +++ b/acceptance/bundle/refschema/output/external_locations.txt @@ -60,6 +60,8 @@ resources.external_locations.*.file_event_queue.provided_sqs.queue_url string AL resources.external_locations.*.id string INPUT resources.external_locations.*.isolation_mode catalog.IsolationMode REMOTE resources.external_locations.*.lifecycle resources.Lifecycle INPUT +resources.external_locations.*.lifecycle.bind *resources.Bind INPUT +resources.external_locations.*.lifecycle.bind.id string INPUT resources.external_locations.*.lifecycle.depends_on []resources.DependsOn INPUT resources.external_locations.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.external_locations.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/features.txt b/acceptance/bundle/refschema/output/features.txt index c06246cee3d..907f26be1b2 100644 --- a/acceptance/bundle/refschema/output/features.txt +++ b/acceptance/bundle/refschema/output/features.txt @@ -89,6 +89,8 @@ resources.features.*.id string INPUT resources.features.*.inputs []string ALL resources.features.*.inputs[*] string ALL resources.features.*.lifecycle resources.Lifecycle INPUT +resources.features.*.lifecycle.bind *resources.Bind INPUT +resources.features.*.lifecycle.bind.id string INPUT resources.features.*.lifecycle.depends_on []resources.DependsOn INPUT resources.features.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.features.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/genie_spaces.txt b/acceptance/bundle/refschema/output/genie_spaces.txt index 40dbd2b70ba..2bad6604528 100644 --- a/acceptance/bundle/refschema/output/genie_spaces.txt +++ b/acceptance/bundle/refschema/output/genie_spaces.txt @@ -3,6 +3,8 @@ resources.genie_spaces.*.etag string ALL resources.genie_spaces.*.file_path string INPUT resources.genie_spaces.*.id string INPUT resources.genie_spaces.*.lifecycle resources.Lifecycle INPUT +resources.genie_spaces.*.lifecycle.bind *resources.Bind INPUT +resources.genie_spaces.*.lifecycle.bind.id string INPUT resources.genie_spaces.*.lifecycle.depends_on []resources.DependsOn INPUT resources.genie_spaces.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.genie_spaces.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/instance_pools.txt b/acceptance/bundle/refschema/output/instance_pools.txt index 71991cd0733..8c7263f8a3e 100644 --- a/acceptance/bundle/refschema/output/instance_pools.txt +++ b/acceptance/bundle/refschema/output/instance_pools.txt @@ -29,6 +29,8 @@ resources.instance_pools.*.idle_instance_autotermination_minutes int ALL resources.instance_pools.*.instance_pool_id string REMOTE resources.instance_pools.*.instance_pool_name string ALL resources.instance_pools.*.lifecycle resources.Lifecycle INPUT +resources.instance_pools.*.lifecycle.bind *resources.Bind INPUT +resources.instance_pools.*.lifecycle.bind.id string INPUT resources.instance_pools.*.lifecycle.depends_on []resources.DependsOn INPUT resources.instance_pools.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.instance_pools.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/job_runs.txt b/acceptance/bundle/refschema/output/job_runs.txt index c613f750f7b..406b7093897 100644 --- a/acceptance/bundle/refschema/output/job_runs.txt +++ b/acceptance/bundle/refschema/output/job_runs.txt @@ -11,6 +11,8 @@ resources.job_runs.*.job_parameters.* string ALL resources.job_runs.*.lifecycle *dresources.JobRunLifecycleState STATE resources.job_runs.*.lifecycle *resources.JobRunLifecycle INPUT resources.job_runs.*.lifecycle resources.Lifecycle INPUT +resources.job_runs.*.lifecycle.bind *resources.Bind INPUT +resources.job_runs.*.lifecycle.bind.id string INPUT resources.job_runs.*.lifecycle.depends_on []resources.DependsOn INPUT resources.job_runs.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.job_runs.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/jobs.txt b/acceptance/bundle/refschema/output/jobs.txt index 06a058987fd..099ceeb7df9 100644 --- a/acceptance/bundle/refschema/output/jobs.txt +++ b/acceptance/bundle/refschema/output/jobs.txt @@ -198,6 +198,8 @@ resources.jobs.*.job_clusters[*].serverless_compute.policy_id string ALL resources.jobs.*.job_clusters[*].serverless_compute_id string ALL resources.jobs.*.job_id int64 REMOTE resources.jobs.*.lifecycle resources.Lifecycle INPUT +resources.jobs.*.lifecycle.bind *resources.Bind INPUT +resources.jobs.*.lifecycle.bind.id string INPUT resources.jobs.*.lifecycle.depends_on []resources.DependsOn INPUT resources.jobs.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.jobs.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/materialization_sets.txt b/acceptance/bundle/refschema/output/materialization_sets.txt index 14b53dcb382..cec5d4731f8 100644 --- a/acceptance/bundle/refschema/output/materialization_sets.txt +++ b/acceptance/bundle/refschema/output/materialization_sets.txt @@ -1,5 +1,7 @@ resources.materialization_sets.*.id string INPUT resources.materialization_sets.*.lifecycle resources.Lifecycle INPUT +resources.materialization_sets.*.lifecycle.bind *resources.Bind INPUT +resources.materialization_sets.*.lifecycle.bind.id string INPUT resources.materialization_sets.*.lifecycle.depends_on []resources.DependsOn INPUT resources.materialization_sets.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.materialization_sets.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/mcp_services.txt b/acceptance/bundle/refschema/output/mcp_services.txt index 578b2636d79..403db38c3d9 100644 --- a/acceptance/bundle/refschema/output/mcp_services.txt +++ b/acceptance/bundle/refschema/output/mcp_services.txt @@ -16,6 +16,8 @@ resources.mcp_services.*.config.source_connection.options map[string]string ALL resources.mcp_services.*.config.source_connection.options.* string ALL resources.mcp_services.*.id string INPUT resources.mcp_services.*.lifecycle resources.Lifecycle INPUT +resources.mcp_services.*.lifecycle.bind *resources.Bind INPUT +resources.mcp_services.*.lifecycle.bind.id string INPUT resources.mcp_services.*.lifecycle.depends_on []resources.DependsOn INPUT resources.mcp_services.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.mcp_services.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/model_provider_services.txt b/acceptance/bundle/refschema/output/model_provider_services.txt index 0d5e76e1a7f..c228aa54904 100644 --- a/acceptance/bundle/refschema/output/model_provider_services.txt +++ b/acceptance/bundle/refschema/output/model_provider_services.txt @@ -119,6 +119,8 @@ resources.model_provider_services.*.config.targets[*].native_api_types []string resources.model_provider_services.*.config.targets[*].native_api_types[*] string ALL resources.model_provider_services.*.id string INPUT resources.model_provider_services.*.lifecycle resources.Lifecycle INPUT +resources.model_provider_services.*.lifecycle.bind *resources.Bind INPUT +resources.model_provider_services.*.lifecycle.bind.id string INPUT resources.model_provider_services.*.lifecycle.depends_on []resources.DependsOn INPUT resources.model_provider_services.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.model_provider_services.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/model_services.txt b/acceptance/bundle/refschema/output/model_services.txt index a13287e468b..8a43df8baa8 100644 --- a/acceptance/bundle/refschema/output/model_services.txt +++ b/acceptance/bundle/refschema/output/model_services.txt @@ -52,6 +52,8 @@ resources.model_services.*.config.routing.fallback.destinations[*].provisioned_t resources.model_services.*.config.routing.fallback.destinations[*].traffic_percentage int ALL resources.model_services.*.id string INPUT resources.model_services.*.lifecycle resources.Lifecycle INPUT +resources.model_services.*.lifecycle.bind *resources.Bind INPUT +resources.model_services.*.lifecycle.bind.id string INPUT resources.model_services.*.lifecycle.depends_on []resources.DependsOn INPUT resources.model_services.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.model_services.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/model_serving_endpoints.txt b/acceptance/bundle/refschema/output/model_serving_endpoints.txt index 95a3adcf440..796a84a3d7d 100644 --- a/acceptance/bundle/refschema/output/model_serving_endpoints.txt +++ b/acceptance/bundle/refschema/output/model_serving_endpoints.txt @@ -461,6 +461,8 @@ resources.model_serving_endpoints.*.endpoint_details.telemetry_config.telemetry_ resources.model_serving_endpoints.*.endpoint_id string REMOTE resources.model_serving_endpoints.*.id string INPUT resources.model_serving_endpoints.*.lifecycle resources.Lifecycle INPUT +resources.model_serving_endpoints.*.lifecycle.bind *resources.Bind INPUT +resources.model_serving_endpoints.*.lifecycle.bind.id string INPUT resources.model_serving_endpoints.*.lifecycle.depends_on []resources.DependsOn INPUT resources.model_serving_endpoints.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.model_serving_endpoints.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/models.txt b/acceptance/bundle/refschema/output/models.txt index 3570dd6fb58..7886abda59d 100644 --- a/acceptance/bundle/refschema/output/models.txt +++ b/acceptance/bundle/refschema/output/models.txt @@ -21,6 +21,8 @@ resources.models.*.latest_versions[*].tags[*].value string REMOTE resources.models.*.latest_versions[*].user_id string REMOTE resources.models.*.latest_versions[*].version string REMOTE resources.models.*.lifecycle resources.Lifecycle INPUT +resources.models.*.lifecycle.bind *resources.Bind INPUT +resources.models.*.lifecycle.bind.id string INPUT resources.models.*.lifecycle.depends_on []resources.DependsOn INPUT resources.models.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.models.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/pipelines.txt b/acceptance/bundle/refschema/output/pipelines.txt index c8bc57f512c..e15ec9f418a 100644 --- a/acceptance/bundle/refschema/output/pipelines.txt +++ b/acceptance/bundle/refschema/output/pipelines.txt @@ -831,6 +831,8 @@ resources.pipelines.*.libraries[*].notebook *pipelines.NotebookLibrary ALL resources.pipelines.*.libraries[*].notebook.path string ALL resources.pipelines.*.libraries[*].whl string ALL resources.pipelines.*.lifecycle resources.Lifecycle INPUT +resources.pipelines.*.lifecycle.bind *resources.Bind INPUT +resources.pipelines.*.lifecycle.bind.id string INPUT resources.pipelines.*.lifecycle.depends_on []resources.DependsOn INPUT resources.pipelines.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.pipelines.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/postgres_branches.txt b/acceptance/bundle/refschema/output/postgres_branches.txt index 85248e7a93a..e7db507827a 100644 --- a/acceptance/bundle/refschema/output/postgres_branches.txt +++ b/acceptance/bundle/refschema/output/postgres_branches.txt @@ -4,6 +4,8 @@ resources.postgres_branches.*.expire_time *time.Time ALL resources.postgres_branches.*.id string INPUT resources.postgres_branches.*.is_protected bool ALL resources.postgres_branches.*.lifecycle resources.Lifecycle INPUT +resources.postgres_branches.*.lifecycle.bind *resources.Bind INPUT +resources.postgres_branches.*.lifecycle.bind.id string INPUT resources.postgres_branches.*.lifecycle.depends_on []resources.DependsOn INPUT resources.postgres_branches.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.postgres_branches.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/postgres_catalogs.txt b/acceptance/bundle/refschema/output/postgres_catalogs.txt index 3cefc681b99..30f13150c6f 100644 --- a/acceptance/bundle/refschema/output/postgres_catalogs.txt +++ b/acceptance/bundle/refschema/output/postgres_catalogs.txt @@ -4,6 +4,8 @@ resources.postgres_catalogs.*.create_database_if_missing bool ALL resources.postgres_catalogs.*.create_time *time.Time REMOTE resources.postgres_catalogs.*.id string INPUT resources.postgres_catalogs.*.lifecycle resources.Lifecycle INPUT +resources.postgres_catalogs.*.lifecycle.bind *resources.Bind INPUT +resources.postgres_catalogs.*.lifecycle.bind.id string INPUT resources.postgres_catalogs.*.lifecycle.depends_on []resources.DependsOn INPUT resources.postgres_catalogs.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.postgres_catalogs.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/postgres_data_apis.txt b/acceptance/bundle/refschema/output/postgres_data_apis.txt index 59d28793b3b..b8b30f1e6f0 100644 --- a/acceptance/bundle/refschema/output/postgres_data_apis.txt +++ b/acceptance/bundle/refschema/output/postgres_data_apis.txt @@ -9,6 +9,8 @@ resources.postgres_data_apis.*.id string INPUT resources.postgres_data_apis.*.jwt_cache_max_lifetime *duration.Duration ALL resources.postgres_data_apis.*.jwt_role_claim_key string ALL resources.postgres_data_apis.*.lifecycle resources.Lifecycle INPUT +resources.postgres_data_apis.*.lifecycle.bind *resources.Bind INPUT +resources.postgres_data_apis.*.lifecycle.bind.id string INPUT resources.postgres_data_apis.*.lifecycle.depends_on []resources.DependsOn INPUT resources.postgres_data_apis.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.postgres_data_apis.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/postgres_databases.txt b/acceptance/bundle/refschema/output/postgres_databases.txt index 2ccb3ba2b68..505bc9cf551 100644 --- a/acceptance/bundle/refschema/output/postgres_databases.txt +++ b/acceptance/bundle/refschema/output/postgres_databases.txt @@ -2,6 +2,8 @@ resources.postgres_databases.*.create_time *time.Time REMOTE resources.postgres_databases.*.database_id string ALL resources.postgres_databases.*.id string INPUT resources.postgres_databases.*.lifecycle resources.Lifecycle INPUT +resources.postgres_databases.*.lifecycle.bind *resources.Bind INPUT +resources.postgres_databases.*.lifecycle.bind.id string INPUT resources.postgres_databases.*.lifecycle.depends_on []resources.DependsOn INPUT resources.postgres_databases.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.postgres_databases.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/postgres_endpoints.txt b/acceptance/bundle/refschema/output/postgres_endpoints.txt index 32f2395228b..ab1280898bf 100644 --- a/acceptance/bundle/refschema/output/postgres_endpoints.txt +++ b/acceptance/bundle/refschema/output/postgres_endpoints.txt @@ -10,6 +10,8 @@ resources.postgres_endpoints.*.group.max int ALL resources.postgres_endpoints.*.group.min int ALL resources.postgres_endpoints.*.id string INPUT resources.postgres_endpoints.*.lifecycle resources.Lifecycle INPUT +resources.postgres_endpoints.*.lifecycle.bind *resources.Bind INPUT +resources.postgres_endpoints.*.lifecycle.bind.id string INPUT resources.postgres_endpoints.*.lifecycle.depends_on []resources.DependsOn INPUT resources.postgres_endpoints.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.postgres_endpoints.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/postgres_projects.txt b/acceptance/bundle/refschema/output/postgres_projects.txt index 575b12db52d..f9727d43730 100644 --- a/acceptance/bundle/refschema/output/postgres_projects.txt +++ b/acceptance/bundle/refschema/output/postgres_projects.txt @@ -27,6 +27,8 @@ resources.postgres_projects.*.initial_endpoint_spec.group.min int REMOTE resources.postgres_projects.*.initial_endpoint_spec.no_suspension bool REMOTE resources.postgres_projects.*.initial_endpoint_spec.suspend_timeout_duration *duration.Duration REMOTE resources.postgres_projects.*.lifecycle resources.Lifecycle INPUT +resources.postgres_projects.*.lifecycle.bind *resources.Bind INPUT +resources.postgres_projects.*.lifecycle.bind.id string INPUT resources.postgres_projects.*.lifecycle.depends_on []resources.DependsOn INPUT resources.postgres_projects.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.postgres_projects.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/postgres_roles.txt b/acceptance/bundle/refschema/output/postgres_roles.txt index b77c3258488..cba2e3ae34d 100644 --- a/acceptance/bundle/refschema/output/postgres_roles.txt +++ b/acceptance/bundle/refschema/output/postgres_roles.txt @@ -7,6 +7,8 @@ resources.postgres_roles.*.create_time *time.Time REMOTE resources.postgres_roles.*.id string INPUT resources.postgres_roles.*.identity_type postgres.RoleIdentityType ALL resources.postgres_roles.*.lifecycle resources.Lifecycle INPUT +resources.postgres_roles.*.lifecycle.bind *resources.Bind INPUT +resources.postgres_roles.*.lifecycle.bind.id string INPUT resources.postgres_roles.*.lifecycle.depends_on []resources.DependsOn INPUT resources.postgres_roles.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.postgres_roles.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/postgres_snapshot_schedules.txt b/acceptance/bundle/refschema/output/postgres_snapshot_schedules.txt index ae44e6072ca..12046615c49 100644 --- a/acceptance/bundle/refschema/output/postgres_snapshot_schedules.txt +++ b/acceptance/bundle/refschema/output/postgres_snapshot_schedules.txt @@ -1,6 +1,8 @@ resources.postgres_snapshot_schedules.*.branch string ALL resources.postgres_snapshot_schedules.*.id string INPUT resources.postgres_snapshot_schedules.*.lifecycle resources.Lifecycle INPUT +resources.postgres_snapshot_schedules.*.lifecycle.bind *resources.Bind INPUT +resources.postgres_snapshot_schedules.*.lifecycle.bind.id string INPUT resources.postgres_snapshot_schedules.*.lifecycle.depends_on []resources.DependsOn INPUT resources.postgres_snapshot_schedules.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.postgres_snapshot_schedules.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/postgres_synced_tables.txt b/acceptance/bundle/refschema/output/postgres_synced_tables.txt index 78be11c8847..0da83f70495 100644 --- a/acceptance/bundle/refschema/output/postgres_synced_tables.txt +++ b/acceptance/bundle/refschema/output/postgres_synced_tables.txt @@ -11,6 +11,8 @@ resources.postgres_synced_tables.*.extra_columns[*].compute string ALL resources.postgres_synced_tables.*.extra_columns[*].maintenance postgres.SyncedTableSyncedTableSpecExtraColumnMaintenance ALL resources.postgres_synced_tables.*.id string INPUT resources.postgres_synced_tables.*.lifecycle resources.Lifecycle INPUT +resources.postgres_synced_tables.*.lifecycle.bind *resources.Bind INPUT +resources.postgres_synced_tables.*.lifecycle.bind.id string INPUT resources.postgres_synced_tables.*.lifecycle.depends_on []resources.DependsOn INPUT resources.postgres_synced_tables.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.postgres_synced_tables.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/quality_monitors.txt b/acceptance/bundle/refschema/output/quality_monitors.txt index 5bd3f952fbd..e51d38e3eaa 100644 --- a/acceptance/bundle/refschema/output/quality_monitors.txt +++ b/acceptance/bundle/refschema/output/quality_monitors.txt @@ -24,6 +24,8 @@ resources.quality_monitors.*.inference_log.problem_type catalog.MonitorInference resources.quality_monitors.*.inference_log.timestamp_col string ALL resources.quality_monitors.*.latest_monitor_failure_msg string ALL resources.quality_monitors.*.lifecycle resources.Lifecycle INPUT +resources.quality_monitors.*.lifecycle.bind *resources.Bind INPUT +resources.quality_monitors.*.lifecycle.bind.id string INPUT resources.quality_monitors.*.lifecycle.depends_on []resources.DependsOn INPUT resources.quality_monitors.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.quality_monitors.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/registered_models.txt b/acceptance/bundle/refschema/output/registered_models.txt index 7c3d1db0860..efc40ad54bb 100644 --- a/acceptance/bundle/refschema/output/registered_models.txt +++ b/acceptance/bundle/refschema/output/registered_models.txt @@ -14,6 +14,8 @@ resources.registered_models.*.created_by string ALL resources.registered_models.*.full_name string ALL resources.registered_models.*.id string INPUT resources.registered_models.*.lifecycle resources.Lifecycle INPUT +resources.registered_models.*.lifecycle.bind *resources.Bind INPUT +resources.registered_models.*.lifecycle.bind.id string INPUT resources.registered_models.*.lifecycle.depends_on []resources.DependsOn INPUT resources.registered_models.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.registered_models.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/schemas.txt b/acceptance/bundle/refschema/output/schemas.txt index 694d59c6211..70983f86e00 100644 --- a/acceptance/bundle/refschema/output/schemas.txt +++ b/acceptance/bundle/refschema/output/schemas.txt @@ -13,6 +13,8 @@ resources.schemas.*.enable_predictive_optimization catalog.EnablePredictiveOptim resources.schemas.*.full_name string REMOTE resources.schemas.*.id string INPUT resources.schemas.*.lifecycle resources.Lifecycle INPUT +resources.schemas.*.lifecycle.bind *resources.Bind INPUT +resources.schemas.*.lifecycle.bind.id string INPUT resources.schemas.*.lifecycle.depends_on []resources.DependsOn INPUT resources.schemas.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.schemas.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/secret_scopes.txt b/acceptance/bundle/refschema/output/secret_scopes.txt index 832a476d47a..793e8591de9 100644 --- a/acceptance/bundle/refschema/output/secret_scopes.txt +++ b/acceptance/bundle/refschema/output/secret_scopes.txt @@ -8,6 +8,8 @@ resources.secret_scopes.*.keyvault_metadata *workspace.AzureKeyVaultSecretScopeM resources.secret_scopes.*.keyvault_metadata.dns_name string INPUT resources.secret_scopes.*.keyvault_metadata.resource_id string INPUT resources.secret_scopes.*.lifecycle resources.Lifecycle INPUT +resources.secret_scopes.*.lifecycle.bind *resources.Bind INPUT +resources.secret_scopes.*.lifecycle.bind.id string INPUT resources.secret_scopes.*.lifecycle.depends_on []resources.DependsOn INPUT resources.secret_scopes.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.secret_scopes.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/secrets.txt b/acceptance/bundle/refschema/output/secrets.txt index f0c4b2b213a..29f5e5f454f 100644 --- a/acceptance/bundle/refschema/output/secrets.txt +++ b/acceptance/bundle/refschema/output/secrets.txt @@ -8,6 +8,8 @@ resources.secrets.*.expire_time *time.Time ALL resources.secrets.*.full_name string ALL resources.secrets.*.id string INPUT resources.secrets.*.lifecycle resources.Lifecycle INPUT +resources.secrets.*.lifecycle.bind *resources.Bind INPUT +resources.secrets.*.lifecycle.bind.id string INPUT resources.secrets.*.lifecycle.depends_on []resources.DependsOn INPUT resources.secrets.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.secrets.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/sql_warehouses.txt b/acceptance/bundle/refschema/output/sql_warehouses.txt index a642c226ac9..f7c11cb8caf 100644 --- a/acceptance/bundle/refschema/output/sql_warehouses.txt +++ b/acceptance/bundle/refschema/output/sql_warehouses.txt @@ -23,6 +23,8 @@ resources.sql_warehouses.*.jdbc_url string REMOTE resources.sql_warehouses.*.lifecycle *dresources.StateLifecycle REMOTE STATE resources.sql_warehouses.*.lifecycle *resources.LifecycleWithStarted INPUT resources.sql_warehouses.*.lifecycle resources.Lifecycle INPUT +resources.sql_warehouses.*.lifecycle.bind *resources.Bind INPUT +resources.sql_warehouses.*.lifecycle.bind.id string INPUT resources.sql_warehouses.*.lifecycle.depends_on []resources.DependsOn INPUT resources.sql_warehouses.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.sql_warehouses.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/synced_database_tables.txt b/acceptance/bundle/refschema/output/synced_database_tables.txt index 7836f00bb8b..7cc0ca860a4 100644 --- a/acceptance/bundle/refschema/output/synced_database_tables.txt +++ b/acceptance/bundle/refschema/output/synced_database_tables.txt @@ -44,6 +44,8 @@ resources.synced_database_tables.*.effective_database_instance_name string ALL resources.synced_database_tables.*.effective_logical_database_name string ALL resources.synced_database_tables.*.id string INPUT resources.synced_database_tables.*.lifecycle resources.Lifecycle INPUT +resources.synced_database_tables.*.lifecycle.bind *resources.Bind INPUT +resources.synced_database_tables.*.lifecycle.bind.id string INPUT resources.synced_database_tables.*.lifecycle.depends_on []resources.DependsOn INPUT resources.synced_database_tables.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.synced_database_tables.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/vector_search_endpoints.txt b/acceptance/bundle/refschema/output/vector_search_endpoints.txt index 7fddb5b8e05..48947c4302f 100644 --- a/acceptance/bundle/refschema/output/vector_search_endpoints.txt +++ b/acceptance/bundle/refschema/output/vector_search_endpoints.txt @@ -15,6 +15,8 @@ resources.vector_search_endpoints.*.id string INPUT REMOTE resources.vector_search_endpoints.*.last_updated_timestamp int64 REMOTE resources.vector_search_endpoints.*.last_updated_user string REMOTE resources.vector_search_endpoints.*.lifecycle resources.Lifecycle INPUT +resources.vector_search_endpoints.*.lifecycle.bind *resources.Bind INPUT +resources.vector_search_endpoints.*.lifecycle.bind.id string INPUT resources.vector_search_endpoints.*.lifecycle.depends_on []resources.DependsOn INPUT resources.vector_search_endpoints.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.vector_search_endpoints.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/vector_search_indexes.txt b/acceptance/bundle/refschema/output/vector_search_indexes.txt index 6246bf1fdc7..24a5807f541 100644 --- a/acceptance/bundle/refschema/output/vector_search_indexes.txt +++ b/acceptance/bundle/refschema/output/vector_search_indexes.txt @@ -36,6 +36,8 @@ resources.vector_search_indexes.*.id string INPUT resources.vector_search_indexes.*.index_subtype vectorsearch.IndexSubtype ALL resources.vector_search_indexes.*.index_type vectorsearch.VectorIndexType ALL resources.vector_search_indexes.*.lifecycle resources.Lifecycle INPUT +resources.vector_search_indexes.*.lifecycle.bind *resources.Bind INPUT +resources.vector_search_indexes.*.lifecycle.bind.id string INPUT resources.vector_search_indexes.*.lifecycle.depends_on []resources.DependsOn INPUT resources.vector_search_indexes.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.vector_search_indexes.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/bundle/refschema/output/volumes.txt b/acceptance/bundle/refschema/output/volumes.txt index 1c573ed1931..c34d267fbd7 100644 --- a/acceptance/bundle/refschema/output/volumes.txt +++ b/acceptance/bundle/refschema/output/volumes.txt @@ -11,6 +11,8 @@ resources.volumes.*.encryption_details.sse_encryption_details.aws_kms_key_arn st resources.volumes.*.full_name string REMOTE resources.volumes.*.id string INPUT resources.volumes.*.lifecycle resources.Lifecycle INPUT +resources.volumes.*.lifecycle.bind *resources.Bind INPUT +resources.volumes.*.lifecycle.bind.id string INPUT resources.volumes.*.lifecycle.depends_on []resources.DependsOn INPUT resources.volumes.*.lifecycle.depends_on[*] resources.DependsOn INPUT resources.volumes.*.lifecycle.depends_on[*].resource string INPUT diff --git a/acceptance/test.toml b/acceptance/test.toml index 6e18a40e78a..0e9599e8347 100644 --- a/acceptance/test.toml +++ b/acceptance/test.toml @@ -23,7 +23,7 @@ Env.PYTHONUTF8 = "1" # applied in parallel, so the order of these lines is not deterministic. Sort each run of # them before comparing, so tests pin which resources were applied, not the order. # A test that needs the raw order sets SortLinesOn.deploy_actions = false. -SortLines.deploy_actions = '^(Created|Updated|Deleted|Recreated|Resized) \S+$' +SortLines.deploy_actions = '^(Created|Updated|Deleted|Recreated|Resized|Bound|Bound and updated) \S+$' # Retries are timing dependent, so their warnings would make output flaky. They go to the # test log instead. A test that injects errors deterministically and asserts the warning @@ -77,11 +77,11 @@ New = "[METASTORE_NAME]" # Normalize plan_version so bumping it doesn't churn every recorded plan. [[Repls]] -Old = '"plan_version": 2,' +Old = '"plan_version": 3,' New = '"plan_version": [PLAN_VERSION],' [[Repls]] -Old = 'json\.plan_version = 2;' +Old = 'json\.plan_version = 3;' New = 'json.plan_version = [PLAN_VERSION];' # The lineage hash (libs/shortid) is not masked: fixture lineages hash to stable values, and diff --git a/bundle/config/resources/lifecycle.go b/bundle/config/resources/lifecycle.go index ec93ba5af06..90e849ba8cd 100644 --- a/bundle/config/resources/lifecycle.go +++ b/bundle/config/resources/lifecycle.go @@ -4,6 +4,7 @@ package resources type LifecycleConfig interface { HasPreventDestroy() bool GetDependsOn() []DependsOn + GetBind() *Bind } // Lifecycle contains base lifecycle settings supported by all resources. @@ -19,6 +20,9 @@ type Lifecycle struct { // Resources that must be deployed before this resource and deleted after it. DependsOn []DependsOn `json:"depends_on,omitempty"` + + // Existing workspace resource to bring under bundle management instead of creating one. + Bind *Bind `json:"bind,omitempty"` } // DependsOn is a single lifecycle.depends_on entry. @@ -27,6 +31,15 @@ type DependsOn struct { Resource string `json:"resource"` } +// Bind is the lifecycle.bind block: the deploy adopts the workspace resource with this +// ID into the deployment state instead of creating a new one. Nothing is recorded in +// state about the adoption; the block guards the resource against being recreated for +// as long as it is present in the configuration. +type Bind struct { + // ID of the existing workspace resource, as used by its API. + ID string `json:"id"` +} + // HasPreventDestroy returns true if prevent_destroy is set. func (l Lifecycle) HasPreventDestroy() bool { return l.PreventDestroy @@ -37,6 +50,11 @@ func (l Lifecycle) GetDependsOn() []DependsOn { return l.DependsOn } +// GetBind returns the lifecycle.bind block, or nil when the resource is not bound. +func (l Lifecycle) GetBind() *Bind { + return l.Bind +} + // LifecycleWithStarted contains lifecycle settings for resources that support lifecycle.started. // It is used by apps, clusters, and sql_warehouses. type LifecycleWithStarted struct { diff --git a/bundle/deployplan/action.go b/bundle/deployplan/action.go index e53ef0d84bd..8e74d4f917e 100644 --- a/bundle/deployplan/action.go +++ b/bundle/deployplan/action.go @@ -40,7 +40,7 @@ func (a Action) IsChildResource() bool { type ActionType string // Actions are ordered in increasing severity. -// If case of several options, action with highest severity wins. +// In case of several options, action with highest severity wins. // Note, Create/Delete are handled explicitly and never compared. const ( Undefined ActionType = "" @@ -64,6 +64,7 @@ var actionOrder = map[ActionType]int{ Delete: 7, } +// KeepsID reports whether the action preserves an ID already recorded in deployment state. func (a ActionType) KeepsID() bool { switch a { case Create, UpdateWithID, Recreate, Delete: @@ -79,14 +80,15 @@ func (a ActionType) StringShort() string { return items[0] } -// AppliedLine renders the user-facing line reporting that action has been applied +// AppliedLine renders the user-facing line reporting that a non-Skip action has been applied // to resourceKey, e.g. "Created jobs.foo". The past-tense verb is the short action // name plus "d" (create->Created, delete->Deleted, ...), capitalized to match the // sentence case of other output. "bundle plan" keeps the lower-case present tense, // so the two are still distinguishable at a glance. func AppliedLine(resourceKey string, action ActionType) string { + key := strings.TrimPrefix(resourceKey, "resources.") verb := action.StringShort() + "d" - return strings.ToUpper(verb[:1]) + verb[1:] + " " + strings.TrimPrefix(resourceKey, "resources.") + return strings.ToUpper(verb[:1]) + verb[1:] + " " + key } // GetHigherAction returns the action with higher severity between a and b. diff --git a/bundle/deployplan/action_test.go b/bundle/deployplan/action_test.go new file mode 100644 index 00000000000..a81d6f34a74 --- /dev/null +++ b/bundle/deployplan/action_test.go @@ -0,0 +1,23 @@ +package deployplan_test + +import ( + "testing" + + "github.com/databricks/cli/bundle/deployplan" + "github.com/stretchr/testify/assert" +) + +func TestAppliedLine(t *testing.T) { + for action, expected := range map[deployplan.ActionType]string{ + deployplan.Create: "Created jobs.foo", + deployplan.Update: "Updated jobs.foo", + deployplan.UpdateWithID: "Updated jobs.foo", + deployplan.Resize: "Resized jobs.foo", + deployplan.Recreate: "Recreated jobs.foo", + deployplan.Delete: "Deleted jobs.foo", + } { + t.Run(string(action), func(t *testing.T) { + assert.Equal(t, expected, deployplan.AppliedLine("resources.jobs.foo", action)) + }) + } +} diff --git a/bundle/deployplan/plan.go b/bundle/deployplan/plan.go index 8446b200b12..2f2b7ebf054 100644 --- a/bundle/deployplan/plan.go +++ b/bundle/deployplan/plan.go @@ -18,7 +18,7 @@ import ( "github.com/databricks/cli/libs/structs/structvar" ) -const currentPlanVersion = 2 +const currentPlanVersion = 3 type Plan struct { PlanVersion int `json:"plan_version,omitempty"` @@ -31,6 +31,8 @@ type Plan struct { // deploy --plan rejects a plan whose features differ from the target's (see process.go). Features map[string]struct{} `json:"features,omitempty"` + // Bind commits existing resources to state before the resource actions in Plan. + Bind map[string]*BindEntry `json:"bind,omitempty"` Plan map[string]*PlanEntry `json:"plan,omitzero"` // NotSelected is the number of resources removed by FilterToSelected via the @@ -49,12 +51,13 @@ type ActionCounts struct { Change int Delete int Unchanged int + Bind int } // CountActions tallies the plan's actions by category. Order is irrelevant to a // tally, so it iterates the plan map directly rather than the sorted GetActions. func (p *Plan) CountActions() ActionCounts { - var c ActionCounts + c := ActionCounts{Bind: len(p.Bind)} for _, entry := range p.Plan { switch entry.Action { case Create: @@ -118,10 +121,17 @@ func LoadPlanFromFile(path string) (*Plan, error) { return &plan, nil } -type PlanEntry struct { - ID string `json:"id,omitempty"` +type BindEntry struct { + ID string `json:"id"` + State json.RawMessage `json:"state"` DependsOn []DependsOnEntry `json:"depends_on,omitempty"` - Action ActionType `json:"action,omitempty"` +} + +type PlanEntry struct { + ID string `json:"id,omitempty"` + DependsOn []DependsOnEntry `json:"depends_on,omitempty"` + Action ActionType `json:"action,omitempty"` + PreventIDChange bool `json:"prevent_id_change,omitempty"` // Gone is set on Delete entries when planning confirmed the resource no longer // exists remotely. Applying such an entry only removes it from the state, without // calling the delete API, and approval prompts do not list it as a deletion. @@ -418,6 +428,11 @@ func (p *Plan) FilterToSelected(_ context.Context, selected []string) error { delete(p.Plan, key) } } + for key := range p.Bind { + if _, ok := reachable[key]; !ok { + delete(p.Bind, key) + } + } p.NotSelected = before - len(p.Plan) return nil diff --git a/bundle/deployplan/plan_test.go b/bundle/deployplan/plan_test.go index cee00f42c44..b4b10011f42 100644 --- a/bundle/deployplan/plan_test.go +++ b/bundle/deployplan/plan_test.go @@ -1,6 +1,9 @@ package deployplan_test import ( + "encoding/json" + "os" + "path/filepath" "testing" "github.com/databricks/cli/bundle/deployplan" @@ -9,6 +12,31 @@ import ( "github.com/stretchr/testify/require" ) +func TestSavedPlanPreservesBindPhase(t *testing.T) { + plan := deployplan.NewPlanDirect() + plan.Bind = map[string]*deployplan.BindEntry{ + "resources.jobs.job": {ID: "123", State: json.RawMessage(`{"name":"existing"}`)}, + "resources.experiments.experiment": {ID: "456", State: json.RawMessage(`{"name":"unchanged"}`)}, + } + plan.Plan = map[string]*deployplan.PlanEntry{ + "resources.jobs.job": {ID: "123", Action: deployplan.Update, PreventIDChange: true}, + "resources.experiments.experiment": {ID: "456", Action: deployplan.Skip, PreventIDChange: true}, + } + encoded, err := json.Marshal(plan) + require.NoError(t, err) + path := filepath.Join(t.TempDir(), "plan.json") + require.NoError(t, os.WriteFile(path, encoded, 0o600)) + loaded, err := deployplan.LoadPlanFromFile(path) + require.NoError(t, err) + assert.Equal(t, plan.Bind, loaded.Bind) + assert.Equal(t, plan.Plan, loaded.Plan) + assert.Equal(t, deployplan.ActionCounts{Bind: 2, Change: 1, Unchanged: 1}, loaded.CountActions()) + require.NoError(t, loaded.FilterToSelected(t.Context(), []string{"jobs.job"})) + assert.Len(t, loaded.Bind, 1) + assert.Contains(t, loaded.Bind, "resources.jobs.job") + assert.Equal(t, deployplan.ActionCounts{Bind: 1, Change: 1}, loaded.CountActions()) +} + func TestHasChange(t *testing.T) { tests := []struct { name string diff --git a/bundle/direct/apply.go b/bundle/direct/apply.go index 9cdd5a4f505..b4ef97dd5ed 100644 --- a/bundle/direct/apply.go +++ b/bundle/direct/apply.go @@ -334,16 +334,24 @@ func (d *DeploymentUnit) Resize(ctx context.Context, db *dstate.DeploymentState, // smaller than a hash placeholder are persisted as is. Sensitive fields are replaced by a // placeholder value so secrets are never written to disk in plaintext. func (d *DeploymentUnit) saveState(ctx context.Context, db *dstate.DeploymentState, newID string, state any, dependsOn []deployplan.DependsOnEntry) error { - compacted, err := dresources.CompactState(d.Adapter.ResourceConfig(), state) + compacted, err := compactStateForSave(d.Adapter, state) if err != nil { - return fmt.Errorf("compacting state: %w", err) - } - if err := zeroSensitiveFields(d.Adapter, compacted); err != nil { - return fmt.Errorf("redacting state: %w", err) + return err } return db.SaveState(ctx, d.ResourceKey, newID, compacted, dependsOn) } +func compactStateForSave(adapter *dresources.Adapter, state any) (any, error) { + compacted, err := dresources.CompactState(adapter.ResourceConfig(), state) + if err != nil { + return nil, fmt.Errorf("compacting state: %w", err) + } + if err := zeroSensitiveFields(adapter, compacted); err != nil { + return nil, fmt.Errorf("redacting state: %w", err) + } + return compacted, nil +} + func parseState(destType reflect.Type, raw json.RawMessage) (any, error) { destPtr := reflect.New(destType).Interface() err := json.Unmarshal(raw, destPtr) diff --git a/bundle/direct/bind_test.go b/bundle/direct/bind_test.go new file mode 100644 index 00000000000..07f6d6a0e3d --- /dev/null +++ b/bundle/direct/bind_test.go @@ -0,0 +1,126 @@ +package direct_test + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/direct" + "github.com/databricks/cli/bundle/direct/dresources" + "github.com/databricks/cli/bundle/direct/dstate" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/testserver" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/bundledeployments" + "github.com/databricks/databricks-sdk-go/service/catalog" + "github.com/databricks/databricks-sdk-go/service/dashboards" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func bindTestClient(t *testing.T) (*testserver.Server, *databricks.WorkspaceClient) { + t.Helper() + server := testserver.New(t) + testserver.AddDefaultHandlers(server) + client, err := databricks.NewWorkspaceClient(&databricks.Config{Host: server.URL, Token: "testtoken"}) + require.NoError(t, err) + return server, client +} + +func TestPureBindSanitizesPersistedState(t *testing.T) { + const secret = "synthetic-sensitive-value" + content := strings.Repeat("dashboard content ", 20) + for _, mode := range []dstate.Mode{ + dstate.WritingFile, + dstate.WritingDeploymentHistoryShadow, + dstate.WritingDeploymentHistoryOnly, + } { + for _, resourceType := range []string{"secrets", "dashboards"} { + t.Run(fmt.Sprintf("%d/%s", mode, resourceType), func(t *testing.T) { + server, client := bindTestClient(t) + recorded := make(chan string, 1) + server.Handle("PATCH", "/api/2.0/bundle/deployments/123/versions/1/operations/{key}", func(req testserver.Request) any { + var operation bundledeployments.Operation + assert.NoError(t, json.Unmarshal(req.Body, &operation)) + recorded <- operation.State + return bundledeployments.Operation{SequenceId: 1} + }) + ctx := cmdctx.SetWorkspaceClient(t.Context(), client) + ctx = logdiag.InitContext(ctx) + statePath := filepath.Join(t.TempDir(), "resources.json") + var stateDB dstate.DeploymentState + require.NoError(t, stateDB.Open(ctx, statePath, mode, dstate.WithRecovery(true), dstate.OpenDmsArgs{})) + t.Cleanup(func() { + _, err := stateDB.Finalize(ctx) + assert.NoError(t, err) + }) + if mode != dstate.WritingFile { + stateDB.InitializeOperationBuffer(ctx, "123", 1, 1) + } + resourceKey := "resources." + resourceType + ".test" + var newState any = &catalog.Secret{Name: "test", Value: secret, EffectiveValue: secret} + server.Handle("GET", "/api/2.1/unity-catalog/secrets/remote-id", func(req testserver.Request) any { + return &catalog.Secret{Name: "test", EffectiveValue: secret} + }) + server.Handle("GET", "/api/2.0/lakeview/dashboards/remote-id", func(req testserver.Request) any { + return &dashboards.Dashboard{DashboardId: "remote-id", SerializedDashboard: content} + }) + server.Handle("GET", "/api/2.0/lakeview/dashboards/remote-id/published", func(req testserver.Request) any { + return &dashboards.PublishedDashboard{} + }) + rawContent := secret + if resourceType == "dashboards" { + newState = &dresources.DashboardState{ + SerializedDashboard: content, + } + rawContent = content + } + definitionJSON, err := json.Marshal(newState) + require.NoError(t, err) + var definition map[string]any + require.NoError(t, json.Unmarshal(definitionJSON, &definition)) + delete(definition, "effective_value") + delete(definition, "published") + definition["lifecycle"] = map[string]any{"bind": map[string]string{"id": "remote-id"}} + configJSON, err := json.Marshal(map[string]any{"resources": map[string]any{resourceType: map[string]any{"test": definition}}}) + require.NoError(t, err) + root, diagnostics := config.LoadFromBytes("databricks.yml", configJSON) + require.Empty(t, diagnostics) + var deployment direct.DeploymentBundle + deployment.StateDB.OpenWithData(statePath, dstate.NewDatabase("", 0)) + bindings, err := deployment.PrepareBindings(ctx, client, root, nil) + require.NoError(t, err) + require.NoError(t, stateDB.Bind(ctx, bindings)) + assert.Equal(t, "remote-id", stateDB.GetResourceID(resourceKey)) + if mode != dstate.WritingDeploymentHistoryOnly { + wal, err := os.ReadFile(statePath + ".wal") + require.NoError(t, err) + assert.NotContains(t, string(wal), rawContent) + if resourceType == "dashboards" { + assert.Contains(t, string(wal), fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(content)))) + } + } + _, err = stateDB.Finalize(ctx) + require.NoError(t, err) + persisted, err := os.ReadFile(statePath) + require.NoError(t, err) + assert.NotContains(t, string(persisted), rawContent) + if mode != dstate.WritingFile { + require.Len(t, recorded, 1) + history := <-recorded + assert.NotContains(t, history, rawContent) + assert.Contains(t, history, `"state":`) + if resourceType == "dashboards" { + assert.Contains(t, history, fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(content)))) + } + } + }) + } + } +} diff --git a/bundle/direct/bundle_apply.go b/bundle/direct/bundle_apply.go index add38bf99f6..34608d4e948 100644 --- a/bundle/direct/bundle_apply.go +++ b/bundle/direct/bundle_apply.go @@ -136,12 +136,21 @@ func (b *DeploymentBundle) Apply(ctx context.Context, client *databricks.Workspa return true } - // We don't keep NewState around for 'skip' nodes - - if action != deployplan.Skip { + // Guarded skips retain NewState so saved plans can recheck the bound resource. + if action != deployplan.Skip || entry.PreventIDChange { if !b.resolveReferences(ctx, resourceKey, entry, errorPrefix, false) { return false } + } + if entry.PreventIDChange { + if err := b.checkBoundResource(ctx, resourceKey, entry, adapter); err != nil { + b.StateDB.RecordFailure(resourceKey, entry.ID, err) + logdiag.LogError(ctx, err) + return false + } + } + + if action != deployplan.Skip { // Get the cached StructVar to check for unresolved refs and get value sv, ok := b.StateCache.Load(resourceKey) diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index 8f8336beaa5..76fe66c5dd2 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -161,7 +161,7 @@ func (b *DeploymentBundle) StampDeploymentIdForFirstVersion(deploymentID string) // CalculatePlan computes the deployment plan by comparing local config against remote state. // StateDB must already be open for read before calling this function. -func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks.WorkspaceClient, configRoot *config.Root) (*deployplan.Plan, error) { +func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks.WorkspaceClient, configRoot *config.Root, selected ...string) (*deployplan.Plan, error) { b.StateDB.AssertOpenedForRead() err := b.init(client) @@ -173,6 +173,11 @@ func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks if err != nil { return nil, fmt.Errorf("reading config: %w", err) } + if len(selected) > 0 { + if err := plan.FilterToSelected(ctx, selected); err != nil { + return nil, err + } + } // The plan records the state it was built against so deploy --plan can reject a plan built for a // target of a different shape or a state that has moved on since. @@ -181,6 +186,11 @@ func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks b.Plan = plan + bindIDByKey := lifecycleBindIDs(configRoot) + if len(bindIDByKey) > 0 && !b.validateBindConfig(ctx, configRoot, bindIDByKey) { + return nil, errors.New("bind validation failed") + } + g, err := makeGraph(plan) if err != nil { return nil, err @@ -208,6 +218,10 @@ func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks } defer plan.WriteUnlockEntry(resourceKey) + _, entry.PreventIDChange = bindIDByKey[resourceKey] + if entry.PreventIDChange { + entry.ID = bindIDByKey[resourceKey] + } if failedDependency != nil { // TODO: this should be a warning @@ -275,6 +289,10 @@ func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks // state files may still carry a malformed entry). Treat as missing // and let the resource be re-created on this plan. if !hasEntry || dbentry.ID == "" { + if entry.PreventIDChange { + logdiag.LogError(ctx, fmt.Errorf("%s: resource must be bound before deployment planning", errorPrefix)) + return false + } entry.Action = deployplan.Create return true } @@ -371,6 +389,13 @@ func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks } if remoteState == nil { + // A bind block pins the resource to one ID. Creating a replacement would + // re-point the state at a new ID behind the user's back and leave the + // block mismatched on every later plan, so report the deletion instead. + if entry.PreventIDChange { + logdiag.LogError(ctx, fmt.Errorf("%s: bound resource with ID %q no longer exists in the workspace; remove lifecycle.bind to create a new resource", errorPrefix, dbentry.ID)) + return false + } // Even if local action is "recreate" which is higher than "create", we should still pick "create" here // because we know remote does not exist. action = deployplan.Create @@ -382,6 +407,11 @@ func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks // it can be used for variable resolution depends on several factors, see canReadRemoteCache in LookupReferencePreDeploy b.RemoteStateCache.Store(resourceKey, remoteState) + if entry.PreventIDChange && (action == deployplan.Recreate || action == deployplan.UpdateWithID) { + logdiag.LogError(ctx, fmt.Errorf("%s: %w", errorPrefix, buildBindConflictError(action, entry.Changes))) + return false + } + // Validate that resources without DoUpdate don't have update actions if action == deployplan.Update && !adapter.HasDoUpdate() { logdiag.LogError(ctx, fmt.Errorf("%s: resource does not support update action but plan produced update", errorPrefix)) @@ -397,7 +427,7 @@ func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks } for _, entry := range plan.Plan { - if entry.Action == deployplan.Skip { + if entry.Action == deployplan.Skip && !entry.PreventIDChange { entry.NewState = nil } } @@ -1036,13 +1066,15 @@ func (b *DeploymentBundle) LookupReferencePreDeploy(ctx context.Context, path *s } // A field is safe to read from the remote cache when the target either has no - // changes (Skip) or keeps its ID and the field cannot have changed: an + // changes (Skip) or retains its remote identity + // and the field cannot have changed: an // immutable field (FieldTriggersRecreate) or a backend-assigned stable output // like an AIP name derived from the ID (FieldIsStableOutput). The latter lets // ${resources.X.name} resolve during an in-place update instead of delaying, // which would otherwise recreate dependents that reference it as their parent. canReadRemoteCache := targetAction == deployplan.Skip || - (targetAction.KeepsID() && (adapter.FieldTriggersRecreate(fieldPath) || adapter.FieldIsStableOutput(fieldPath))) + (targetAction.KeepsID() && + (adapter.FieldTriggersRecreate(fieldPath) || adapter.FieldIsStableOutput(fieldPath))) if configValidErr != nil && remoteValidErr == nil { // The field is only present in remote state schema. diff --git a/bundle/direct/bundle_plan_bind.go b/bundle/direct/bundle_plan_bind.go new file mode 100644 index 00000000000..b2066bee2bf --- /dev/null +++ b/bundle/direct/bundle_plan_bind.go @@ -0,0 +1,416 @@ +package direct + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "maps" + "reflect" + "slices" + "strings" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/deployplan" + "github.com/databricks/cli/bundle/direct/dresources" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/retry" + "github.com/databricks/cli/libs/structs/structaccess" + "github.com/databricks/cli/libs/structs/structdiff" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/apierr" +) + +// lifecycleBindIDs collects lifecycle.bind IDs by resource key. A nil configRoot +// (destroy) has no bind blocks. +func lifecycleBindIDs(configRoot *config.Root) map[string]string { + result := map[string]string{} + if configRoot == nil { + return result + } + for _, group := range configRoot.Resources.AllResources() { + for name, resource := range group.Resources { + bind := resource.GetLifecycle().GetBind() + if bind == nil { + continue + } + result["resources."+group.Description.PluralName+"."+name] = bind.ID + } + } + return result +} + +// bindIDPath returns the config path of a resource's lifecycle.bind.id, used to point +// diagnostics at the offending entry. +func bindIDPath(resourceKey string) *structpath.PathNode { + resourceType, resourceName := splitResourceKey(resourceKey) + return structpath.NewPath(nil, "resources", resourceType, resourceName, "lifecycle", "bind", "id") +} + +func (b *DeploymentBundle) logBindError(ctx context.Context, configRoot *config.Root, resourceKey, summary string) { + path := bindIDPath(resourceKey) + logdiag.LogDiag(ctx, diag.Diagnostic{ + Severity: diag.Error, + Summary: summary, + Locations: configRoot.GetLocationsOf(path), + Paths: []*structpath.PathNode{path}, + }) +} + +// validateBindConfig logs diagnostics for lifecycle.bind entries that conflict with the +// state. Returns false if any errors were logged. +func (b *DeploymentBundle) validateBindConfig(ctx context.Context, configRoot *config.Root, bindIDByKey map[string]string) bool { + hasError := false + keys := slices.Sorted(maps.Keys(bindIDByKey)) + + // A resource that is already in state cannot be re-bound to a different ID: + // lifecycle.bind adopts a resource for the first time. When the IDs match the bind + // is a no-op and the resource just deploys normally. + for _, key := range keys { + bindID := bindIDByKey[key] + dbentry, hasEntry := b.StateDB.GetResourceEntry(key) + if !hasEntry || dbentry.ID == "" || dbentry.ID == bindID { + continue + } + b.logBindError(ctx, configRoot, key, fmt.Sprintf("%s: resource already bound to ID %q, cannot bind as %q; remove lifecycle.bind or unbind the existing resource", key, dbentry.ID, bindID)) + hasError = true + } + + // Two resources must not bind the same workspace ID. Otherwise both would be + // adopted and two state entries would manage one remote resource. IDs are only + // unique within a resource type (a job and an experiment can share a numeric ID), + // so only resources of the same type can collide. + keysByTypeAndID := map[string][]string{} + for _, key := range keys { + keysByTypeAndID[typeAndID(key, bindIDByKey[key])] = append(keysByTypeAndID[typeAndID(key, bindIDByKey[key])], key) + } + for _, key := range keys { + bindID := bindIDByKey[key] + others := slices.DeleteFunc(slices.Clone(keysByTypeAndID[typeAndID(key, bindID)]), func(k string) bool { return k == key }) + if len(others) == 0 { + continue + } + b.logBindError(ctx, configRoot, key, fmt.Sprintf("%s has the same lifecycle.bind ID %q as %s; each workspace resource can only be bound once", key, bindID, strings.Join(others, ", "))) + hasError = true + } + + // A bind ID must not collide with another resource's ID in state. Such a collision + // would let a bind silently redirect a delete/recreate/update_id at a resource the + // user is also trying to import; reject it instead of choosing one over the other. + stateKeys := slices.Sorted(maps.Keys(b.StateDB.Data.State)) + for _, key := range keys { + bindID := bindIDByKey[key] + for _, stateKey := range stateKeys { + if stateKey == key || b.StateDB.Data.State[stateKey].ID != bindID || typeAndID(stateKey, bindID) != typeAndID(key, bindID) { + continue + } + b.logBindError(ctx, configRoot, key, fmt.Sprintf("%s has the same lifecycle.bind ID %q as existing resource %q; remove lifecycle.bind or the conflicting resource", key, bindID, stateKey)) + hasError = true + } + } + + return !hasError +} + +// typeAndID keys a bind by resource type and workspace ID, the scope within which IDs +// are unique. +func typeAndID(resourceKey, bindID string) string { + resourceType, _ := splitResourceKey(resourceKey) + return resourceType + ":" + bindID +} + +// PrepareBindings reads and validates resources to adopt without modifying deployment state. +func (b *DeploymentBundle) PrepareBindings(ctx context.Context, client *databricks.WorkspaceClient, configRoot *config.Root, selected []string) (map[string]*deployplan.BindEntry, error) { + b.StateDB.AssertOpenedForRead() + bindIDs := lifecycleBindIDs(configRoot) + if len(bindIDs) == 0 { + return nil, nil + } + if !b.validateBindConfig(ctx, configRoot, bindIDs) { + return nil, errors.New("bind validation failed") + } + if err := b.init(client); err != nil { + return nil, err + } + configured, err := b.makePlan(ctx, configRoot, &b.StateDB.Data) + if err != nil { + return nil, err + } + if len(selected) > 0 { + if err := configured.FilterToSelected(ctx, selected); err != nil { + return nil, err + } + } + bindings := make(map[string]*deployplan.BindEntry) + for _, resourceKey := range slices.Sorted(maps.Keys(bindIDs)) { + entry := configured.Plan[resourceKey] + if entry == nil || b.StateDB.GetResourceID(resourceKey) != "" { + continue + } + adapter, err := b.getAdapterForKey(resourceKey) + if err != nil { + return nil, err + } + remote, err := readBoundResource(ctx, adapter, resourceKey, bindIDs[resourceKey]) + if err != nil { + return nil, err + } + state, err := adapter.RemapState(remote) + if err != nil { + return nil, fmt.Errorf("reading binding state for %s: %w", resourceKey, err) + } + configuredState, _ := b.StateCache.Load(resourceKey) + state, err = prepareBindingState(adapter, state, configuredState.Value) + if err != nil { + return nil, fmt.Errorf("preparing binding for %s: %w", resourceKey, err) + } + encoded, err := json.Marshal(state) + if err != nil { + return nil, fmt.Errorf("serializing binding for %s: %w", resourceKey, err) + } + bindings[resourceKey] = &deployplan.BindEntry{ + ID: bindIDs[resourceKey], State: encoded, DependsOn: entry.DependsOn, + } + } + return bindings, nil +} + +// prepareBindingState imports the remote values of configured fields. Importing +// backend-only fields would claim ownership of defaults the bundle never set; +// importing desired values would hide changes that have not been applied yet. +func prepareBindingState(adapter *dresources.Adapter, remote, configured any) (any, error) { + diff, err := structdiff.GetStructDiff(remote, configured, adapter.KeyedSlices()) + if err != nil { + return nil, err + } + encoded, err := json.Marshal(remote) + if err != nil { + return nil, err + } + var state any + decoder := json.NewDecoder(bytes.NewReader(encoded)) + decoder.UseNumber() + if err := decoder.Decode(&state); err != nil { + return nil, err + } + // Remove positional array entries from the end so earlier indices stay valid. + for _, change := range slices.Backward(diff) { + value := reflect.ValueOf(change.New) + if value.IsValid() { + switch value.Kind() { + case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Interface: + if !value.IsNil() { + continue + } + default: + continue + } + } + // Dashboards and Genie spaces persist the remote etag for drift detection. + if change.Path.String() == "etag" { + continue + } + if change.Path.IsRoot() { + return nil, errors.New("cannot bind an empty resource configuration") + } + state, err = removeBindingField(state, change.Path.AsSlice()) + if err != nil { + return nil, fmt.Errorf("removing unconfigured field %s: %w", change.Path, err) + } + } + encoded, err = json.Marshal(state) + if err != nil { + return nil, err + } + typed, err := parseState(adapter.StateType(), encoded) + if err != nil { + return nil, err + } + return compactStateForSave(adapter, typed) +} + +// removeBindingField removes a field from a JSON value, including keyed array +// elements such as tasks[task_key='main']. The paths come from structdiff. +func removeBindingField(value any, path []*structpath.PathNode) (any, error) { + node := path[0] + if key, ok := node.StringKey(); ok { + object, ok := value.(map[string]any) + if !ok { + return nil, fmt.Errorf("expected an object at %s", node.Parent()) + } + if len(path) == 1 { + delete(object, key) + return object, nil + } + child, err := removeBindingField(object[key], path[1:]) + if err != nil { + return nil, err + } + object[key] = child + return object, nil + } + sequence, ok := value.([]any) + if !ok { + return nil, fmt.Errorf("expected an array at %s", node.Parent()) + } + index, indexed := node.Index() + if !indexed { + key, match, _ := node.KeyValue() + index = slices.IndexFunc(sequence, func(element any) bool { + field, _ := structaccess.GetByString(element, key) + return field == match + }) + } + if index < 0 || index >= len(sequence) { + return nil, fmt.Errorf("array element %s does not exist", node) + } + if len(path) == 1 { + return slices.Delete(sequence, index, index+1), nil + } + child, err := removeBindingField(sequence[index], path[1:]) + if err != nil { + return nil, err + } + sequence[index] = child + return sequence, nil +} + +func readBoundResource(ctx context.Context, adapter *dresources.Adapter, resourceKey, id string) (any, error) { + remote, err := retry.With(ctx, retry.IsTransientRead, func() (any, error) { + return adapter.DoRead(ctx, id) + }) + if apierr.IsMissing(err) || (err == nil && adapter.IsGone(remote)) { + return nil, fmt.Errorf("cannot bind %s: resource with ID %q does not exist in workspace", resourceKey, id) + } + if err != nil { + return nil, fmt.Errorf("cannot bind %s: reading remote resource id=%q: %w", resourceKey, id, err) + } + return remote, nil +} + +// CheckBindings verifies that resources from a saved bind phase still exist. +func (b *DeploymentBundle) CheckBindings(ctx context.Context, client *databricks.WorkspaceClient, bindings map[string]*deployplan.BindEntry) error { + if err := b.init(client); err != nil { + return err + } + for _, key := range slices.Sorted(maps.Keys(bindings)) { + adapter, err := b.getAdapterForKey(key) + if err != nil { + return err + } + if _, err := readBoundResource(ctx, adapter, key, bindings[key].ID); err != nil { + return err + } + } + return nil +} + +func (b *DeploymentBundle) checkBoundResource(ctx context.Context, resourceKey string, entry *deployplan.PlanEntry, adapter *dresources.Adapter) error { + if !entry.Action.KeepsID() { + return fmt.Errorf("cannot %s %s: lifecycle.bind requires preserving resource ID %q", entry.Action, resourceKey, entry.ID) + } + id := b.StateDB.GetResourceID(resourceKey) + if id != entry.ID { + return fmt.Errorf("cannot deploy %s: bound resource ID %q does not match state ID %q", resourceKey, entry.ID, id) + } + remote, err := retry.With(ctx, retry.IsTransientRead, func() (any, error) { + return adapter.DoRead(ctx, id) + }) + if apierr.IsMissing(err) || (err == nil && adapter.IsGone(remote)) { + return fmt.Errorf("cannot deploy %s: bound resource with ID %q no longer exists in the workspace; remove lifecycle.bind to create a new resource", resourceKey, id) + } + if err != nil { + return fmt.Errorf("reading bound resource %s: %w", resourceKey, err) + } + configured, ok := b.StateCache.Load(resourceKey) + if !ok { + return fmt.Errorf("cannot deploy %s: missing configured state for bound resource", resourceKey) + } + local, err := dresources.CompactState(adapter.ResourceConfig(), configured.Value) + if err != nil { + return err + } + comparable, err := adapter.RemapState(remote) + if err != nil { + return err + } + comparable, err = dresources.CompactState(adapter.ResourceConfig(), comparable) + if err != nil { + return err + } + diff, err := structdiff.GetStructDiff(comparable, local, adapter.KeyedSlices()) + if err != nil { + return err + } + saved, _ := b.StateDB.GetResourceEntry(resourceKey) + old, err := parseState(adapter.StateType(), saved.State) + if err != nil { + return err + } + changes, err := prepareChanges(ctx, adapter, nil, diff, old, comparable) + if err != nil { + return err + } + if err := addPerFieldActions(ctx, adapter, changes, configured.Value, remote); err != nil { + return err + } + action := getMaxAction(changes) + if action == deployplan.Recreate || action == deployplan.UpdateWithID { + return fmt.Errorf("cannot deploy %s: %w", resourceKey, buildBindConflictError(action, changes)) + } + return nil +} + +// buildBindConflictError names the fields that would change the bound resource's ID. +func buildBindConflictError(action deployplan.ActionType, changes deployplan.Changes) error { + problematicFields := make([]string, 0, len(changes)) + for fieldPath, change := range changes { + if change.Action == action { + problematicFields = append(problematicFields, fieldPath) + } + } + slices.Sort(problematicFields) + + var headline string + // buildBindConflictError is only invoked for Recreate / UpdateWithID; other + // action types are guarded by the caller. + switch action { //nolint:exhaustive + case deployplan.Recreate: + headline = "lifecycle.bind prevents recreating the bound resource" + case deployplan.UpdateWithID: + headline = "lifecycle.bind prevents changing the ID of the bound resource" + } + + var msg strings.Builder + msg.WriteString(headline + "\n\n") + msg.WriteString("Fields requiring ") + if action == deployplan.Recreate { + msg.WriteString("resource recreation:\n") + } else { + msg.WriteString("ID changes:\n") + } + for _, field := range problematicFields { + if reason := changes[field].Reason; reason != "" { + fmt.Fprintf(&msg, " - %s (%s)\n", field, reason) + } else { + fmt.Fprintf(&msg, " - %s\n", field) + } + } + msg.WriteString("\nMatch these fields to the existing resource to preserve its ID") + msg.WriteString("; remove lifecycle.bind to allow the ID to change") + + return errors.New(msg.String()) +} + +// splitResourceKey extracts the resource type and name from a key like +// "resources.jobs.foo" → ("jobs", "foo"). +func splitResourceKey(resourceKey string) (resourceType, resourceName string) { + parts := strings.SplitN(resourceKey, ".", 3) + if len(parts) < 3 { + return "", "" + } + return parts[1], parts[2] +} diff --git a/bundle/direct/bundle_plan_bind_test.go b/bundle/direct/bundle_plan_bind_test.go new file mode 100644 index 00000000000..344b0370603 --- /dev/null +++ b/bundle/direct/bundle_plan_bind_test.go @@ -0,0 +1,76 @@ +package direct_test + +import ( + "encoding/json" + "path/filepath" + "testing" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/direct" + "github.com/databricks/cli/bundle/direct/dstate" + "github.com/databricks/cli/libs/testserver" + "github.com/databricks/databricks-sdk-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPrepareBindingsImportsConfiguredRemoteFields(t *testing.T) { + for _, test := range []struct { + name string + resource string + path string + config string + remote string + state string + }{ + { + name: "immutable backend defaults", + resource: "experiments", + path: "/api/2.0/mlflow/experiments/get", + config: `{"name":"desired"}`, + remote: `{"experiment":{"experiment_id":"123","name":"existing","artifact_location":"dbfs:/existing"}}`, + state: `{"name":"existing"}`, + }, + { + name: "remote etag for drift detection", + resource: "genie_spaces", + path: "/api/2.0/genie/spaces/123", + config: `{"title":"desired"}`, + remote: `{"space_id":"123","title":"existing","etag":"remote-version","parent_path":"/Shared"}`, + state: `{"title":"existing","etag":"remote-version"}`, + }, + { + name: "keyed arrays, maps, explicit zero, and large integers", + resource: "jobs", + path: "/api/2.2/jobs/get", + config: `{"name":"desired","timeout_seconds":0,"tasks":[{"task_key":"a","notebook_task":{"notebook_path":"/a","base_parameters":{"keep":"new"}}},{"task_key":"b","description":"new"}]}`, + remote: `{"job_id":123,"settings":{"name":"existing","timeout_seconds":9007199254740993,"tasks":[{"task_key":"b","description":"old","timeout_seconds":50},{"task_key":"a","notebook_task":{"notebook_path":"/a","base_parameters":{"keep":"old","drop":"value"}}},{"task_key":"unused"}]}}`, + state: `{"name":"existing","timeout_seconds":9007199254740993,"tasks":[{"task_key":"b","description":"old"},{"task_key":"a","notebook_task":{"notebook_path":"/a","base_parameters":{"keep":"old"}}}]}`, + }, + } { + t.Run(test.name, func(t *testing.T) { + server := testserver.New(t) + server.Handle("GET", test.path, func(req testserver.Request) any { + return json.RawMessage(test.remote) + }) + client, err := databricks.NewWorkspaceClient(&databricks.Config{Host: server.URL, Token: "testtoken"}) + require.NoError(t, err) + var resource map[string]any + require.NoError(t, json.Unmarshal([]byte(test.config), &resource)) + resource["lifecycle"] = map[string]any{"bind": map[string]string{"id": "123"}} + source, err := json.Marshal(map[string]any{"resources": map[string]any{test.resource: map[string]any{"test": resource}}}) + require.NoError(t, err) + root, diagnostics := config.LoadFromBytes("databricks.yml", source) + require.Empty(t, diagnostics) + var deployment direct.DeploymentBundle + deployment.StateDB.OpenWithData(filepath.Join(t.TempDir(), "resources.json"), dstate.NewDatabase("", 0)) + bindings, err := deployment.PrepareBindings(t.Context(), client, root, nil) + require.NoError(t, err) + assert.JSONEq(t, test.state, string(bindings["resources."+test.resource+".test"].State)) + if test.resource == "jobs" { + assert.Contains(t, string(bindings["resources.jobs.test"].State), "9007199254740993") + } + assert.Empty(t, deployment.StateDB.Data.State) + }) + } +} diff --git a/bundle/direct/dresources/type_test.go b/bundle/direct/dresources/type_test.go index d7515ae55a8..38b758b9625 100644 --- a/bundle/direct/dresources/type_test.go +++ b/bundle/direct/dresources/type_test.go @@ -83,19 +83,23 @@ var knownMissingInStateType = map[string][]string{ "apps": { "lifecycle.prevent_destroy", "lifecycle.depends_on", + "lifecycle.bind", }, "clusters": { "lifecycle.prevent_destroy", "lifecycle.depends_on", + "lifecycle.bind", }, "sql_warehouses": { "lifecycle.prevent_destroy", "lifecycle.depends_on", + "lifecycle.bind", }, "job_runs": { // State stores trigger fingerprints, not the config trigger list / prevent_destroy. "lifecycle.prevent_destroy", "lifecycle.depends_on", + "lifecycle.bind", "lifecycle.triggers", }, "dashboards": { diff --git a/bundle/direct/dstate/bind.go b/bundle/direct/dstate/bind.go new file mode 100644 index 00000000000..bd480453023 --- /dev/null +++ b/bundle/direct/dstate/bind.go @@ -0,0 +1,46 @@ +package dstate + +import ( + "context" + "fmt" + "maps" + "slices" + + "github.com/databricks/cli/bundle/deployplan" +) + +// WithBindings returns an independent snapshot for previewing deployment after binding. +func (db *DeploymentState) WithBindings(bindings map[string]*deployplan.BindEntry) Database { + db.AssertOpenedForRead() + db.mu.Lock() + defer db.mu.Unlock() + data := db.Data + data.Features = maps.Clone(data.Features) + data.State = make(map[string]ResourceEntry, len(db.Data.State)+len(bindings)) + maps.Copy(data.State, db.Data.State) + for key, entry := range bindings { + data.State[key] = ResourceEntry{ID: entry.ID, State: entry.State, DependsOn: entry.DependsOn} + } + return data +} + +// Bind records bindings without changing the workspace resources. +func (db *DeploymentState) Bind(ctx context.Context, bindings map[string]*deployplan.BindEntry) error { + db.AssertOpenedForWrite() + keys := slices.Sorted(maps.Keys(bindings)) + for _, key := range keys { + if id := db.GetResourceID(key); id != "" { + return fmt.Errorf("cannot bind %s: already bound to ID %q", key, id) + } + if bindings[key].ID == "" { + return fmt.Errorf("cannot bind %s: missing resource ID", key) + } + } + for _, key := range keys { + entry := bindings[key] + if err := db.SaveState(ctx, key, entry.ID, entry.State, entry.DependsOn); err != nil { + return fmt.Errorf("binding %s: %w", key, err) + } + } + return nil +} diff --git a/bundle/direct/dstate/bind_test.go b/bundle/direct/dstate/bind_test.go new file mode 100644 index 00000000000..78286428137 --- /dev/null +++ b/bundle/direct/dstate/bind_test.go @@ -0,0 +1,50 @@ +package dstate_test + +import ( + "encoding/json" + "path/filepath" + "testing" + + "github.com/databricks/cli/bundle/deployplan" + "github.com/databricks/cli/bundle/direct/dstate" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBindingPreviewDoesNotChangeState(t *testing.T) { + statePath := filepath.Join(t.TempDir(), "resources.json") + var state dstate.DeploymentState + state.OpenWithData(statePath, dstate.NewDatabase("lineage", 4)) + bindings := map[string]*deployplan.BindEntry{ + "resources.jobs.job": {ID: "123", State: json.RawMessage(`{"name":"existing"}`)}, + } + preview := state.WithBindings(bindings) + assert.Equal(t, "123", preview.State["resources.jobs.job"].ID) + assert.Empty(t, state.GetResourceID("resources.jobs.job")) + assert.Empty(t, state.Data.State) + assert.Equal(t, 4, state.GetSerial()) + assert.NoFileExists(t, statePath) + assert.NoFileExists(t, statePath+".wal") +} + +func TestBindRejectsExistingResourcesBeforeWriting(t *testing.T) { + for _, id := range []string{"existing-id", "another-id"} { + t.Run(id, func(t *testing.T) { + statePath := filepath.Join(t.TempDir(), "resources.json") + var state dstate.DeploymentState + data := dstate.NewDatabase("lineage", 1) + data.State["resources.jobs.existing"] = dstate.ResourceEntry{ID: "existing-id", State: json.RawMessage(`{}`)} + state.OpenWithData(statePath, data) + require.NoError(t, state.UpgradeToWrite()) + err := state.Bind(t.Context(), map[string]*deployplan.BindEntry{ + "resources.jobs.a_new": {ID: "new-id", State: json.RawMessage(`{}`)}, + "resources.jobs.existing": {ID: id, State: json.RawMessage(`{}`)}, + }) + require.ErrorContains(t, err, "already bound") + assert.Empty(t, state.GetResourceID("resources.jobs.a_new")) + assert.Equal(t, "existing-id", state.GetResourceID("resources.jobs.existing")) + _, err = state.Finalize(t.Context()) + require.NoError(t, err) + }) + } +} diff --git a/bundle/internal/schema/annotations.yml b/bundle/internal/schema/annotations.yml index 08b65053e3b..06fb7490c97 100644 --- a/bundle/internal/schema/annotations.yml +++ b/bundle/internal/schema/annotations.yml @@ -629,6 +629,9 @@ resources: "description": |- Settings that control the deployment lifecycle of the resource, such as preventing it from being destroyed. "$fields": + "bind": + "description": |- + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. "depends_on": "description": |- Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -1117,6 +1120,9 @@ resources: "description": |- Settings that control the deployment lifecycle of the resource, such as preventing it from being destroyed and when the run re-fires. "$fields": + "bind": + "description": |- + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. "depends_on": "description": |- Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -1200,6 +1206,13 @@ resources: "description": |- Settings that control the deployment lifecycle of the resource, such as preventing it from being destroyed. "$fields": + "bind": + "description": |- + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + "$fields": + "id": + "description": |- + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. "depends_on": "description": |- Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/bundle/internal/validation/generated/required_fields.go b/bundle/internal/validation/generated/required_fields.go index 6b2ad1f369d..c45e91b966c 100644 --- a/bundle/internal/validation/generated/required_fields.go +++ b/bundle/internal/validation/generated/required_fields.go @@ -15,6 +15,7 @@ var RequiredFields = map[string][]string{ "resources.alerts.*.evaluation": {"comparison_operator", "source"}, "resources.alerts.*.evaluation.source": {"name"}, "resources.alerts.*.evaluation.threshold.column": {"name"}, + "resources.alerts.*.lifecycle.bind": {"id"}, "resources.alerts.*.lifecycle.depends_on[*]": {"resource"}, "resources.alerts.*.parameters[*]": {"name"}, "resources.alerts.*.permissions[*]": {"level"}, @@ -26,6 +27,7 @@ var RequiredFields = map[string][]string{ "resources.apps.*.default_git_source.git_repository": {"provider", "url"}, "resources.apps.*.git_repository": {"provider", "url"}, "resources.apps.*.git_source.git_repository": {"provider", "url"}, + "resources.apps.*.lifecycle.bind": {"id"}, "resources.apps.*.lifecycle.depends_on[*]": {"resource"}, "resources.apps.*.pending_deployment.git_source.git_repository": {"provider", "url"}, "resources.apps.*.permissions[*]": {"level"}, @@ -41,12 +43,14 @@ var RequiredFields = map[string][]string{ "resources.apps.*.telemetry_export_destinations[*].unity_catalog": {"logs_table", "metrics_table", "traces_table"}, "resources.catalogs.*": {"name"}, + "resources.catalogs.*.lifecycle.bind": {"id"}, "resources.catalogs.*.lifecycle.depends_on[*]": {"resource"}, "resources.catalogs.*.managed_encryption_settings.azure_encryption_settings": {"azure_tenant_id"}, "resources.cluster_policies.*.libraries[*].cran": {"package"}, "resources.cluster_policies.*.libraries[*].maven": {"coordinates"}, "resources.cluster_policies.*.libraries[*].pypi": {"package"}, + "resources.cluster_policies.*.lifecycle.bind": {"id"}, "resources.cluster_policies.*.lifecycle.depends_on[*]": {"resource"}, "resources.cluster_policies.*.permissions[*]": {"level"}, @@ -63,10 +67,12 @@ var RequiredFields = map[string][]string{ "resources.clusters.*.libraries[*].cran": {"package"}, "resources.clusters.*.libraries[*].maven": {"coordinates"}, "resources.clusters.*.libraries[*].pypi": {"package"}, + "resources.clusters.*.lifecycle.bind": {"id"}, "resources.clusters.*.lifecycle.depends_on[*]": {"resource"}, "resources.clusters.*.permissions[*]": {"level"}, "resources.clusters.*.workload_type": {"clients"}, + "resources.dashboards.*.lifecycle.bind": {"id"}, "resources.dashboards.*.lifecycle.depends_on[*]": {"resource"}, "resources.dashboards.*.permissions[*]": {"level"}, @@ -76,21 +82,26 @@ var RequiredFields = map[string][]string{ "resources.data_quality_monitors.*.data_profiling_config.inference_log": {"granularities", "model_id_column", "prediction_column", "problem_type", "timestamp_column"}, "resources.data_quality_monitors.*.data_profiling_config.schedule": {"quartz_cron_expression", "timezone_id"}, "resources.data_quality_monitors.*.data_profiling_config.time_series": {"granularities", "timestamp_column"}, + "resources.data_quality_monitors.*.lifecycle.bind": {"id"}, "resources.data_quality_monitors.*.lifecycle.depends_on[*]": {"resource"}, "resources.database_catalogs.*": {"database_instance_name", "database_name", "name"}, + "resources.database_catalogs.*.lifecycle.bind": {"id"}, "resources.database_catalogs.*.lifecycle.depends_on[*]": {"resource"}, "resources.database_instances.*": {"name"}, + "resources.database_instances.*.lifecycle.bind": {"id"}, "resources.database_instances.*.lifecycle.depends_on[*]": {"resource"}, "resources.database_instances.*.permissions[*]": {"level"}, "resources.experiments.*": {"name"}, + "resources.experiments.*.lifecycle.bind": {"id"}, "resources.experiments.*.lifecycle.depends_on[*]": {"resource"}, "resources.experiments.*.permissions[*]": {"level"}, "resources.experiments.*.trace_location.uc_trace_location": {"catalog", "schema"}, "resources.external_locations.*": {"credential_name", "name", "url"}, + "resources.external_locations.*.lifecycle.bind": {"id"}, "resources.external_locations.*.lifecycle.depends_on[*]": {"resource"}, "resources.features.*": {"full_name", "function", "source"}, @@ -119,6 +130,7 @@ var RequiredFields = map[string][]string{ "resources.features.*.function.custom_udf": {"function_path"}, "resources.features.*.function.custom_udf.input_bindings[*]": {"column", "parameter"}, "resources.features.*.function.extra_parameters[*]": {"key", "value"}, + "resources.features.*.lifecycle.bind": {"id"}, "resources.features.*.lifecycle.depends_on[*]": {"resource"}, "resources.features.*.source.delta_table_source": {"full_name"}, "resources.features.*.source.feature_view_source.feature_references[*]": {"feature"}, @@ -133,14 +145,17 @@ var RequiredFields = map[string][]string{ "resources.features.*.time_window.tumbling": {"window_duration"}, "resources.features.*.timeseries_column": {"name"}, + "resources.genie_spaces.*.lifecycle.bind": {"id"}, "resources.genie_spaces.*.lifecycle.depends_on[*]": {"resource"}, "resources.genie_spaces.*.permissions[*]": {"level"}, "resources.instance_pools.*": {"instance_pool_name", "node_type_id"}, + "resources.instance_pools.*.lifecycle.bind": {"id"}, "resources.instance_pools.*.lifecycle.depends_on[*]": {"resource"}, "resources.instance_pools.*.permissions[*]": {"level"}, "resources.job_runs.*": {"job_id"}, + "resources.job_runs.*.lifecycle.bind": {"id"}, "resources.job_runs.*.lifecycle.depends_on[*]": {"resource"}, "resources.job_runs.*.queue": {"enabled"}, @@ -163,6 +178,7 @@ var RequiredFields = map[string][]string{ "resources.jobs.*.job_clusters[*].new_cluster.init_scripts[*].volumes": {"destination"}, "resources.jobs.*.job_clusters[*].new_cluster.init_scripts[*].workspace": {"destination"}, "resources.jobs.*.job_clusters[*].new_cluster.workload_type": {"clients"}, + "resources.jobs.*.lifecycle.bind": {"id"}, "resources.jobs.*.lifecycle.depends_on[*]": {"resource"}, "resources.jobs.*.parameters[*]": {"default", "name"}, "resources.jobs.*.permissions[*]": {"level"}, @@ -299,6 +315,7 @@ var RequiredFields = map[string][]string{ "resources.jobs.*.webhook_notifications.on_streaming_backlog_exceeded[*]": {"id"}, "resources.jobs.*.webhook_notifications.on_success[*]": {"id"}, + "resources.materialization_sets.*.lifecycle.bind": {"id"}, "resources.materialization_sets.*.lifecycle.depends_on[*]": {"resource"}, "resources.materialization_sets.*.materialized_features[*]": {"feature_name"}, "resources.materialization_sets.*.materialized_features[*].offline_store_config": {"catalog_name", "schema_name", "table_name_prefix"}, @@ -307,6 +324,7 @@ var RequiredFields = map[string][]string{ "resources.mcp_services.*": {"parent", "mcp_service_id"}, "resources.mcp_services.*.config.rate_limits[*]": {"key", "renewal_period"}, "resources.mcp_services.*.config.source_connection": {"name"}, + "resources.mcp_services.*.lifecycle.bind": {"id"}, "resources.mcp_services.*.lifecycle.depends_on[*]": {"resource"}, "resources.model_provider_services.*": {"parent", "model_provider_service_id"}, @@ -329,6 +347,7 @@ var RequiredFields = map[string][]string{ "resources.model_provider_services.*.config.openai.direct.api_key.secret_reference": {"name"}, "resources.model_provider_services.*.config.rate_limits[*]": {"key", "renewal_period"}, "resources.model_provider_services.*.config.targets[*]": {"model"}, + "resources.model_provider_services.*.lifecycle.bind": {"id"}, "resources.model_provider_services.*.lifecycle.depends_on[*]": {"resource"}, "resources.model_services.*": {"parent", "model_service_id"}, @@ -344,6 +363,7 @@ var RequiredFields = map[string][]string{ "resources.model_services.*.config.routing.fallback.destinations[*].external_model_config.target": {"model"}, "resources.model_services.*.config.routing.fallback.destinations[*].pay_per_token_config": {"model"}, "resources.model_services.*.config.routing.fallback.destinations[*].provisioned_throughput_config": {"model_serving_endpoint"}, + "resources.model_services.*.lifecycle.bind": {"id"}, "resources.model_services.*.lifecycle.depends_on[*]": {"resource"}, "resources.model_serving_endpoints.*": {"name"}, @@ -357,12 +377,14 @@ var RequiredFields = map[string][]string{ "resources.model_serving_endpoints.*.config.served_entities[*].external_model.google_cloud_vertex_ai_config": {"project_id", "region"}, "resources.model_serving_endpoints.*.config.served_models[*]": {"model_name", "model_version", "scale_to_zero_enabled"}, "resources.model_serving_endpoints.*.config.traffic_config.routes[*]": {"traffic_percentage"}, + "resources.model_serving_endpoints.*.lifecycle.bind": {"id"}, "resources.model_serving_endpoints.*.lifecycle.depends_on[*]": {"resource"}, "resources.model_serving_endpoints.*.permissions[*]": {"level"}, "resources.model_serving_endpoints.*.rate_limits[*]": {"calls", "renewal_period"}, "resources.model_serving_endpoints.*.tags[*]": {"key"}, "resources.models.*": {"name"}, + "resources.models.*.lifecycle.bind": {"id"}, "resources.models.*.lifecycle.depends_on[*]": {"resource"}, "resources.models.*.permissions[*]": {"level"}, @@ -397,34 +419,43 @@ var RequiredFields = map[string][]string{ "resources.pipelines.*.ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy": {"enabled"}, "resources.pipelines.*.ingestion_definition.table_configuration.auto_full_refresh_policy": {"enabled"}, "resources.pipelines.*.libraries[*].maven": {"coordinates"}, + "resources.pipelines.*.lifecycle.bind": {"id"}, "resources.pipelines.*.lifecycle.depends_on[*]": {"resource"}, "resources.pipelines.*.permissions[*]": {"level"}, "resources.pipelines.*.restart_window": {"start_hour"}, "resources.postgres_branches.*": {"branch_id", "parent"}, + "resources.postgres_branches.*.lifecycle.bind": {"id"}, "resources.postgres_branches.*.lifecycle.depends_on[*]": {"resource"}, "resources.postgres_catalogs.*": {"postgres_database", "catalog_id"}, + "resources.postgres_catalogs.*.lifecycle.bind": {"id"}, "resources.postgres_catalogs.*.lifecycle.depends_on[*]": {"resource"}, "resources.postgres_data_apis.*": {"parent"}, + "resources.postgres_data_apis.*.lifecycle.bind": {"id"}, "resources.postgres_data_apis.*.lifecycle.depends_on[*]": {"resource"}, "resources.postgres_databases.*": {"role", "database_id", "parent"}, + "resources.postgres_databases.*.lifecycle.bind": {"id"}, "resources.postgres_databases.*.lifecycle.depends_on[*]": {"resource"}, "resources.postgres_endpoints.*": {"endpoint_type", "endpoint_id", "parent"}, "resources.postgres_endpoints.*.group": {"max", "min"}, + "resources.postgres_endpoints.*.lifecycle.bind": {"id"}, "resources.postgres_endpoints.*.lifecycle.depends_on[*]": {"resource"}, "resources.postgres_projects.*": {"project_id"}, + "resources.postgres_projects.*.lifecycle.bind": {"id"}, "resources.postgres_projects.*.lifecycle.depends_on[*]": {"resource"}, "resources.postgres_projects.*.permissions[*]": {"level"}, "resources.postgres_roles.*": {"role_id", "parent"}, + "resources.postgres_roles.*.lifecycle.bind": {"id"}, "resources.postgres_roles.*.lifecycle.depends_on[*]": {"resource"}, "resources.postgres_snapshot_schedules.*": {"branch"}, + "resources.postgres_snapshot_schedules.*.lifecycle.bind": {"id"}, "resources.postgres_snapshot_schedules.*.lifecycle.depends_on[*]": {"resource"}, "resources.postgres_snapshot_schedules.*.schedule[*]": {"retention"}, "resources.postgres_snapshot_schedules.*.schedule[*].monthly_schedule": {"day"}, @@ -432,44 +463,55 @@ var RequiredFields = map[string][]string{ "resources.postgres_synced_tables.*": {"synced_table_id"}, "resources.postgres_synced_tables.*.extra_columns[*]": {"column_name", "column_type"}, + "resources.postgres_synced_tables.*.lifecycle.bind": {"id"}, "resources.postgres_synced_tables.*.lifecycle.depends_on[*]": {"resource"}, "resources.postgres_synced_tables.*.type_overrides[*]": {"column_name", "pg_type"}, "resources.quality_monitors.*": {"assets_dir", "output_schema_name", "table_name"}, "resources.quality_monitors.*.custom_metrics[*]": {"definition", "input_columns", "name", "output_data_type", "type"}, "resources.quality_monitors.*.inference_log": {"granularities", "model_id_col", "prediction_col", "problem_type", "timestamp_col"}, + "resources.quality_monitors.*.lifecycle.bind": {"id"}, "resources.quality_monitors.*.lifecycle.depends_on[*]": {"resource"}, "resources.quality_monitors.*.schedule": {"quartz_cron_expression", "timezone_id"}, "resources.quality_monitors.*.time_series": {"granularities", "timestamp_col"}, + "resources.registered_models.*.lifecycle.bind": {"id"}, "resources.registered_models.*.lifecycle.depends_on[*]": {"resource"}, "resources.schemas.*": {"catalog_name", "name"}, + "resources.schemas.*.lifecycle.bind": {"id"}, "resources.schemas.*.lifecycle.depends_on[*]": {"resource"}, "resources.secret_scopes.*": {"name"}, "resources.secret_scopes.*.keyvault_metadata": {"dns_name", "resource_id"}, + "resources.secret_scopes.*.lifecycle.bind": {"id"}, "resources.secret_scopes.*.lifecycle.depends_on[*]": {"resource"}, "resources.secret_scopes.*.permissions[*]": {"level"}, "resources.secrets.*": {"catalog_name", "name", "schema_name", "value"}, + "resources.secrets.*.lifecycle.bind": {"id"}, "resources.secrets.*.lifecycle.depends_on[*]": {"resource"}, + "resources.sql_warehouses.*.lifecycle.bind": {"id"}, "resources.sql_warehouses.*.lifecycle.depends_on[*]": {"resource"}, "resources.sql_warehouses.*.permissions[*]": {"level"}, "resources.synced_database_tables.*": {"name"}, + "resources.synced_database_tables.*.lifecycle.bind": {"id"}, "resources.synced_database_tables.*.lifecycle.depends_on[*]": {"resource"}, "resources.synced_database_tables.*.spec.type_overrides[*]": {"column_name", "pg_type"}, "resources.vector_search_endpoints.*": {"endpoint_type", "name"}, + "resources.vector_search_endpoints.*.lifecycle.bind": {"id"}, "resources.vector_search_endpoints.*.lifecycle.depends_on[*]": {"resource"}, "resources.vector_search_endpoints.*.permissions[*]": {"level"}, "resources.vector_search_indexes.*": {"endpoint_name", "index_type", "name", "primary_key"}, + "resources.vector_search_indexes.*.lifecycle.bind": {"id"}, "resources.vector_search_indexes.*.lifecycle.depends_on[*]": {"resource"}, "resources.volumes.*": {"catalog_name", "name", "schema_name", "volume_type"}, + "resources.volumes.*.lifecycle.bind": {"id"}, "resources.volumes.*.lifecycle.depends_on[*]": {"resource"}, "scripts.*": {"content"}, diff --git a/bundle/phases/bind.go b/bundle/phases/bind.go index 36c4fcf56b4..fef226abe99 100644 --- a/bundle/phases/bind.go +++ b/bundle/phases/bind.go @@ -15,8 +15,10 @@ import ( "github.com/databricks/cli/bundle/statemgmt" "github.com/databricks/cli/libs/agent" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structpath" ) type BindOptions struct { @@ -49,6 +51,16 @@ func Bind(ctx context.Context, b *bundle.Bundle, opts *BindOptions, stateDesc *s // Direct engine: import into temp state, run plan, check for changes. resourceKey := fmt.Sprintf("resources.%s.%s", opts.Group, opts.ResourceKey) + resource, _, err := b.Config.Resources.ResolveKey(opts.Group + "." + opts.ResourceKey) + if err == nil && resource.GetLifecycle().GetBind() != nil { + bindPath := structpath.NewPath(nil, "resources", opts.Group, opts.ResourceKey, "lifecycle", "bind") + logdiag.LogDiag(ctx, diag.Diagnostic{ + Severity: diag.Warning, + Summary: resourceKey + ".lifecycle.bind already declares the resource ID; use 'bundle deploy' to bind it declaratively", + Locations: b.Config.GetLocationsOf(bindPath), + Paths: []*structpath.PathNode{bindPath}, + }) + } _, statePath := b.StateFilenameDirect(ctx) result, err := b.DeploymentBundle.Bind(ctx, b.WorkspaceClient(ctx), &b.Config, statePath, resourceKey, opts.ResourceId) @@ -142,4 +154,17 @@ func Unbind(ctx context.Context, b *bundle.Bundle, group, resourceKey string, en } statemgmt.PushResourcesState(ctx, b) + + // Bind is state-less: nothing records that the resource was just unbound, so a + // lifecycle.bind block still in config re-adopts it on the next deploy. + resource, _, err := b.Config.Resources.ResolveKey(group + "." + resourceKey) + if err == nil && resource.GetLifecycle().GetBind() != nil { + bindPath := structpath.NewPath(nil, "resources", group, resourceKey, "lifecycle", "bind") + logdiag.LogDiag(ctx, diag.Diagnostic{ + Severity: diag.Warning, + Summary: fullResourceKey + ".lifecycle.bind re-binds the resource on the next deploy; remove it to keep the resource unbound", + Locations: b.Config.GetLocationsOf(bindPath), + Paths: []*structpath.PathNode{bindPath}, + }) + } } diff --git a/bundle/phases/declarative_bind.go b/bundle/phases/declarative_bind.go new file mode 100644 index 00000000000..c6c856c01ba --- /dev/null +++ b/bundle/phases/declarative_bind.go @@ -0,0 +1,107 @@ +package phases + +import ( + "context" + "maps" + "slices" + "strings" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/deploy/metadata" + "github.com/databricks/cli/bundle/deployplan" + "github.com/databricks/cli/bundle/direct/dstate" + "github.com/databricks/cli/bundle/statemgmt" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/dms" + "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/databricks-sdk-go/service/bundledeployments" +) + +func applyBindPhase(ctx context.Context, b *bundle.Bundle, bindings map[string]*deployplan.BindEntry, current *bundledeployments.Deployment) *bundledeployments.Deployment { + if b.MigratingToDirect { + statemgmt.CommitMigration(ctx, b) + if logdiag.HasError(ctx) { + return current + } + b.MigratingToDirect = false + } + db := &b.DeploymentBundle.StateDB + statePath := db.Path + mode := dstate.ReadingFile + if db.IsDeploymentMetadataService() { + mode = dstate.WritingDeploymentHistoryOnly + } + if err := db.UpgradeToWrite(); err != nil { + logdiag.LogError(ctx, err) + return current + } + if db.RecordsHistory() { + var err error + current, err = prepareBindHistory(ctx, b, bindings, current) + if err != nil { + if db.IsDeploymentMetadataService() { + logdiag.LogError(ctx, err) + } else { + log.Warnf(ctx, "Could not record binding history: %s", err) + } + } + } + if !logdiag.HasError(ctx) { + if err := db.Bind(ctx, bindings); err != nil { + logdiag.LogError(ctx, err) + } + } + state, err := db.Finalize(ctx) + b.Metrics.ResourceState = state + if err != nil { + logdiag.LogError(ctx, err) + } + statemgmt.PushResourcesState(ctx, b) + if _, err := db.CompleteVersion(ctx, !logdiag.HasError(ctx)); err != nil { + if db.IsDeploymentMetadataService() { + logdiag.LogError(ctx, err) + } else { + log.Warnf(ctx, "Failed to complete binding history version: %s", err) + } + } + if logdiag.HasError(ctx) { + return current + } + if reportPerResource(b) { + for _, key := range slices.Sorted(maps.Keys(bindings)) { + cmdio.LogString(ctx, "Bound "+strings.TrimPrefix(key, "resources.")) + } + } + history := dstate.OpenDmsArgs{DeploymentID: db.DeploymentID, LastVersionID: db.VersionID} + if err := db.Open(ctx, statePath, mode, dstate.WithRecovery(true), history); err != nil { + logdiag.LogError(ctx, err) + return current + } + if db.RecordsHistory() { + bundle.ApplyContext(ctx, b, metadata.AnnotateDeploymentVersion(db.GetSerial()+1)) + if db.DeploymentID != "" { + bundle.ApplyContext(ctx, b, metadata.AnnotateDeployment(db.DeploymentID)) + } + } + return current +} + +func prepareBindHistory(ctx context.Context, b *bundle.Bundle, bindings map[string]*deployplan.BindEntry, current *bundledeployments.Deployment) (*bundledeployments.Deployment, error) { + current, err := createOrUpdateDeployment(ctx, b, current) + if err != nil { + return current, err + } + staged := make([]bundledeployments.StagedOperation, 0, len(bindings)) + for _, key := range slices.Sorted(maps.Keys(bindings)) { + staged = append(staged, bundledeployments.StagedOperation{ + ResourceKey: strings.TrimPrefix(key, dms.StatePrefix), + ActionType: bundledeployments.OperationActionTypeOperationActionTypeBind, + }) + } + if err := startVersion(ctx, b, dms.VersionTypeDeploy, staged); err != nil { + return current, err + } + logDeploymentVersion(ctx, b) + return current, nil +} diff --git a/bundle/phases/declarative_bind_test.go b/bundle/phases/declarative_bind_test.go new file mode 100644 index 00000000000..5017bc9662a --- /dev/null +++ b/bundle/phases/declarative_bind_test.go @@ -0,0 +1,78 @@ +package phases_test + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/deployplan" + "github.com/databricks/cli/bundle/direct/dstate" + "github.com/databricks/cli/bundle/phases" + "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/testserver" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/postgres" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBindResolvesStableParentOutputs(t *testing.T) { + for _, parentAction := range []deployplan.ActionType{deployplan.Skip, deployplan.Update} { + for _, managedChild := range []bool{false, true} { + for _, parent := range []string{"projects/p", "${resources.postgres_projects.project.name}"} { + t.Run(fmt.Sprintf("%s/managed=%t/%s", parentAction, managedChild, parent), func(t *testing.T) { + server := testserver.New(t) + testserver.AddDefaultHandlers(server) + client, err := databricks.NewWorkspaceClient(&databricks.Config{Host: server.URL, Token: "testtoken"}) + require.NoError(t, err) + server.Handle("GET", "/api/2.0/postgres/projects/p", func(req testserver.Request) any { + return postgres.Project{ + Name: "projects/p", + ProjectId: "p", + Spec: &postgres.ProjectSpec{DisplayName: "existing project"}, + } + }) + server.Handle("GET", "/api/2.0/postgres/projects/p/branches/b", func(req testserver.Request) any { + return postgres.Branch{Name: "projects/p/branches/b", BranchId: "b", Parent: "projects/p"} + }) + source := `{"resources":{"postgres_projects":{"project":{"project_id":"p","display_name":"existing project","lifecycle":{"bind":{"id":"projects/p"}}}},"postgres_branches":{"branch":{"parent":"PARENT","branch_id":"b","lifecycle":{"bind":{"id":"projects/p/branches/b"}}}}}}` + if parentAction == deployplan.Update { + source = strings.ReplaceAll(source, "existing project", "updated project") + } + if managedChild { + source = strings.ReplaceAll(source, `,"lifecycle":{"bind":{"id":"projects/p/branches/b"}}`, "") + } + root, diagnostics := config.LoadFromBytes("databricks.yml", []byte(strings.ReplaceAll(source, "PARENT", parent))) + require.Empty(t, diagnostics) + ctx := logdiag.InitContext(t.Context()) + logdiag.SetCollect(ctx, true) + configured := &bundle.Bundle{Config: *root} + configured.SetWorkpaceClient(client) + deployment := &configured.DeploymentBundle + statePath := filepath.Join(t.TempDir(), "resources.json") + if managedChild { + require.NoError(t, deployment.StateDB.Open(ctx, statePath, dstate.WritingFile, dstate.WithRecovery(true), dstate.OpenDmsArgs{})) + require.NoError(t, deployment.StateDB.SaveState(ctx, "resources.postgres_branches.branch", "projects/p/branches/b", &resources.PostgresBranchConfig{Parent: "projects/p", BranchId: "b"}, nil)) + _, err := deployment.StateDB.Finalize(ctx) + require.NoError(t, err) + } + require.NoError(t, deployment.StateDB.Open(ctx, statePath, dstate.ReadingFile, dstate.WithRecovery(true), dstate.OpenDmsArgs{})) + t.Cleanup(func() { + _, err := deployment.StateDB.Finalize(ctx) + assert.NoError(t, err) + }) + plan := phases.RunPlan(ctx, configured) + require.NotNil(t, plan, "%v", logdiag.FlushCollected(ctx)) + assert.Empty(t, deployment.StateDB.GetResourceID("resources.postgres_projects.project")) + assert.Equal(t, "projects/p", plan.Bind["resources.postgres_projects.project"].ID) + assert.Equal(t, parentAction, plan.Plan["resources.postgres_projects.project"].Action) + assert.Equal(t, deployplan.Skip, plan.Plan["resources.postgres_branches.branch"].Action) + }) + } + } + } +} diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index c3c3306130e..40baba56ece 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -15,6 +15,7 @@ import ( "github.com/databricks/cli/bundle/deploy/metadata" "github.com/databricks/cli/bundle/deploy/snapshot" "github.com/databricks/cli/bundle/deployplan" + "github.com/databricks/cli/bundle/direct" "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/bundle/metrics" "github.com/databricks/cli/bundle/permissions" @@ -132,6 +133,9 @@ func logDeploySummary(ctx context.Context, b *bundle.Bundle, plan *deployplan.Pl counts := plan.CountActions() summary := fmt.Sprintf("Resources: %d created, %d changed, %d deleted, %d unchanged", counts.Create, counts.Change, counts.Delete, counts.Unchanged) + if counts.Bind > 0 { + summary += fmt.Sprintf(", %d bound", counts.Bind) + } // Gate on the plan's own NotSelected (not b.Select) so the suffix survives a // deploy from a --plan file, where --select was applied at plan time and // b.Select is empty here. NotSelected is only ever set by FilterToSelected. @@ -168,7 +172,7 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand // lock is acquired here // - // The version is created only after approval; CompleteVersion is deferred before + // The apply version is created only after approval; CompleteVersion is deferred before // lock.Release and no-ops until then. defer func() { if b.DeploymentBundle.StateDB.RecordsHistory() { @@ -250,10 +254,36 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand } planFromFile := plan != nil + var bindings map[string]*deployplan.BindEntry + if planFromFile { + bindings = plan.Bind + if len(bindings) > 0 { + if err := b.DeploymentBundle.CheckBindings(ctx, b.WorkspaceClient(ctx), bindings); err != nil { + logdiag.LogError(ctx, err) + return + } + } + } else { + var err error + bindings, err = b.DeploymentBundle.PrepareBindings(ctx, b.WorkspaceClient(ctx), &b.Config, b.Select) + if err != nil { + logdiag.LogError(ctx, err) + return + } + } + if len(bindings) > 0 { + dmsDeployment = applyBindPhase(ctx, b, bindings, dmsDeployment) + if logdiag.HasError(ctx) { + return + } + } if plan == nil { // State is already open for read by process.go (or by Migrate) plan = RunPlan(ctx, b) + if plan != nil { + plan.Bind = bindings + } } // Stop before opening the WAL for write if planning failed. UpgradeToWrite @@ -265,9 +295,9 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand haveApproval, err := approvalForDeploy(ctx, b, plan) if !haveApproval { - // No version was created, so the deferred CompleteVersion is a no-op and the version - // number is left for the next deploy. Both the user declining and a console that - // cannot prompt land here. + // No apply version was created, so the deferred CompleteVersion is a no-op and the + // version number is left for the next deploy. Any bind version has already completed. + // Both the user declining and a console that cannot prompt land here. // // A prepared terraform→direct migration lives only in memory (Migrate wrote nothing // durable and CommitMigration below never ran), so a declined deploy just drops it and @@ -355,17 +385,30 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand } func RunPlan(ctx context.Context, b *bundle.Bundle) *deployplan.Plan { - plan, err := b.DeploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(ctx), &b.Config) + bindings, err := b.DeploymentBundle.PrepareBindings(ctx, b.WorkspaceClient(ctx), &b.Config, b.Select) if err != nil { logdiag.LogError(ctx, err) return nil } - if len(b.Select) > 0 { - if err := plan.FilterToSelected(ctx, b.Select); err != nil { - logdiag.LogError(ctx, err) - return nil + deployment := &b.DeploymentBundle + if len(bindings) > 0 { + if deployment.StateDB.RecordsHistory() { + bundle.ApplyContext(ctx, b, metadata.AnnotateDeploymentVersion(deployment.StateDB.GetSerial()+2)) + if logdiag.HasError(ctx) { + return nil + } } + preview := &direct.DeploymentBundle{Adapters: deployment.Adapters} + preview.StateDB.OpenWithData(deployment.StateDB.Path, deployment.StateDB.WithBindings(bindings)) + preview.StateDB.VersionID = deployment.StateDB.VersionID + deployment = preview + } + plan, err := deployment.CalculatePlan(ctx, b.WorkspaceClient(ctx), &b.Config, b.Select...) + if err != nil { + logdiag.LogError(ctx, err) + return nil } + plan.Bind = bindings return plan } diff --git a/bundle/phases/destroy.go b/bundle/phases/destroy.go index b3e885e54d4..8e0e87687e6 100644 --- a/bundle/phases/destroy.go +++ b/bundle/phases/destroy.go @@ -322,6 +322,9 @@ func Destroy(ctx context.Context, b *bundle.Bundle) { bundle.ApplyContext(ctx, b, lock.Release(lock.GoalDestroy)) }() + // Planned without config, so lifecycle.bind plays no role: naming destroy explicitly + // captures the intent to tear the deployment down, bound resources included. The + // block only guards against a recreate during a normal deploy. plan, err := b.DeploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(ctx), nil) if err != nil { logdiag.LogError(ctx, err) diff --git a/bundle/phases/dms.go b/bundle/phases/dms.go index 38210913ce8..5e4a49d8853 100644 --- a/bundle/phases/dms.go +++ b/bundle/phases/dms.go @@ -69,15 +69,15 @@ func actionToSDK(a deployplan.ActionType) (bundledeployments.OperationActionType } // createOrUpdateDeployment creates the deployment on a first deploy, or updates the metadata this -// run changed. current is the record the service holds (nil before the first recorded deploy), -// diffed to mask the update down to what changed. Runs after approval, so a declined deploy leaves -// no deployment behind; a first deploy's new id is then stamped into the plan (StampDeploymentID). -func createOrUpdateDeployment(ctx context.Context, b *bundle.Bundle, current *bundledeployments.Deployment) error { +// run changed, returning the current record for later phases. current is the record the service +// holds (nil before the first recorded deploy), diffed to update only changed fields. Binding +// can create the deployment before resource changes are planned and approved. +func createOrUpdateDeployment(ctx context.Context, b *bundle.Bundle, current *bundledeployments.Deployment) (*bundledeployments.Deployment, error) { db := &b.DeploymentBundle w := b.WorkspaceClient(ctx) metadata, err := deploymentMetadata(b) if err != nil { - return fmt.Errorf("failed to compute deployment metadata: %w", err) + return current, fmt.Errorf("failed to compute deployment metadata: %w", err) } deploymentID := db.StateDB.DeploymentID if deploymentID == "" { @@ -85,16 +85,17 @@ func createOrUpdateDeployment(ctx context.Context, b *bundle.Bundle, current *bu dep.InitialParentPath = b.Config.Workspace.StatePath created, err := w.BundleDeployments.CreateDeployment(ctx, bundledeployments.CreateDeploymentRequest{Deployment: dep}) if err != nil { - return fmt.Errorf("failed to create deployment: %w", err) + return current, fmt.Errorf("failed to create deployment: %w", err) } // The id is the workspace node the server makes under the parent path. deploymentID, err = dms.DeploymentIDFromName(created.Name) if err != nil { - return fmt.Errorf("failed to create deployment: %w", err) + return current, fmt.Errorf("failed to create deployment: %w", err) } db.StateDB.DeploymentID = deploymentID + current = created } else if mask := metadata.StaleFields(current); mask != "" { - _, err := retry.With(ctx, retry.IsTransient, func() (*bundledeployments.Deployment, error) { + updated, err := retry.With(ctx, retry.IsTransient, func() (*bundledeployments.Deployment, error) { return w.BundleDeployments.UpdateDeployment(ctx, bundledeployments.UpdateDeploymentRequest{ Name: dms.DeploymentName(deploymentID), Deployment: dms.DeploymentUpdate(metadata, mask), @@ -102,15 +103,16 @@ func createOrUpdateDeployment(ctx context.Context, b *bundle.Bundle, current *bu }) }) if err != nil { - return fmt.Errorf("failed to update deployment: %w", err) + return current, fmt.Errorf("failed to update deployment: %w", err) } + current = updated } // A first deploy had no deployment to read at startup, so its id enters the history here. bundle.ApplyFuncContext(ctx, b, func(_ context.Context, b *bundle.Bundle) { b.Config.Bundle.Deployment.DeploymentID = deploymentID }) - return nil + return current, nil } // deleteDeployment deletes the deployment record. A retry that finds it missing succeeds: the @@ -130,14 +132,11 @@ func deleteDeployment(ctx context.Context, w *databricks.WorkspaceClient, deploy // prepareDeploymentHistory creates history metadata and a version after approval. func prepareDeploymentHistory(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan, current *bundledeployments.Deployment) error { - firstDeploy := b.DeploymentBundle.StateDB.DeploymentID == "" - if err := createOrUpdateDeployment(ctx, b, current); err != nil { + if _, err := createOrUpdateDeployment(ctx, b, current); err != nil { return err } - if firstDeploy { - if err := b.DeploymentBundle.StampDeploymentIdForFirstVersion(b.DeploymentBundle.StateDB.DeploymentID); err != nil { - return err - } + if err := b.DeploymentBundle.StampDeploymentIdForFirstVersion(b.DeploymentBundle.StateDB.DeploymentID); err != nil { + return err } staged, err := stagedOperations(plan) if err != nil { @@ -156,7 +155,7 @@ func prepareDeploymentHistory(ctx context.Context, b *bundle.Bundle, plan *deplo // prepareDestroyHistory records the approved destroy without changing its state source. func prepareDestroyHistory(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan) error { if !b.DeploymentBundle.StateDB.IsDeploymentMetadataService() && b.DeploymentBundle.StateDB.DeploymentID == "" { - if err := createOrUpdateDeployment(ctx, b, nil); err != nil { + if _, err := createOrUpdateDeployment(ctx, b, nil); err != nil { return err } } @@ -167,8 +166,8 @@ func prepareDestroyHistory(ctx context.Context, b *bundle.Bundle, plan *deploypl return startVersion(ctx, b, dms.VersionTypeDestroy, staged) } -// startVersion opens a history version after approval. File-backed history skips empty versions; -// DMS-only destroy retains its existing behavior. A declined deploy never claims a version. +// startVersion opens a history version for the phase being committed. File-backed history skips empty versions; +// DMS-only destroy retains its existing behavior. A declined apply never claims a version. // A no-op when the bundle does not record deployment history. func startVersion(ctx context.Context, b *bundle.Bundle, versionType dms.VersionType, staged []bundledeployments.StagedOperation) error { db := &b.DeploymentBundle diff --git a/bundle/schema/jsonschema.json b/bundle/schema/jsonschema.json index 9035a2be76c..8043a6db902 100644 --- a/bundle/schema/jsonschema.json +++ b/bundle/schema/jsonschema.json @@ -350,6 +350,27 @@ } ] }, + "resources.Bind": { + "oneOf": [ + { + "type": "object", + "properties": { + "id": { + "description": "The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false, + "required": [ + "id" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\._*\\p{L}+([-_]*[\\p{L}\\p{N}]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "resources.Catalog": { "oneOf": [ { @@ -1675,6 +1696,10 @@ { "type": "object", "properties": { + "bind": { + "description": "Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state.", + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Bind" + }, "depends_on": { "description": "Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering.", "$ref": "#/$defs/slice/github.com/databricks/cli/bundle/config/resources.DependsOn" @@ -1723,6 +1748,10 @@ { "type": "object", "properties": { + "bind": { + "description": "Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state.", + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Bind" + }, "depends_on": { "description": "Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering.", "$ref": "#/$defs/slice/github.com/databricks/cli/bundle/config/resources.DependsOn" @@ -1745,6 +1774,10 @@ { "type": "object", "properties": { + "bind": { + "description": "Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state.", + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Bind" + }, "depends_on": { "description": "Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering.", "$ref": "#/$defs/slice/github.com/databricks/cli/bundle/config/resources.DependsOn" diff --git a/cmd/bundle/plan.go b/cmd/bundle/plan.go index 1132834bc4d..4ffc2f9bb6a 100644 --- a/cmd/bundle/plan.go +++ b/cmd/bundle/plan.go @@ -3,6 +3,8 @@ package bundle import ( "encoding/json" "fmt" + "maps" + "slices" "strings" "github.com/databricks/cli/bundle" @@ -82,8 +84,20 @@ It is useful for previewing changes before running 'bundle deploy'.`, switch root.OutputType(cmd) { case flags.OutputText: // Print summary line and actions to stdout - totalChanges := counts.Create + counts.Change + counts.Delete + totalChanges := counts.Create + counts.Change + counts.Delete + counts.Bind if totalChanges > 0 && bundle.QuietLevel(quiet) < bundle.QuietSummary { + indent := "" + if len(plan.Bind) > 0 { + indent = " " + fmt.Fprintln(out, "Bind:") + for _, key := range slices.Sorted(maps.Keys(plan.Bind)) { + fmt.Fprintf(out, " %s (id: %s)\n", strings.TrimPrefix(key, "resources."), plan.Bind[key].ID) + } + fmt.Fprintln(out, "\nDeploy:") + if counts.Create+counts.Change+counts.Delete == 0 { + fmt.Fprintln(out, " No changes") + } + } // Print all actions in the order they were processed for _, action := range plan.GetActions() { if action.ActionType == deployplan.Skip { @@ -95,7 +109,7 @@ It is useful for previewing changes before running 'bundle deploy'.`, continue } key := strings.TrimPrefix(action.ResourceKey, "resources.") - fmt.Fprintf(out, "%s %s\n", action.ActionType.StringShort(), key) + fmt.Fprintf(out, "%s%s %s\n", indent, action.ActionType.StringShort(), key) } fmt.Fprintln(out) } @@ -104,6 +118,9 @@ It is useful for previewing changes before running 'bundle deploy'.`, } // Note, this string should not be changed, "bundle deployment migrate" depends on this format: fmt.Fprintf(out, "Plan: %d to add, %d to change, %d to delete, %d unchanged", counts.Create, counts.Change, counts.Delete, counts.Unchanged) + if counts.Bind > 0 { + fmt.Fprintf(out, ", %d to bind", counts.Bind) + } if len(selectResources) > 0 { fmt.Fprintf(out, ", %d not selected", plan.NotSelected) } diff --git a/python/databricks/bundles/alerts/__init__.py b/python/databricks/bundles/alerts/__init__.py index edc2e7294d9..4785eeb3caa 100644 --- a/python/databricks/bundles/alerts/__init__.py +++ b/python/databricks/bundles/alerts/__init__.py @@ -32,6 +32,9 @@ "AlertV2Subscription", "AlertV2SubscriptionDict", "AlertV2SubscriptionParam", + "Bind", + "BindDict", + "BindParam", "ComparisonOperator", "ComparisonOperatorParam", "CronSchedule", @@ -101,6 +104,7 @@ AlertV2SubscriptionDict, AlertV2SubscriptionParam, ) +from databricks.bundles.alerts._models.bind import Bind, BindDict, BindParam from databricks.bundles.alerts._models.comparison_operator import ( ComparisonOperator, ComparisonOperatorParam, diff --git a/python/databricks/bundles/alerts/_models/bind.py b/python/databricks/bundles/alerts/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/alerts/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/alerts/_models/lifecycle.py b/python/databricks/bundles/alerts/_models/lifecycle.py index 3f8bb9dec50..f917656cb2c 100644 --- a/python/databricks/bundles/alerts/_models/lifecycle.py +++ b/python/databricks/bundles/alerts/_models/lifecycle.py @@ -3,6 +3,7 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, TypedDict +from databricks.bundles.alerts._models.bind import Bind, BindParam from databricks.bundles.alerts._models.depends_on import DependsOn, DependsOnParam from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/apps/__init__.py b/python/databricks/bundles/apps/__init__.py index 944c7424fd5..4d1c18730cb 100644 --- a/python/databricks/bundles/apps/__init__.py +++ b/python/databricks/bundles/apps/__init__.py @@ -70,6 +70,9 @@ "AppResourceUcSecurableUcSecurablePermissionParam", "AppResourceUcSecurableUcSecurableType", "AppResourceUcSecurableUcSecurableTypeParam", + "Bind", + "BindDict", + "BindParam", "ComputeSize", "ComputeSizeParam", "DependsOn", @@ -212,6 +215,7 @@ AppResourceUcSecurableUcSecurableType, AppResourceUcSecurableUcSecurableTypeParam, ) +from databricks.bundles.apps._models.bind import Bind, BindDict, BindParam from databricks.bundles.apps._models.compute_size import ComputeSize, ComputeSizeParam from databricks.bundles.apps._models.depends_on import ( DependsOn, diff --git a/python/databricks/bundles/apps/_models/bind.py b/python/databricks/bundles/apps/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/apps/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/apps/_models/lifecycle_with_started.py b/python/databricks/bundles/apps/_models/lifecycle_with_started.py index 5ceb1848cc3..e1e244ffae1 100644 --- a/python/databricks/bundles/apps/_models/lifecycle_with_started.py +++ b/python/databricks/bundles/apps/_models/lifecycle_with_started.py @@ -3,6 +3,7 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, TypedDict +from databricks.bundles.apps._models.bind import Bind, BindParam from databricks.bundles.apps._models.depends_on import DependsOn, DependsOnParam from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value @@ -16,6 +17,11 @@ class LifecycleWithStarted: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -42,6 +48,11 @@ def as_dict(self) -> "LifecycleWithStartedDict": class LifecycleWithStartedDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/catalogs/__init__.py b/python/databricks/bundles/catalogs/__init__.py index fced08f1bb2..da17929ec74 100644 --- a/python/databricks/bundles/catalogs/__init__.py +++ b/python/databricks/bundles/catalogs/__init__.py @@ -4,6 +4,9 @@ "AzureEncryptionSettings", "AzureEncryptionSettingsDict", "AzureEncryptionSettingsParam", + "Bind", + "BindDict", + "BindParam", "Catalog", "CatalogDict", "CatalogGrant", @@ -34,6 +37,7 @@ AzureEncryptionSettingsDict, AzureEncryptionSettingsParam, ) +from databricks.bundles.catalogs._models.bind import Bind, BindDict, BindParam from databricks.bundles.catalogs._models.catalog import ( Catalog, CatalogDict, diff --git a/python/databricks/bundles/catalogs/_models/bind.py b/python/databricks/bundles/catalogs/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/catalogs/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/catalogs/_models/lifecycle.py b/python/databricks/bundles/catalogs/_models/lifecycle.py index 0a9985c5b30..5656ce99205 100644 --- a/python/databricks/bundles/catalogs/_models/lifecycle.py +++ b/python/databricks/bundles/catalogs/_models/lifecycle.py @@ -3,6 +3,7 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, TypedDict +from databricks.bundles.catalogs._models.bind import Bind, BindParam from databricks.bundles.catalogs._models.depends_on import DependsOn, DependsOnParam from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/cluster_policies/__init__.py b/python/databricks/bundles/cluster_policies/__init__.py index 5f5790166f7..92e19472d4d 100644 --- a/python/databricks/bundles/cluster_policies/__init__.py +++ b/python/databricks/bundles/cluster_policies/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "ClusterPolicy", "ClusterPolicyDict", "ClusterPolicyParam", @@ -30,6 +33,7 @@ ] +from databricks.bundles.cluster_policies._models.bind import Bind, BindDict, BindParam from databricks.bundles.cluster_policies._models.cluster_policy import ( ClusterPolicy, ClusterPolicyDict, diff --git a/python/databricks/bundles/cluster_policies/_models/bind.py b/python/databricks/bundles/cluster_policies/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/cluster_policies/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/cluster_policies/_models/lifecycle.py b/python/databricks/bundles/cluster_policies/_models/lifecycle.py index ccec29db600..0563fe47d11 100644 --- a/python/databricks/bundles/cluster_policies/_models/lifecycle.py +++ b/python/databricks/bundles/cluster_policies/_models/lifecycle.py @@ -3,6 +3,7 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, TypedDict +from databricks.bundles.cluster_policies._models.bind import Bind, BindParam from databricks.bundles.cluster_policies._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/clusters/__init__.py b/python/databricks/bundles/clusters/__init__.py index feaa262198f..3796dee5368 100644 --- a/python/databricks/bundles/clusters/__init__.py +++ b/python/databricks/bundles/clusters/__init__.py @@ -17,6 +17,9 @@ "AzureAttributesParam", "AzureAvailability", "AzureAvailabilityParam", + "Bind", + "BindDict", + "BindParam", "ClientsTypes", "ClientsTypesDict", "ClientsTypesParam", @@ -133,6 +136,7 @@ AzureAvailability, AzureAvailabilityParam, ) +from databricks.bundles.clusters._models.bind import Bind, BindDict, BindParam from databricks.bundles.clusters._models.clients_types import ( ClientsTypes, ClientsTypesDict, diff --git a/python/databricks/bundles/clusters/_models/bind.py b/python/databricks/bundles/clusters/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/clusters/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/clusters/_models/lifecycle_with_started.py b/python/databricks/bundles/clusters/_models/lifecycle_with_started.py index 27cf9aabbf8..6c7dbdeda79 100644 --- a/python/databricks/bundles/clusters/_models/lifecycle_with_started.py +++ b/python/databricks/bundles/clusters/_models/lifecycle_with_started.py @@ -3,6 +3,7 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, TypedDict +from databricks.bundles.clusters._models.bind import Bind, BindParam from databricks.bundles.clusters._models.depends_on import DependsOn, DependsOnParam from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value @@ -16,6 +17,11 @@ class LifecycleWithStarted: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -42,6 +48,11 @@ def as_dict(self) -> "LifecycleWithStartedDict": class LifecycleWithStartedDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/dashboards/__init__.py b/python/databricks/bundles/dashboards/__init__.py index 7e383631fb2..233b0015ffc 100644 --- a/python/databricks/bundles/dashboards/__init__.py +++ b/python/databricks/bundles/dashboards/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "Dashboard", "DashboardDict", "DashboardParam", @@ -20,6 +23,7 @@ ] +from databricks.bundles.dashboards._models.bind import Bind, BindDict, BindParam from databricks.bundles.dashboards._models.dashboard import ( Dashboard, DashboardDict, diff --git a/python/databricks/bundles/dashboards/_models/bind.py b/python/databricks/bundles/dashboards/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/dashboards/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/dashboards/_models/lifecycle.py b/python/databricks/bundles/dashboards/_models/lifecycle.py index 616e3db5db0..d0d69eaa435 100644 --- a/python/databricks/bundles/dashboards/_models/lifecycle.py +++ b/python/databricks/bundles/dashboards/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.dashboards._models.bind import Bind, BindParam from databricks.bundles.dashboards._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/data_quality_monitors/__init__.py b/python/databricks/bundles/data_quality_monitors/__init__.py index 47c0aa88dc0..16125cb939a 100644 --- a/python/databricks/bundles/data_quality_monitors/__init__.py +++ b/python/databricks/bundles/data_quality_monitors/__init__.py @@ -6,6 +6,9 @@ "AnomalyDetectionConfig", "AnomalyDetectionConfigDict", "AnomalyDetectionConfigParam", + "Bind", + "BindDict", + "BindParam", "CronSchedule", "CronScheduleDict", "CronScheduleParam", @@ -55,6 +58,11 @@ AnomalyDetectionConfigDict, AnomalyDetectionConfigParam, ) +from databricks.bundles.data_quality_monitors._models.bind import ( + Bind, + BindDict, + BindParam, +) from databricks.bundles.data_quality_monitors._models.cron_schedule import ( CronSchedule, CronScheduleDict, diff --git a/python/databricks/bundles/data_quality_monitors/_models/bind.py b/python/databricks/bundles/data_quality_monitors/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/data_quality_monitors/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/data_quality_monitors/_models/lifecycle.py b/python/databricks/bundles/data_quality_monitors/_models/lifecycle.py index 2643a1c4e95..baba7fc3b85 100644 --- a/python/databricks/bundles/data_quality_monitors/_models/lifecycle.py +++ b/python/databricks/bundles/data_quality_monitors/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.data_quality_monitors._models.bind import Bind, BindParam from databricks.bundles.data_quality_monitors._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/database_catalogs/__init__.py b/python/databricks/bundles/database_catalogs/__init__.py index a02e6710b39..633b373d88d 100644 --- a/python/databricks/bundles/database_catalogs/__init__.py +++ b/python/databricks/bundles/database_catalogs/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DatabaseCatalog", "DatabaseCatalogDict", "DatabaseCatalogParam", @@ -13,6 +16,7 @@ ] +from databricks.bundles.database_catalogs._models.bind import Bind, BindDict, BindParam from databricks.bundles.database_catalogs._models.database_catalog import ( DatabaseCatalog, DatabaseCatalogDict, diff --git a/python/databricks/bundles/database_catalogs/_models/bind.py b/python/databricks/bundles/database_catalogs/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/database_catalogs/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/database_catalogs/_models/lifecycle.py b/python/databricks/bundles/database_catalogs/_models/lifecycle.py index 098d125a985..928edc9c745 100644 --- a/python/databricks/bundles/database_catalogs/_models/lifecycle.py +++ b/python/databricks/bundles/database_catalogs/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.database_catalogs._models.bind import Bind, BindParam from databricks.bundles.database_catalogs._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/database_instances/__init__.py b/python/databricks/bundles/database_instances/__init__.py index f6b05ef2765..f92fd55c82c 100644 --- a/python/databricks/bundles/database_instances/__init__.py +++ b/python/databricks/bundles/database_instances/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "CustomTag", "CustomTagDict", "CustomTagParam", @@ -24,6 +27,7 @@ ] +from databricks.bundles.database_instances._models.bind import Bind, BindDict, BindParam from databricks.bundles.database_instances._models.custom_tag import ( CustomTag, CustomTagDict, diff --git a/python/databricks/bundles/database_instances/_models/bind.py b/python/databricks/bundles/database_instances/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/database_instances/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/database_instances/_models/lifecycle.py b/python/databricks/bundles/database_instances/_models/lifecycle.py index 2e81c847cc6..7cf4ede5e49 100644 --- a/python/databricks/bundles/database_instances/_models/lifecycle.py +++ b/python/databricks/bundles/database_instances/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.database_instances._models.bind import Bind, BindParam from databricks.bundles.database_instances._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/experiments/__init__.py b/python/databricks/bundles/experiments/__init__.py index 78726dd8002..e6b01f08927 100644 --- a/python/databricks/bundles/experiments/__init__.py +++ b/python/databricks/bundles/experiments/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -27,6 +30,7 @@ ] +from databricks.bundles.experiments._models.bind import Bind, BindDict, BindParam from databricks.bundles.experiments._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/experiments/_models/bind.py b/python/databricks/bundles/experiments/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/experiments/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/experiments/_models/lifecycle.py b/python/databricks/bundles/experiments/_models/lifecycle.py index 08ee894bfbc..332aec97859 100644 --- a/python/databricks/bundles/experiments/_models/lifecycle.py +++ b/python/databricks/bundles/experiments/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.experiments._models.bind import Bind, BindParam from databricks.bundles.experiments._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/external_locations/__init__.py b/python/databricks/bundles/external_locations/__init__.py index 18f867345b2..32ba39ab02f 100644 --- a/python/databricks/bundles/external_locations/__init__.py +++ b/python/databricks/bundles/external_locations/__init__.py @@ -7,6 +7,9 @@ "AzureQueueStorage", "AzureQueueStorageDict", "AzureQueueStorageParam", + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -48,6 +51,7 @@ AzureQueueStorageDict, AzureQueueStorageParam, ) +from databricks.bundles.external_locations._models.bind import Bind, BindDict, BindParam from databricks.bundles.external_locations._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/external_locations/_models/bind.py b/python/databricks/bundles/external_locations/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/external_locations/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/external_locations/_models/lifecycle.py b/python/databricks/bundles/external_locations/_models/lifecycle.py index ace07eb75a1..186622e0a4d 100644 --- a/python/databricks/bundles/external_locations/_models/lifecycle.py +++ b/python/databricks/bundles/external_locations/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.external_locations._models.bind import Bind, BindParam from databricks.bundles.external_locations._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/features/__init__.py b/python/databricks/bundles/features/__init__.py index 28b95182245..fc9843349c8 100644 --- a/python/databricks/bundles/features/__init__.py +++ b/python/databricks/bundles/features/__init__.py @@ -13,6 +13,9 @@ "AvgFunction", "AvgFunctionDict", "AvgFunctionParam", + "Bind", + "BindDict", + "BindParam", "ColumnSelection", "ColumnSelectionDict", "ColumnSelectionParam", @@ -161,6 +164,7 @@ AvgFunctionDict, AvgFunctionParam, ) +from databricks.bundles.features._models.bind import Bind, BindDict, BindParam from databricks.bundles.features._models.column_selection import ( ColumnSelection, ColumnSelectionDict, diff --git a/python/databricks/bundles/features/_models/bind.py b/python/databricks/bundles/features/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/features/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/features/_models/lifecycle.py b/python/databricks/bundles/features/_models/lifecycle.py index 53c3a4b38b9..982d9ace1a8 100644 --- a/python/databricks/bundles/features/_models/lifecycle.py +++ b/python/databricks/bundles/features/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.features._models.bind import Bind, BindParam from databricks.bundles.features._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/genie_spaces/__init__.py b/python/databricks/bundles/genie_spaces/__init__.py index 960dd98014d..5dd9ef0f56e 100644 --- a/python/databricks/bundles/genie_spaces/__init__.py +++ b/python/databricks/bundles/genie_spaces/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -18,6 +21,7 @@ ] +from databricks.bundles.genie_spaces._models.bind import Bind, BindDict, BindParam from databricks.bundles.genie_spaces._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/genie_spaces/_models/bind.py b/python/databricks/bundles/genie_spaces/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/genie_spaces/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/genie_spaces/_models/lifecycle.py b/python/databricks/bundles/genie_spaces/_models/lifecycle.py index 8e740937885..6ff1b5a31a3 100644 --- a/python/databricks/bundles/genie_spaces/_models/lifecycle.py +++ b/python/databricks/bundles/genie_spaces/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.genie_spaces._models.bind import Bind, BindParam from databricks.bundles.genie_spaces._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/instance_pools/__init__.py b/python/databricks/bundles/instance_pools/__init__.py index 782be8340a5..8a842a373a5 100644 --- a/python/databricks/bundles/instance_pools/__init__.py +++ b/python/databricks/bundles/instance_pools/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -52,6 +55,7 @@ ] +from databricks.bundles.instance_pools._models.bind import Bind, BindDict, BindParam from databricks.bundles.instance_pools._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/instance_pools/_models/bind.py b/python/databricks/bundles/instance_pools/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/instance_pools/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/instance_pools/_models/lifecycle.py b/python/databricks/bundles/instance_pools/_models/lifecycle.py index 182c0682172..1f3d0c9bdb3 100644 --- a/python/databricks/bundles/instance_pools/_models/lifecycle.py +++ b/python/databricks/bundles/instance_pools/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.instance_pools._models.bind import Bind, BindParam from databricks.bundles.instance_pools._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/job_runs/__init__.py b/python/databricks/bundles/job_runs/__init__.py index 4b83903777d..9a71d0b3290 100644 --- a/python/databricks/bundles/job_runs/__init__.py +++ b/python/databricks/bundles/job_runs/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -24,6 +27,7 @@ ] +from databricks.bundles.job_runs._models.bind import Bind, BindDict, BindParam from databricks.bundles.job_runs._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/job_runs/_models/bind.py b/python/databricks/bundles/job_runs/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/job_runs/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/job_runs/_models/job_run_lifecycle.py b/python/databricks/bundles/job_runs/_models/job_run_lifecycle.py index 01d44e54ad3..0826d659b95 100644 --- a/python/databricks/bundles/job_runs/_models/job_run_lifecycle.py +++ b/python/databricks/bundles/job_runs/_models/job_run_lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.job_runs._models.bind import Bind, BindParam from databricks.bundles.job_runs._models.depends_on import DependsOn, DependsOnParam from databricks.bundles.job_runs._models.job_run_trigger import ( JobRunTrigger, @@ -20,6 +21,11 @@ class JobRunLifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -46,6 +52,11 @@ def as_dict(self) -> "JobRunLifecycleDict": class JobRunLifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/jobs/__init__.py b/python/databricks/bundles/jobs/__init__.py index d75e2474de1..ef42f2f8dbe 100644 --- a/python/databricks/bundles/jobs/__init__.py +++ b/python/databricks/bundles/jobs/__init__.py @@ -52,6 +52,9 @@ "AzureAttributesParam", "AzureAvailability", "AzureAvailabilityParam", + "Bind", + "BindDict", + "BindParam", "CleanRoomsNotebookTask", "CleanRoomsNotebookTaskDict", "CleanRoomsNotebookTaskParam", @@ -494,6 +497,7 @@ AzureAvailability, AzureAvailabilityParam, ) +from databricks.bundles.jobs._models.bind import Bind, BindDict, BindParam from databricks.bundles.jobs._models.clean_rooms_notebook_task import ( CleanRoomsNotebookTask, CleanRoomsNotebookTaskDict, diff --git a/python/databricks/bundles/jobs/_models/bind.py b/python/databricks/bundles/jobs/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/jobs/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/jobs/_models/lifecycle.py b/python/databricks/bundles/jobs/_models/lifecycle.py index 12040802104..d4e32783c29 100644 --- a/python/databricks/bundles/jobs/_models/lifecycle.py +++ b/python/databricks/bundles/jobs/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.jobs._models.bind import Bind, BindParam from databricks.bundles.jobs._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/materialization_sets/__init__.py b/python/databricks/bundles/materialization_sets/__init__.py index a7e26eaaf07..41b7e9567e2 100644 --- a/python/databricks/bundles/materialization_sets/__init__.py +++ b/python/databricks/bundles/materialization_sets/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "CronSchedule", "CronScheduleDict", "CronScheduleMode", @@ -46,6 +49,11 @@ ] +from databricks.bundles.materialization_sets._models.bind import ( + Bind, + BindDict, + BindParam, +) from databricks.bundles.materialization_sets._models.cron_schedule import ( CronSchedule, CronScheduleDict, diff --git a/python/databricks/bundles/materialization_sets/_models/bind.py b/python/databricks/bundles/materialization_sets/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/materialization_sets/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/materialization_sets/_models/lifecycle.py b/python/databricks/bundles/materialization_sets/_models/lifecycle.py index e2721031364..f1c12a2b86a 100644 --- a/python/databricks/bundles/materialization_sets/_models/lifecycle.py +++ b/python/databricks/bundles/materialization_sets/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.materialization_sets._models.bind import Bind, BindParam from databricks.bundles.materialization_sets._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/mcp_services/__init__.py b/python/databricks/bundles/mcp_services/__init__.py index 981e709bff8..af10858d3be 100644 --- a/python/databricks/bundles/mcp_services/__init__.py +++ b/python/databricks/bundles/mcp_services/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -31,6 +34,7 @@ ] +from databricks.bundles.mcp_services._models.bind import Bind, BindDict, BindParam from databricks.bundles.mcp_services._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/mcp_services/_models/bind.py b/python/databricks/bundles/mcp_services/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/mcp_services/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/mcp_services/_models/lifecycle.py b/python/databricks/bundles/mcp_services/_models/lifecycle.py index eca16a56d15..981e3111f60 100644 --- a/python/databricks/bundles/mcp_services/_models/lifecycle.py +++ b/python/databricks/bundles/mcp_services/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.mcp_services._models.bind import Bind, BindParam from databricks.bundles.mcp_services._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/model_provider_services/__init__.py b/python/databricks/bundles/model_provider_services/__init__.py index 57b9bb80463..ed93a148814 100644 --- a/python/databricks/bundles/model_provider_services/__init__.py +++ b/python/databricks/bundles/model_provider_services/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -102,6 +105,11 @@ ] +from databricks.bundles.model_provider_services._models.bind import ( + Bind, + BindDict, + BindParam, +) from databricks.bundles.model_provider_services._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/model_provider_services/_models/bind.py b/python/databricks/bundles/model_provider_services/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/model_provider_services/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/model_provider_services/_models/lifecycle.py b/python/databricks/bundles/model_provider_services/_models/lifecycle.py index 2837780438d..e48cefd7dfc 100644 --- a/python/databricks/bundles/model_provider_services/_models/lifecycle.py +++ b/python/databricks/bundles/model_provider_services/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.model_provider_services._models.bind import Bind, BindParam from databricks.bundles.model_provider_services._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/model_services/__init__.py b/python/databricks/bundles/model_services/__init__.py index 2e40ce88cb2..157f633fa01 100644 --- a/python/databricks/bundles/model_services/__init__.py +++ b/python/databricks/bundles/model_services/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -54,6 +57,7 @@ ] +from databricks.bundles.model_services._models.bind import Bind, BindDict, BindParam from databricks.bundles.model_services._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/model_services/_models/bind.py b/python/databricks/bundles/model_services/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/model_services/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/model_services/_models/lifecycle.py b/python/databricks/bundles/model_services/_models/lifecycle.py index fa15d53667c..475cbfa0b36 100644 --- a/python/databricks/bundles/model_services/_models/lifecycle.py +++ b/python/databricks/bundles/model_services/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.model_services._models.bind import Bind, BindParam from databricks.bundles.model_services._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/model_serving_endpoints/__init__.py b/python/databricks/bundles/model_serving_endpoints/__init__.py index be47098b53e..ef7fb7b1015 100644 --- a/python/databricks/bundles/model_serving_endpoints/__init__.py +++ b/python/databricks/bundles/model_serving_endpoints/__init__.py @@ -48,6 +48,9 @@ "BearerTokenAuth", "BearerTokenAuthDict", "BearerTokenAuthParam", + "Bind", + "BindDict", + "BindParam", "CohereConfig", "CohereConfigDict", "CohereConfigParam", @@ -215,6 +218,11 @@ BearerTokenAuthDict, BearerTokenAuthParam, ) +from databricks.bundles.model_serving_endpoints._models.bind import ( + Bind, + BindDict, + BindParam, +) from databricks.bundles.model_serving_endpoints._models.cohere_config import ( CohereConfig, CohereConfigDict, diff --git a/python/databricks/bundles/model_serving_endpoints/_models/bind.py b/python/databricks/bundles/model_serving_endpoints/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/model_serving_endpoints/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/model_serving_endpoints/_models/lifecycle.py b/python/databricks/bundles/model_serving_endpoints/_models/lifecycle.py index f6e3412eb9a..61e5b85fd69 100644 --- a/python/databricks/bundles/model_serving_endpoints/_models/lifecycle.py +++ b/python/databricks/bundles/model_serving_endpoints/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.model_serving_endpoints._models.bind import Bind, BindParam from databricks.bundles.model_serving_endpoints._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/models/__init__.py b/python/databricks/bundles/models/__init__.py index fa9922454b7..e040ceeb807 100644 --- a/python/databricks/bundles/models/__init__.py +++ b/python/databricks/bundles/models/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -21,6 +24,7 @@ ] +from databricks.bundles.models._models.bind import Bind, BindDict, BindParam from databricks.bundles.models._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/models/_models/bind.py b/python/databricks/bundles/models/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/models/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/models/_models/lifecycle.py b/python/databricks/bundles/models/_models/lifecycle.py index 45e0915ebab..ca34d77ab2e 100644 --- a/python/databricks/bundles/models/_models/lifecycle.py +++ b/python/databricks/bundles/models/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.models._models.bind import Bind, BindParam from databricks.bundles.models._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/pipelines/__init__.py b/python/databricks/bundles/pipelines/__init__.py index 412cbfa59b3..00f1b49a432 100644 --- a/python/databricks/bundles/pipelines/__init__.py +++ b/python/databricks/bundles/pipelines/__init__.py @@ -26,6 +26,9 @@ "AzureAttributesParam", "AzureAvailability", "AzureAvailabilityParam", + "Bind", + "BindDict", + "BindParam", "ClusterLogConf", "ClusterLogConfDict", "ClusterLogConfParam", @@ -338,6 +341,7 @@ AzureAvailability, AzureAvailabilityParam, ) +from databricks.bundles.pipelines._models.bind import Bind, BindDict, BindParam from databricks.bundles.pipelines._models.cluster_log_conf import ( ClusterLogConf, ClusterLogConfDict, diff --git a/python/databricks/bundles/pipelines/_models/bind.py b/python/databricks/bundles/pipelines/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/pipelines/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/pipelines/_models/lifecycle.py b/python/databricks/bundles/pipelines/_models/lifecycle.py index 6ef8f4fc104..309c83efe3a 100644 --- a/python/databricks/bundles/pipelines/_models/lifecycle.py +++ b/python/databricks/bundles/pipelines/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.pipelines._models.bind import Bind, BindParam from databricks.bundles.pipelines._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/quality_monitors/__init__.py b/python/databricks/bundles/quality_monitors/__init__.py index 806df764ade..e281999b695 100644 --- a/python/databricks/bundles/quality_monitors/__init__.py +++ b/python/databricks/bundles/quality_monitors/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -43,6 +46,7 @@ ] +from databricks.bundles.quality_monitors._models.bind import Bind, BindDict, BindParam from databricks.bundles.quality_monitors._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/quality_monitors/_models/bind.py b/python/databricks/bundles/quality_monitors/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/quality_monitors/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/quality_monitors/_models/lifecycle.py b/python/databricks/bundles/quality_monitors/_models/lifecycle.py index 53ba2aafd3d..d7305fdcc1c 100644 --- a/python/databricks/bundles/quality_monitors/_models/lifecycle.py +++ b/python/databricks/bundles/quality_monitors/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.quality_monitors._models.bind import Bind, BindParam from databricks.bundles.quality_monitors._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/registered_models/__init__.py b/python/databricks/bundles/registered_models/__init__.py index e193f063599..932c5bd5b4d 100644 --- a/python/databricks/bundles/registered_models/__init__.py +++ b/python/databricks/bundles/registered_models/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -21,6 +24,7 @@ ] +from databricks.bundles.registered_models._models.bind import Bind, BindDict, BindParam from databricks.bundles.registered_models._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/registered_models/_models/bind.py b/python/databricks/bundles/registered_models/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/registered_models/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/registered_models/_models/lifecycle.py b/python/databricks/bundles/registered_models/_models/lifecycle.py index b8642305131..9200d60f957 100644 --- a/python/databricks/bundles/registered_models/_models/lifecycle.py +++ b/python/databricks/bundles/registered_models/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.registered_models._models.bind import Bind, BindParam from databricks.bundles.registered_models._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/schemas/__init__.py b/python/databricks/bundles/schemas/__init__.py index 60ce398bd97..4ba569f4670 100644 --- a/python/databricks/bundles/schemas/__init__.py +++ b/python/databricks/bundles/schemas/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -23,6 +26,7 @@ ] +from databricks.bundles.schemas._models.bind import Bind, BindDict, BindParam from databricks.bundles.schemas._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/schemas/_models/bind.py b/python/databricks/bundles/schemas/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/schemas/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/schemas/_models/lifecycle.py b/python/databricks/bundles/schemas/_models/lifecycle.py index 644715de859..92cff06c17e 100644 --- a/python/databricks/bundles/schemas/_models/lifecycle.py +++ b/python/databricks/bundles/schemas/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.schemas._models.bind import Bind, BindParam from databricks.bundles.schemas._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/secret_scopes/__init__.py b/python/databricks/bundles/secret_scopes/__init__.py index d85f347bbba..9a6109b9375 100644 --- a/python/databricks/bundles/secret_scopes/__init__.py +++ b/python/databricks/bundles/secret_scopes/__init__.py @@ -4,6 +4,9 @@ "AzureKeyVaultSecretScopeMetadata", "AzureKeyVaultSecretScopeMetadataDict", "AzureKeyVaultSecretScopeMetadataParam", + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -28,6 +31,7 @@ AzureKeyVaultSecretScopeMetadataDict, AzureKeyVaultSecretScopeMetadataParam, ) +from databricks.bundles.secret_scopes._models.bind import Bind, BindDict, BindParam from databricks.bundles.secret_scopes._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/secret_scopes/_models/bind.py b/python/databricks/bundles/secret_scopes/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/secret_scopes/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/secret_scopes/_models/lifecycle.py b/python/databricks/bundles/secret_scopes/_models/lifecycle.py index 7f1b17140e0..bbda9f5aa4d 100644 --- a/python/databricks/bundles/secret_scopes/_models/lifecycle.py +++ b/python/databricks/bundles/secret_scopes/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.secret_scopes._models.bind import Bind, BindParam from databricks.bundles.secret_scopes._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/secrets/__init__.py b/python/databricks/bundles/secrets/__init__.py index 138d56de11b..5698fdad304 100644 --- a/python/databricks/bundles/secrets/__init__.py +++ b/python/databricks/bundles/secrets/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -18,6 +21,7 @@ ] +from databricks.bundles.secrets._models.bind import Bind, BindDict, BindParam from databricks.bundles.secrets._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/secrets/_models/bind.py b/python/databricks/bundles/secrets/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/secrets/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/secrets/_models/lifecycle.py b/python/databricks/bundles/secrets/_models/lifecycle.py index a7e1a79f1a7..5b9d137c32b 100644 --- a/python/databricks/bundles/secrets/_models/lifecycle.py +++ b/python/databricks/bundles/secrets/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.secrets._models.bind import Bind, BindParam from databricks.bundles.secrets._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/sql_warehouses/__init__.py b/python/databricks/bundles/sql_warehouses/__init__.py index 3f19eb82205..375f91f8b5d 100644 --- a/python/databricks/bundles/sql_warehouses/__init__.py +++ b/python/databricks/bundles/sql_warehouses/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "Channel", "ChannelDict", "ChannelName", @@ -33,6 +36,7 @@ ] +from databricks.bundles.sql_warehouses._models.bind import Bind, BindDict, BindParam from databricks.bundles.sql_warehouses._models.channel import ( Channel, ChannelDict, diff --git a/python/databricks/bundles/sql_warehouses/_models/bind.py b/python/databricks/bundles/sql_warehouses/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/sql_warehouses/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/sql_warehouses/_models/lifecycle_with_started.py b/python/databricks/bundles/sql_warehouses/_models/lifecycle_with_started.py index 54f0d97ae5a..eadf7002c22 100644 --- a/python/databricks/bundles/sql_warehouses/_models/lifecycle_with_started.py +++ b/python/databricks/bundles/sql_warehouses/_models/lifecycle_with_started.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.sql_warehouses._models.bind import Bind, BindParam from databricks.bundles.sql_warehouses._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class LifecycleWithStarted: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -45,6 +51,11 @@ def as_dict(self) -> "LifecycleWithStartedDict": class LifecycleWithStartedDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/synced_database_tables/__init__.py b/python/databricks/bundles/synced_database_tables/__init__.py index c626e16396c..dfa5563d2c0 100644 --- a/python/databricks/bundles/synced_database_tables/__init__.py +++ b/python/databricks/bundles/synced_database_tables/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -26,6 +29,11 @@ ] +from databricks.bundles.synced_database_tables._models.bind import ( + Bind, + BindDict, + BindParam, +) from databricks.bundles.synced_database_tables._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/synced_database_tables/_models/bind.py b/python/databricks/bundles/synced_database_tables/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/synced_database_tables/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/synced_database_tables/_models/lifecycle.py b/python/databricks/bundles/synced_database_tables/_models/lifecycle.py index 6145aa8ac96..4fe2e4da418 100644 --- a/python/databricks/bundles/synced_database_tables/_models/lifecycle.py +++ b/python/databricks/bundles/synced_database_tables/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.synced_database_tables._models.bind import Bind, BindParam from databricks.bundles.synced_database_tables._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/vector_search_endpoints/__init__.py b/python/databricks/bundles/vector_search_endpoints/__init__.py index ad58a72b996..c33e2dcd6af 100644 --- a/python/databricks/bundles/vector_search_endpoints/__init__.py +++ b/python/databricks/bundles/vector_search_endpoints/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -20,6 +23,11 @@ ] +from databricks.bundles.vector_search_endpoints._models.bind import ( + Bind, + BindDict, + BindParam, +) from databricks.bundles.vector_search_endpoints._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/vector_search_endpoints/_models/bind.py b/python/databricks/bundles/vector_search_endpoints/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/vector_search_endpoints/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/vector_search_endpoints/_models/lifecycle.py b/python/databricks/bundles/vector_search_endpoints/_models/lifecycle.py index 45f1d1427b3..ff32955adcb 100644 --- a/python/databricks/bundles/vector_search_endpoints/_models/lifecycle.py +++ b/python/databricks/bundles/vector_search_endpoints/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.vector_search_endpoints._models.bind import Bind, BindParam from databricks.bundles.vector_search_endpoints._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/vector_search_indexes/__init__.py b/python/databricks/bundles/vector_search_indexes/__init__.py index 8a23b46be42..bcf11f12f55 100644 --- a/python/databricks/bundles/vector_search_indexes/__init__.py +++ b/python/databricks/bundles/vector_search_indexes/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DeltaSyncVectorIndexSpecRequest", "DeltaSyncVectorIndexSpecRequestDict", "DeltaSyncVectorIndexSpecRequestParam", @@ -36,6 +39,11 @@ ] +from databricks.bundles.vector_search_indexes._models.bind import ( + Bind, + BindDict, + BindParam, +) from databricks.bundles.vector_search_indexes._models.delta_sync_vector_index_spec_request import ( DeltaSyncVectorIndexSpecRequest, DeltaSyncVectorIndexSpecRequestDict, diff --git a/python/databricks/bundles/vector_search_indexes/_models/bind.py b/python/databricks/bundles/vector_search_indexes/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/vector_search_indexes/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/vector_search_indexes/_models/lifecycle.py b/python/databricks/bundles/vector_search_indexes/_models/lifecycle.py index b03f75d8f6e..38d49d095d3 100644 --- a/python/databricks/bundles/vector_search_indexes/_models/lifecycle.py +++ b/python/databricks/bundles/vector_search_indexes/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.vector_search_indexes._models.bind import Bind, BindParam from databricks.bundles.vector_search_indexes._models.depends_on import ( DependsOn, DependsOnParam, @@ -19,6 +20,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -40,6 +46,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. diff --git a/python/databricks/bundles/volumes/__init__.py b/python/databricks/bundles/volumes/__init__.py index 3fdd871e124..3432bbb9a1f 100644 --- a/python/databricks/bundles/volumes/__init__.py +++ b/python/databricks/bundles/volumes/__init__.py @@ -1,6 +1,9 @@ # Code generated by pydabs-codegen. DO NOT EDIT. __all__ = [ + "Bind", + "BindDict", + "BindParam", "DependsOn", "DependsOnDict", "DependsOnParam", @@ -25,6 +28,7 @@ ] +from databricks.bundles.volumes._models.bind import Bind, BindDict, BindParam from databricks.bundles.volumes._models.depends_on import ( DependsOn, DependsOnDict, diff --git a/python/databricks/bundles/volumes/_models/bind.py b/python/databricks/bundles/volumes/_models/bind.py new file mode 100644 index 00000000000..35f09d15949 --- /dev/null +++ b/python/databricks/bundles/volumes/_models/bind.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Bind: + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + @classmethod + def from_dict(cls, value: "BindDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "BindDict": + return _transform_to_json_value(self) # type:ignore + + +class BindDict(TypedDict, total=False): + """""" + + id: VariableOr[str] + """ + The ID of the existing workspace resource, as used by its API (for example a job ID, or the full name of a schema or volume). Set it per target through a target override of the resource; variable references are resolved. + """ + + +BindParam = BindDict | Bind diff --git a/python/databricks/bundles/volumes/_models/lifecycle.py b/python/databricks/bundles/volumes/_models/lifecycle.py index db1737339ec..281e5b05ac8 100644 --- a/python/databricks/bundles/volumes/_models/lifecycle.py +++ b/python/databricks/bundles/volumes/_models/lifecycle.py @@ -6,6 +6,7 @@ from databricks.bundles.core._transform import _transform from databricks.bundles.core._transform_to_json import _transform_to_json_value from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.volumes._models.bind import Bind, BindParam from databricks.bundles.volumes._models.depends_on import DependsOn, DependsOnParam if TYPE_CHECKING: @@ -16,6 +17,11 @@ class Lifecycle: """""" + bind: VariableOrOptional[Bind] = None + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOn] = field(default_factory=list) """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering. @@ -37,6 +43,11 @@ def as_dict(self) -> "LifecycleDict": class LifecycleDict(TypedDict, total=False): """""" + bind: VariableOrOptional[BindParam] + """ + Existing workspace resource to adopt before deployment. Deploy first records its ID and configuration in remote state, then applies normal updates, including target settings such as `mode: development`. Binding remains recorded if deployment fails. While present, this block prevents creation, recreation, and ID changes; removing it does not unbind the resource. `bundle destroy` still deletes bound resources. No bind marker is stored in resource state. + """ + depends_on: VariableOrList[DependsOnParam] """ Resources that must be deployed before this resource and deleted after it. Unlike variable references, this only affects ordering.