diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.no_change.direct.json b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.no_change.direct.json index 5538371eb7..20fafce214 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.no_change.direct.json +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.no_change.direct.json @@ -42,7 +42,7 @@ }, "history_retention_duration": { "action": "skip", - "reason": "empty_struct", + "reason": "spec:input_only", "old": "604800s", "new": "604800s" }, diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.restore.direct.json b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.restore.direct.json index 4647dd0f7d..90645242f0 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.restore.direct.json +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.restore.direct.json @@ -54,7 +54,7 @@ }, "history_retention_duration": { "action": "skip", - "reason": "empty_struct", + "reason": "spec:input_only", "old": "604800s", "new": "604800s" }, diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.update.direct.json b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.update.direct.json index b9623f5d28..46c6217103 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.update.direct.json +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.update.direct.json @@ -54,7 +54,7 @@ }, "history_retention_duration": { "action": "skip", - "reason": "empty_struct", + "reason": "spec:input_only", "old": "604800s", "new": "604800s" }, diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/databricks.yml.tmpl b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/databricks.yml.tmpl new file mode 100644 index 0000000000..e0cbd89c50 --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/databricks.yml.tmpl @@ -0,0 +1,17 @@ +bundle: + name: update-suspend-timeout-$UNIQUE_NAME + +sync: + paths: [] + +resources: + postgres_projects: + my_project: + project_id: test-pg-proj-$UNIQUE_NAME + display_name: "My Project" + pg_version: 16 + history_retention_duration: 604800s + default_endpoint_settings: + autoscaling_limit_min_cu: 0.5 + autoscaling_limit_max_cu: 4 + suspend_timeout_duration: 300s diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.add_field.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.add_field.direct.json new file mode 100644 index 0000000000..d493e3d71f --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.add_field.direct.json @@ -0,0 +1,73 @@ +{ + "action": "update", + "new_state": { + "value": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "120s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]" + } + }, + "remote_state": { + "create_time": "[TIMESTAMP]", + "name": "projects/test-pg-proj-[UNIQUE_NAME]", + "status": { + "branch_logical_size_limit_bytes": [NUMID], + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "600s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "synthetic_storage_size_bytes": 0 + }, + "uid": "[UUID]", + "update_time": "[TIMESTAMP]" + }, + "changes": { + "default_endpoint_settings": { + "action": "skip", + "reason": "spec:input_only", + "old": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "120s" + }, + "new": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "120s" + } + }, + "default_endpoint_settings.suspend_timeout_duration": { + "action": "update", + "new": "120s" + }, + "display_name": { + "action": "skip", + "reason": "spec:input_only", + "old": "My Project", + "new": "My Project" + }, + "history_retention_duration": { + "action": "skip", + "reason": "spec:input_only", + "old": "604800s", + "new": "604800s" + }, + "pg_version": { + "action": "skip", + "reason": "spec:input_only", + "old": 16, + "new": 16 + } + } +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.create.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.create.direct.json new file mode 100644 index 0000000000..3c1429d814 --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.create.direct.json @@ -0,0 +1,16 @@ +{ + "action": "create", + "new_state": { + "value": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]" + } + } +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.no_change.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.no_change.direct.json new file mode 100644 index 0000000000..80efdcb94a --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.no_change.direct.json @@ -0,0 +1,56 @@ +{ + "action": "skip", + "remote_state": { + "create_time": "[TIMESTAMP]", + "name": "projects/test-pg-proj-[UNIQUE_NAME]", + "status": { + "branch_logical_size_limit_bytes": [NUMID], + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "synthetic_storage_size_bytes": 0 + }, + "uid": "[UUID]", + "update_time": "[TIMESTAMP]" + }, + "changes": { + "default_endpoint_settings": { + "action": "skip", + "reason": "spec:input_only", + "old": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "new": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + } + }, + "display_name": { + "action": "skip", + "reason": "spec:input_only", + "old": "My Project", + "new": "My Project" + }, + "history_retention_duration": { + "action": "skip", + "reason": "spec:input_only", + "old": "604800s", + "new": "604800s" + }, + "pg_version": { + "action": "skip", + "reason": "spec:input_only", + "old": 16, + "new": 16 + } + } +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.remove_field.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.remove_field.direct.json new file mode 100644 index 0000000000..a1348a7aaa --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.remove_field.direct.json @@ -0,0 +1,70 @@ +{ + "action": "update", + "new_state": { + "value": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5 + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]" + } + }, + "remote_state": { + "create_time": "[TIMESTAMP]", + "name": "projects/test-pg-proj-[UNIQUE_NAME]", + "status": { + "branch_logical_size_limit_bytes": [NUMID], + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "600s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "synthetic_storage_size_bytes": 0 + }, + "uid": "[UUID]", + "update_time": "[TIMESTAMP]" + }, + "changes": { + "default_endpoint_settings": { + "action": "skip", + "reason": "spec:input_only", + "old": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5 + }, + "new": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5 + } + }, + "default_endpoint_settings.suspend_timeout_duration": { + "action": "update", + "old": "600s" + }, + "display_name": { + "action": "skip", + "reason": "spec:input_only", + "old": "My Project", + "new": "My Project" + }, + "history_retention_duration": { + "action": "skip", + "reason": "spec:input_only", + "old": "604800s", + "new": "604800s" + }, + "pg_version": { + "action": "skip", + "reason": "spec:input_only", + "old": 16, + "new": 16 + } + } +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.update_different.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.update_different.direct.json new file mode 100644 index 0000000000..a614013b47 --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.plan.update_different.direct.json @@ -0,0 +1,74 @@ +{ + "action": "update", + "new_state": { + "value": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "600s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]" + } + }, + "remote_state": { + "create_time": "[TIMESTAMP]", + "name": "projects/test-pg-proj-[UNIQUE_NAME]", + "status": { + "branch_logical_size_limit_bytes": [NUMID], + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "synthetic_storage_size_bytes": 0 + }, + "uid": "[UUID]", + "update_time": "[TIMESTAMP]" + }, + "changes": { + "default_endpoint_settings": { + "action": "skip", + "reason": "spec:input_only", + "old": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "600s" + }, + "new": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "600s" + } + }, + "default_endpoint_settings.suspend_timeout_duration": { + "action": "update", + "old": "300s", + "new": "600s" + }, + "display_name": { + "action": "skip", + "reason": "spec:input_only", + "old": "My Project", + "new": "My Project" + }, + "history_retention_duration": { + "action": "skip", + "reason": "spec:input_only", + "old": "604800s", + "new": "604800s" + }, + "pg_version": { + "action": "skip", + "reason": "spec:input_only", + "old": 16, + "new": 16 + } + } +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.add_field.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.add_field.direct.json new file mode 100644 index 0000000000..02f36e7d2e --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.add_field.direct.json @@ -0,0 +1,23 @@ +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} +{ + "method": "PATCH", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]", + "q": { + "update_mask": "spec" + }, + "body": { + "spec": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "120s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.create.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.create.direct.json new file mode 100644 index 0000000000..008b5a689d --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.create.direct.json @@ -0,0 +1,19 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.no_change.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.no_change.direct.json new file mode 100644 index 0000000000..0d383894bd --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.no_change.direct.json @@ -0,0 +1,4 @@ +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.remove_field.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.remove_field.direct.json new file mode 100644 index 0000000000..dc842cfb89 --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.remove_field.direct.json @@ -0,0 +1,22 @@ +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} +{ + "method": "PATCH", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]", + "q": { + "update_mask": "spec" + }, + "body": { + "spec": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5 + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.update_different.direct.json b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.update_different.direct.json new file mode 100644 index 0000000000..7f2927d67e --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.requests.update_different.direct.json @@ -0,0 +1,23 @@ +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} +{ + "method": "PATCH", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]", + "q": { + "update_mask": "spec" + }, + "body": { + "spec": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "600s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.test.toml b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.test.toml new file mode 100644 index 0000000000..f3d5b03e2e --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/out.test.toml @@ -0,0 +1,10 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true + +[CloudEnvs] + azure = false + gcp = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/output.txt b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/output.txt new file mode 100644 index 0000000000..21ce7837e6 --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/output.txt @@ -0,0 +1,233 @@ + +=== Initial deployment with suspend_timeout_duration: 300s +>>> [CLI] bundle validate +Name: update-suspend-timeout-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/update-suspend-timeout-[UNIQUE_NAME]/default + +Validation OK! + +>>> [CLI] bundle plan +create postgres_projects.my_project + +Plan: 1 to add, 0 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle plan -o json + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-suspend-timeout-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ + +>>> [CLI] postgres get-project projects/test-pg-proj-[UNIQUE_NAME] +{ + "name": "projects/test-pg-proj-[UNIQUE_NAME]", + "status": { + "branch_logical_size_limit_bytes": [NUMID], + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "synthetic_storage_size_bytes": 0 + }, + "uid": "[UUID]" +} + +=== Verify no changes +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged + +>>> [CLI] bundle plan -o json + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-suspend-timeout-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ + +=== Update to different value: 300s -> 600s +>>> update_file.py databricks.yml suspend_timeout_duration: 300s suspend_timeout_duration: 600s + +>>> cat databricks.yml +bundle: + name: update-suspend-timeout-[UNIQUE_NAME] + +sync: + paths: [] + +resources: + postgres_projects: + my_project: + project_id: test-pg-proj-[UNIQUE_NAME] + display_name: "My Project" + pg_version: 16 + history_retention_duration: 604800s + default_endpoint_settings: + autoscaling_limit_min_cu: 0.5 + autoscaling_limit_max_cu: 4 + suspend_timeout_duration: 600s + +>>> [CLI] bundle plan +update postgres_projects.my_project + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle plan -o json + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-suspend-timeout-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ + +>>> [CLI] postgres get-project projects/test-pg-proj-[UNIQUE_NAME] +{ + "name": "projects/test-pg-proj-[UNIQUE_NAME]", + "status": { + "branch_logical_size_limit_bytes": [NUMID], + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "600s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "synthetic_storage_size_bytes": 0 + }, + "uid": "[UUID]" +} + +=== Remove suspend_timeout_duration field completely +>>> cat databricks.yml +bundle: + name: update-suspend-timeout-[UNIQUE_NAME] + +sync: + paths: [] + +resources: + postgres_projects: + my_project: + project_id: test-pg-proj-[UNIQUE_NAME] + display_name: "My Project" + pg_version: 16 + history_retention_duration: 604800s + default_endpoint_settings: + autoscaling_limit_min_cu: 0.5 + autoscaling_limit_max_cu: 4 + +>>> [CLI] bundle plan +update postgres_projects.my_project + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle plan -o json + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-suspend-timeout-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ + +>>> [CLI] postgres get-project projects/test-pg-proj-[UNIQUE_NAME] +{ + "name": "projects/test-pg-proj-[UNIQUE_NAME]", + "status": { + "branch_logical_size_limit_bytes": [NUMID], + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "600s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "synthetic_storage_size_bytes": 0 + }, + "uid": "[UUID]" +} + +=== Add suspend_timeout_duration field back: add 120s +>>> update_file.py databricks.yml autoscaling_limit_max_cu: 4 autoscaling_limit_max_cu: 4 + suspend_timeout_duration: 120s + +>>> cat databricks.yml +bundle: + name: update-suspend-timeout-[UNIQUE_NAME] + +sync: + paths: [] + +resources: + postgres_projects: + my_project: + project_id: test-pg-proj-[UNIQUE_NAME] + display_name: "My Project" + pg_version: 16 + history_retention_duration: 604800s + default_endpoint_settings: + autoscaling_limit_min_cu: 0.5 + autoscaling_limit_max_cu: 4 + suspend_timeout_duration: 120s + +>>> [CLI] bundle plan +update postgres_projects.my_project + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle plan -o json + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-suspend-timeout-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ + +>>> [CLI] postgres get-project projects/test-pg-proj-[UNIQUE_NAME] +{ + "name": "projects/test-pg-proj-[UNIQUE_NAME]", + "status": { + "branch_logical_size_limit_bytes": [NUMID], + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "120s" + }, + "display_name": "My Project", + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "synthetic_storage_size_bytes": 0 + }, + "uid": "[UUID]" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.postgres_projects.my_project + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/update-suspend-timeout-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/script b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/script new file mode 100755 index 0000000000..e7b4277844 --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/script @@ -0,0 +1,93 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve +} +trap cleanup EXIT + +print_requests() { + local name=$1 + trace print_requests.py --keep --get '//postgres' '^//workspace-files/' '^//workspace/' '^//telemetry-ext' '^//operations/' > out.requests.${name}.$DATABRICKS_BUNDLE_ENGINE.json + rm -f out.requests.txt +} + +title "Initial deployment with suspend_timeout_duration: 300s" +trace $CLI bundle validate +trace $CLI bundle plan +trace $CLI bundle plan -o json | jq '.plan."resources.postgres_projects.my_project" // .' > out.plan.create.$DATABRICKS_BUNDLE_ENGINE.json +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests create + +project_name="projects/test-pg-proj-${UNIQUE_NAME}" +trace $CLI postgres get-project "${project_name}" | jq 'del(.create_time, .update_time)' + +title "Verify no changes" +trace $CLI bundle plan +trace $CLI bundle plan -o json | jq '.plan."resources.postgres_projects.my_project" // .' > out.plan.no_change.$DATABRICKS_BUNDLE_ENGINE.json +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests no_change + +title "Update to different value: 300s -> 600s" +trace update_file.py databricks.yml "suspend_timeout_duration: 300s" "suspend_timeout_duration: 600s" +trace cat databricks.yml + +trace $CLI bundle plan +trace $CLI bundle plan -o json | jq '.plan."resources.postgres_projects.my_project" // .' > out.plan.update_different.$DATABRICKS_BUNDLE_ENGINE.json +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests update_different + +trace $CLI postgres get-project "${project_name}" | jq 'del(.create_time, .update_time)' + +title "Remove suspend_timeout_duration field completely" +# Note: Removing a field from config sends an update, but the Postgres API's PATCH semantics +# don't unset fields that are absent from the request body - they remain at their previous value. +# So the field will still show 600s after this step, not revert to a default. +# Write a new databricks.yml without the suspend_timeout_duration field +cat > databricks.yml < out.plan.remove_field.$DATABRICKS_BUNDLE_ENGINE.json +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests remove_field + +trace $CLI postgres get-project "${project_name}" | jq 'del(.create_time, .update_time)' + +title "Add suspend_timeout_duration field back: add 120s" +trace update_file.py databricks.yml "autoscaling_limit_max_cu: 4" "autoscaling_limit_max_cu: 4 + suspend_timeout_duration: 120s" +trace cat databricks.yml + +trace $CLI bundle plan +trace $CLI bundle plan -o json | jq '.plan."resources.postgres_projects.my_project" // .' > out.plan.add_field.$DATABRICKS_BUNDLE_ENGINE.json +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests add_field + +trace $CLI postgres get-project "${project_name}" | jq 'del(.create_time, .update_time)' diff --git a/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/test.toml b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/test.toml new file mode 100644 index 0000000000..d78e52e195 --- /dev/null +++ b/acceptance/bundle/resources/postgres_projects/update_suspend_timeout_duration/test.toml @@ -0,0 +1,6 @@ +# All configuration inherited from parent test.toml + +# Only run with direct engine - terraform produces different plan output for suspend_timeout_duration +# because direct engine respects ignore_remote_changes rules while terraform doesn't +[EnvMatrix] +DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index b3d0674537..950c42fc65 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -334,15 +334,11 @@ func prepareChanges(ctx context.Context, adapter *dresources.Adapter, localDiff, for _, ch := range remoteDiff { entry := m[ch.Path.String()] if entry == nil { - // we have difference for remoteState but not difference for localState - // from remoteDiff we can find out remote value (ch.Old) and new config value (ch.New) but we don't know oldState value - oldStateVal, err := structaccess.Get(oldState, ch.Path) - var notFound *structaccess.NotFoundError - if err != nil && !errors.As(err, ¬Found) { - log.Debugf(ctx, "Constructing diff: accessing %q on %T: %s", ch.Path, oldState, err) - } + // We have a difference for remoteState but not for localState. + // No local diff means oldState == newConfig for this field, + // so ch.New (from newConfig) is also the oldState value. m[ch.Path.String()] = &deployplan.ChangeDesc{ - Old: oldStateVal, + Old: ch.New, New: ch.New, Remote: ch.Old, } @@ -370,7 +366,12 @@ func addPerFieldActions(ctx context.Context, adapter *dresources.Adapter, change return err } - if structdiff.IsEqual(ch.Remote, ch.New) { + if ch.Old != nil && ch.New == nil && ch.Remote == nil { + // Explicitly removing a field that we previously deployed. + // Even though remote is unknown (parent unavailable from API), + // we should send the update to unset it. + ch.Action = deployplan.Update + } else if structdiff.IsEqual(ch.Remote, ch.New) { ch.Action = deployplan.Skip ch.Reason = deployplan.ReasonRemoteAlreadySet } else if isEmptySlice(ch.Old, ch.New, ch.Remote) { diff --git a/bundle/direct/dresources/postgres_branch.go b/bundle/direct/dresources/postgres_branch.go index 2e79eb5dda..a3418e55fa 100644 --- a/bundle/direct/dresources/postgres_branch.go +++ b/bundle/direct/dresources/postgres_branch.go @@ -87,12 +87,10 @@ func (r *ResourcePostgresBranch) DoCreate(ctx context.Context, config *PostgresB } func (r *ResourcePostgresBranch) DoUpdate(ctx context.Context, id string, config *PostgresBranchState, changes Changes) (*postgres.Branch, error) { - // Build update mask from fields that have action="update" in the changes map. - // This excludes immutable fields and fields that haven't changed. - // Prefix with "spec." because the API expects paths relative to the Branch object, - // not relative to our flattened state type. - fieldPaths := collectUpdatePathsWithPrefix(changes, "spec.") - + // The Postgres API requires that fields in the update_mask must be present in the request body. + // When a field is removed from config (nil in Go), it doesn't appear in the JSON payload, + // but would appear in a granular update_mask, causing the backend to reject the request. + // To avoid this, we use "spec" (which is always present) instead of listing individual fields. waiter, err := r.client.Postgres.UpdateBranch(ctx, postgres.UpdateBranchRequest{ Branch: postgres.Branch{ Spec: &config.BranchSpec, @@ -108,7 +106,7 @@ func (r *ResourcePostgresBranch) DoUpdate(ctx context.Context, id string, config }, Name: id, UpdateMask: fieldmask.FieldMask{ - Paths: fieldPaths, + Paths: []string{"spec"}, }, }) if err != nil { diff --git a/bundle/direct/dresources/postgres_endpoint.go b/bundle/direct/dresources/postgres_endpoint.go index 5b154fbc93..d1308ce74a 100644 --- a/bundle/direct/dresources/postgres_endpoint.go +++ b/bundle/direct/dresources/postgres_endpoint.go @@ -131,12 +131,10 @@ func (r *ResourcePostgresEndpoint) DoCreate(ctx context.Context, config *Postgre } func (r *ResourcePostgresEndpoint) DoUpdate(ctx context.Context, id string, config *PostgresEndpointState, changes Changes) (*postgres.Endpoint, error) { - // Build update mask from fields that have action="update" in the changes map. - // This excludes immutable fields and fields that haven't changed. - // Prefix with "spec." because the API expects paths relative to the Endpoint object, - // not relative to our flattened state type. - fieldPaths := collectUpdatePathsWithPrefix(changes, "spec.") - + // The Postgres API requires that fields in the update_mask must be present in the request body. + // When a field is removed from config (nil in Go), it doesn't appear in the JSON payload, + // but would appear in a granular update_mask, causing the backend to reject the request. + // To avoid this, we use "spec" (which is always present) instead of listing individual fields. waiter, err := r.client.Postgres.UpdateEndpoint(ctx, postgres.UpdateEndpointRequest{ Endpoint: postgres.Endpoint{ Spec: &config.EndpointSpec, @@ -152,7 +150,7 @@ func (r *ResourcePostgresEndpoint) DoUpdate(ctx context.Context, id string, conf }, Name: id, UpdateMask: fieldmask.FieldMask{ - Paths: fieldPaths, + Paths: []string{"spec"}, }, }) if err != nil { diff --git a/bundle/direct/dresources/postgres_project.go b/bundle/direct/dresources/postgres_project.go index e08ba9e603..2f5cf31831 100644 --- a/bundle/direct/dresources/postgres_project.go +++ b/bundle/direct/dresources/postgres_project.go @@ -80,12 +80,10 @@ func (r *ResourcePostgresProject) DoCreate(ctx context.Context, config *Postgres } func (r *ResourcePostgresProject) DoUpdate(ctx context.Context, id string, config *PostgresProjectState, changes Changes) (*postgres.Project, error) { - // Build update mask from fields that have action="update" in the changes map. - // This excludes immutable fields and fields that haven't changed. - // Prefix with "spec." because the API expects paths relative to the Project object, - // not relative to our flattened state type. - fieldPaths := collectUpdatePathsWithPrefix(changes, "spec.") - + // The Postgres API requires that fields in the update_mask must be present in the request body. + // When a field is removed from config (nil in Go), it doesn't appear in the JSON payload, + // but would appear in a granular update_mask, causing the backend to reject the request. + // To avoid this, we use "spec" (which is always present) instead of listing individual fields. waiter, err := r.client.Postgres.UpdateProject(ctx, postgres.UpdateProjectRequest{ Project: postgres.Project{ Spec: &config.ProjectSpec, @@ -100,7 +98,7 @@ func (r *ResourcePostgresProject) DoUpdate(ctx context.Context, id string, confi }, Name: id, UpdateMask: fieldmask.FieldMask{ - Paths: fieldPaths, + Paths: []string{"spec"}, }, }) if err != nil { diff --git a/bundle/direct/dresources/util.go b/bundle/direct/dresources/util.go index 5a7671e8bc..6ebc15c983 100644 --- a/bundle/direct/dresources/util.go +++ b/bundle/direct/dresources/util.go @@ -4,7 +4,6 @@ import ( "fmt" "regexp" - "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/databricks-sdk-go/retries" ) @@ -48,16 +47,3 @@ func shouldRetry(err error) bool { } return !e.Halt } - -// collectUpdatePathsWithPrefix extracts field paths from Changes that have action=Update, -// adding a prefix to each path. This is used when the state type has a flattened structure -// but the API expects paths relative to a nested object (e.g., "spec.display_name"). -func collectUpdatePathsWithPrefix(changes Changes, prefix string) []string { - var paths []string - for path, change := range changes { - if change.Action == deployplan.Update { - paths = append(paths, prefix+path) - } - } - return paths -} diff --git a/libs/structs/structdiff/diff.go b/libs/structs/structdiff/diff.go index 886f9c1f83..7161cc98dd 100644 --- a/libs/structs/structdiff/diff.go +++ b/libs/structs/structdiff/diff.go @@ -108,11 +108,19 @@ func diffValues(ctx *diffContext, path *structpath.PathNode, v1, v2 reflect.Valu return nil } - *changes = append(*changes, Change{Path: path, Old: nil, New: v2.Interface()}) + new, err := toChangeValue(v2) + if err != nil { + return err + } + *changes = append(*changes, Change{Path: path, Old: nil, New: new}) return nil } else if !v2.IsValid() { // v1 is valid - *changes = append(*changes, Change{Path: path, Old: v1.Interface(), New: nil}) + old, err := toChangeValue(v1) + if err != nil { + return err + } + *changes = append(*changes, Change{Path: path, Old: old, New: nil}) return nil } @@ -126,6 +134,25 @@ func diffValues(ctx *diffContext, path *structpath.PathNode, v1, v2 reflect.Valu kind := v1.Kind() + // Handle SDK native types by comparing their string representation. + // SDK types like duration.Duration have only unexported fields, so we + // compare them using their JSON-marshaled string form (e.g., "300s"). + if isSDKNativeType(v1Type) { + str1, err := marshalSDKNative(v1) + if err != nil { + return err + } + str2, err := marshalSDKNative(v2) + if err != nil { + return err + } + if str1 != str2 { + // Store string representation in Change, not the struct + *changes = append(*changes, Change{Path: path, Old: str1, New: str2}) + } + return nil + } + // Perform nil checks for nilable types. switch kind { case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Interface, reflect.Chan, reflect.Func: @@ -135,7 +162,15 @@ func diffValues(ctx *diffContext, path *structpath.PathNode, v1, v2 reflect.Valu return nil } if v1Nil || v2Nil { - *changes = append(*changes, Change{Path: path, Old: v1.Interface(), New: v2.Interface()}) + old, err := toChangeValue(v1) + if err != nil { + return err + } + new, err := toChangeValue(v2) + if err != nil { + return err + } + *changes = append(*changes, Change{Path: path, Old: old, New: new}) return nil } default: @@ -179,6 +214,26 @@ func deepEqualValues(path *structpath.PathNode, v1, v2 reflect.Value, changes *[ } } +// toChangeValue converts a reflect.Value to a value suitable for Change.Old/New. +// For SDK native types (including pointers), it marshals to string representation. +// For other types, it returns the interface value as-is. +func toChangeValue(v reflect.Value) (any, error) { + if !v.IsValid() { + return nil, nil + } + t := v.Type() + if isSDKNativeType(t) { + return marshalSDKNative(v) + } + if t.Kind() == reflect.Pointer && isSDKNativeType(t.Elem()) { + if v.IsNil() { + return nil, nil + } + return marshalSDKNative(v.Elem()) + } + return v.Interface(), nil +} + func diffStruct(ctx *diffContext, path *structpath.PathNode, s1, s2 reflect.Value, changes *[]Change) error { t := s1.Type() forced1 := getForceSendFields(s1) diff --git a/libs/structs/structdiff/equal.go b/libs/structs/structdiff/equal.go index 7d4ec84a65..8fa0ed739e 100644 --- a/libs/structs/structdiff/equal.go +++ b/libs/structs/structdiff/equal.go @@ -48,6 +48,13 @@ func equalValues(v1, v2 reflect.Value) bool { kind := v1.Kind() + // Handle SDK native types by comparing their string representation. + if isSDKNativeType(v1Type) { + str1, _ := marshalSDKNative(v1) + str2, _ := marshalSDKNative(v2) + return str1 == str2 + } + // Perform nil checks for nilable types. switch kind { case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Interface, reflect.Chan, reflect.Func: diff --git a/libs/structs/structdiff/sdk_native_types.go b/libs/structs/structdiff/sdk_native_types.go new file mode 100644 index 0000000000..1276a84e56 --- /dev/null +++ b/libs/structs/structdiff/sdk_native_types.go @@ -0,0 +1,37 @@ +package structdiff + +import ( + "encoding/json" + "reflect" + "slices" + + sdkduration "github.com/databricks/databricks-sdk-go/common/types/duration" + sdkfieldmask "github.com/databricks/databricks-sdk-go/common/types/fieldmask" + sdktime "github.com/databricks/databricks-sdk-go/common/types/time" +) + +// sdkNativeTypes is a list of SDK native types that use custom JSON marshaling +// and should be compared using their string representation. +var sdkNativeTypes = []reflect.Type{ + reflect.TypeFor[sdkduration.Duration](), + reflect.TypeFor[sdktime.Time](), + reflect.TypeFor[sdkfieldmask.FieldMask](), +} + +// isSDKNativeType returns true if t is an SDK native type. +func isSDKNativeType(t reflect.Type) bool { + return slices.Contains(sdkNativeTypes, t) +} + +// marshalSDKNative converts an SDK native type to its string representation. +func marshalSDKNative(v reflect.Value) (string, error) { + jsonBytes, err := json.Marshal(v.Interface()) + if err != nil { + return "", err + } + var str string + if err := json.Unmarshal(jsonBytes, &str); err != nil { + return "", err + } + return str, nil +} diff --git a/libs/structs/structdiff/sdk_native_types_test.go b/libs/structs/structdiff/sdk_native_types_test.go new file mode 100644 index 0000000000..f77c5a42a9 --- /dev/null +++ b/libs/structs/structdiff/sdk_native_types_test.go @@ -0,0 +1,196 @@ +package structdiff_test + +import ( + "testing" + "time" + + "github.com/databricks/cli/libs/structs/structdiff" + sdkduration "github.com/databricks/databricks-sdk-go/common/types/duration" + sdkfieldmask "github.com/databricks/databricks-sdk-go/common/types/fieldmask" + sdktime "github.com/databricks/databricks-sdk-go/common/types/time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type structWithDuration struct { + Name string + Timeout *sdkduration.Duration `json:"timeout,omitempty"` +} + +type structWithTime struct { + Name string + CreatedAt *sdktime.Time `json:"created_at,omitempty"` +} + +type structWithFieldMask struct { + Name string + Paths *sdkfieldmask.FieldMask `json:"paths,omitempty"` +} + +func TestGetStructDiffSDKNativeTypes(t *testing.T) { + dur5m := sdkduration.New(5 * time.Minute) + dur10m := sdkduration.New(10 * time.Minute) + dur2m := sdkduration.New(2 * time.Minute) + dur120s := sdkduration.New(120 * time.Second) + + ts1 := sdktime.New(time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)) + ts2 := sdktime.New(time.Date(2024, 1, 16, 10, 30, 0, 0, time.UTC)) + + fm1 := sdkfieldmask.New([]string{"field1"}) + fm2 := sdkfieldmask.New([]string{"field1", "field2"}) + + tests := []struct { + name string + a, b any + wantChanges int + wantPath string + wantOld any + wantNew any + }{ + // Duration + { + name: "duration/same_value", + a: structWithDuration{Timeout: dur5m}, + b: structWithDuration{Timeout: dur5m}, + wantChanges: 0, + }, + { + name: "duration/different_value", + a: structWithDuration{Timeout: dur5m}, + b: structWithDuration{Timeout: dur10m}, + wantChanges: 1, + wantPath: "timeout", + wantOld: "300s", + wantNew: "600s", + }, + { + name: "duration/equivalent_values", + a: structWithDuration{Timeout: dur2m}, + b: structWithDuration{Timeout: dur120s}, + wantChanges: 0, + }, + { + name: "duration/add_field", + a: structWithDuration{Timeout: nil}, + b: structWithDuration{Timeout: dur5m}, + wantChanges: 1, + wantPath: "timeout", + wantOld: nil, + wantNew: "300s", + }, + { + name: "duration/remove_field", + a: structWithDuration{Timeout: dur5m}, + b: structWithDuration{Timeout: nil}, + wantChanges: 1, + wantPath: "timeout", + wantOld: "300s", + wantNew: nil, + }, + { + name: "duration/both_nil", + a: structWithDuration{Timeout: nil}, + b: structWithDuration{Timeout: nil}, + wantChanges: 0, + }, + + // Time + { + name: "time/same_value", + a: structWithTime{CreatedAt: ts1}, + b: structWithTime{CreatedAt: ts1}, + wantChanges: 0, + }, + { + name: "time/different_value", + a: structWithTime{CreatedAt: ts1}, + b: structWithTime{CreatedAt: ts2}, + wantChanges: 1, + wantPath: "created_at", + wantOld: "2024-01-15T10:30:00Z", + wantNew: "2024-01-16T10:30:00Z", + }, + + // FieldMask + { + name: "fieldmask/same_value", + a: structWithFieldMask{Paths: fm2}, + b: structWithFieldMask{Paths: fm2}, + wantChanges: 0, + }, + { + name: "fieldmask/different_value", + a: structWithFieldMask{Paths: fm1}, + b: structWithFieldMask{Paths: fm2}, + wantChanges: 1, + wantPath: "paths", + wantOld: "field1", + wantNew: "field1,field2", + }, + { + name: "fieldmask/add_field", + a: structWithFieldMask{Paths: nil}, + b: structWithFieldMask{Paths: fm2}, + wantChanges: 1, + wantPath: "paths", + wantOld: nil, + wantNew: "field1,field2", + }, + { + name: "fieldmask/remove_field", + a: structWithFieldMask{Paths: fm2}, + b: structWithFieldMask{Paths: nil}, + wantChanges: 1, + wantPath: "paths", + wantOld: "field1,field2", + wantNew: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + changes, err := structdiff.GetStructDiff(tt.a, tt.b, nil) + require.NoError(t, err) + require.Len(t, changes, tt.wantChanges) + + if tt.wantChanges > 0 { + assert.Equal(t, tt.wantPath, changes[0].Path.String()) + assert.Equal(t, tt.wantOld, changes[0].Old) + assert.Equal(t, tt.wantNew, changes[0].New) + } + }) + } +} + +func TestIsEqualSDKNativeTypes(t *testing.T) { + dur5m := sdkduration.New(5 * time.Minute) + dur10m := sdkduration.New(10 * time.Minute) + dur2m := sdkduration.New(2 * time.Minute) + dur120s := sdkduration.New(120 * time.Second) + + ts1 := sdktime.New(time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)) + ts2 := sdktime.New(time.Date(2024, 1, 16, 10, 30, 0, 0, time.UTC)) + + fm1 := sdkfieldmask.New([]string{"field1"}) + fm2 := sdkfieldmask.New([]string{"field1", "field2"}) + + tests := []struct { + name string + a, b any + want bool + }{ + {"duration/same_value", structWithDuration{Timeout: dur5m}, structWithDuration{Timeout: dur5m}, true}, + {"duration/different_value", structWithDuration{Timeout: dur5m}, structWithDuration{Timeout: dur10m}, false}, + {"duration/equivalent_values", structWithDuration{Timeout: dur2m}, structWithDuration{Timeout: dur120s}, true}, + {"time/same_value", structWithTime{CreatedAt: ts1}, structWithTime{CreatedAt: ts1}, true}, + {"time/different_value", structWithTime{CreatedAt: ts1}, structWithTime{CreatedAt: ts2}, false}, + {"fieldmask/same_value", structWithFieldMask{Paths: fm2}, structWithFieldMask{Paths: fm2}, true}, + {"fieldmask/different_value", structWithFieldMask{Paths: fm1}, structWithFieldMask{Paths: fm2}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, structdiff.IsEqual(tt.a, tt.b)) + }) + } +} diff --git a/libs/testserver/postgres.go b/libs/testserver/postgres.go index 3ff865d4d9..9b9550d4f6 100644 --- a/libs/testserver/postgres.go +++ b/libs/testserver/postgres.go @@ -170,6 +170,9 @@ func (s *FakeWorkspace) PostgresProjectUpdate(req Request, name string) Response if updateProject.Spec.DefaultEndpointSettings.AutoscalingLimitMaxCu != 0 { project.Status.DefaultEndpointSettings.AutoscalingLimitMaxCu = updateProject.Spec.DefaultEndpointSettings.AutoscalingLimitMaxCu } + if updateProject.Spec.DefaultEndpointSettings.SuspendTimeoutDuration != nil { + project.Status.DefaultEndpointSettings.SuspendTimeoutDuration = updateProject.Spec.DefaultEndpointSettings.SuspendTimeoutDuration + } } }