From 37fad5f79621b6101660bf9a4b36a71a58f73279 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 11:09:46 +0200 Subject: [PATCH 001/118] acc: show that recreate leaves permissions/grants drifted forever Recreating a resource re-keys the parent but not its permissions/grants child: the child's state entry keeps the old id, DoRead echoes it back, and the planner re-proposes the same update on every deploy. The ACL itself is applied to the recreated object, so this is stale bookkeeping rather than lost access, and terraform converges -- hence the per-engine plan files. Co-authored-by: Isaac --- .../out.plan_after_recreate.direct.txt | 5 +++++ .../out.plan_after_recreate.terraform.txt | 3 +++ .../resources/volumes/recreate/output.txt | 14 ++++++++++++++ .../bundle/resources/volumes/recreate/script | 18 ++++++++++++++++++ .../resources/volumes/recreate/test.toml | 2 ++ 5 files changed, 42 insertions(+) create mode 100644 acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.direct.txt create mode 100644 acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.terraform.txt diff --git a/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.direct.txt b/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.direct.txt new file mode 100644 index 00000000000..c35ba2907ae --- /dev/null +++ b/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.direct.txt @@ -0,0 +1,5 @@ + +>>> [CLI] bundle plan --var=schema_name=${resources.schemas.schema2.name} +update volumes.foo.grants + +Plan: 0 to add, 1 to change, 0 to delete, 3 unchanged diff --git a/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.terraform.txt b/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.terraform.txt new file mode 100644 index 00000000000..6c0e42d66da --- /dev/null +++ b/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.terraform.txt @@ -0,0 +1,3 @@ + +>>> [CLI] bundle plan --var=schema_name=${resources.schemas.schema2.name} +Plan: 0 to add, 0 to change, 0 to delete, 4 unchanged diff --git a/acceptance/bundle/resources/volumes/recreate/output.txt b/acceptance/bundle/resources/volumes/recreate/output.txt index fc94847412b..bb9a50e6c79 100644 --- a/acceptance/bundle/resources/volumes/recreate/output.txt +++ b/acceptance/bundle/resources/volumes/recreate/output.txt @@ -58,6 +58,20 @@ cloud tenant within 30 days. For external volumes, the metadata about the volume is removed from the catalog, but the underlying files are not deleted: recreate resources.volumes.foo +=== The grants landed on the recreated volume +>>> [CLI] grants get VOLUME main.schema2-[UNIQUE_NAME].my_volume +{ + "privilege_assignments": [ + { + "principal": "account users", + "privileges": [ + "WRITE_VOLUME" + ] + } + ] +} + +=== Plan after recreate >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: delete resources.schemas.schema1 diff --git a/acceptance/bundle/resources/volumes/recreate/script b/acceptance/bundle/resources/volumes/recreate/script index 5e1b2e779ee..544e50c59a3 100644 --- a/acceptance/bundle/resources/volumes/recreate/script +++ b/acceptance/bundle/resources/volumes/recreate/script @@ -23,3 +23,21 @@ trace errcode $CLI bundle deploy --var=schema_name='${resources.schemas.schema2. title "Successfully recreate the volume with --auto-approve" trace $CLI bundle deploy --var=schema_name='${resources.schemas.schema2.name}' --auto-approve -qq + +title "The grants landed on the recreated volume" +NEW_VOLUME_FULL_NAME="${CATALOG_NAME}.schema2-${UNIQUE_NAME}.${VOLUME_NAME}" +trace $CLI grants get VOLUME "${NEW_VOLUME_FULL_NAME}" + +# The engines diverge here, so the plan goes to a per-engine file. Terraform converges. +# +# BUG (direct engine): the plan is not clean. Recreating the volume gave it a new id, but +# the grants node's state entry keeps the old one -- DeploymentUnit.Update saves state +# under the id it was called with, and neither grants nor permissions implements +# DoUpdateWithID, so the entry is never re-keyed. DoRead echoes the stale id back as +# full_name, the planner compares it against the config's new name, and plans the same +# update again -- every deploy, forever. The grants themselves are correct (asserted +# above), so this is stale bookkeeping rather than lost access; the deploy just never +# reports converged. Same shape for permissions on any resource whose id changes on +# recreate. +title "Plan after recreate" +trace $CLI bundle plan --var=schema_name='${resources.schemas.schema2.name}' &> out.plan_after_recreate.$DATABRICKS_BUNDLE_ENGINE.txt diff --git a/acceptance/bundle/resources/volumes/recreate/test.toml b/acceptance/bundle/resources/volumes/recreate/test.toml index 4d77b7783c8..9f039a99ab8 100644 --- a/acceptance/bundle/resources/volumes/recreate/test.toml +++ b/acceptance/bundle/resources/volumes/recreate/test.toml @@ -1,3 +1,5 @@ +Badness = "Direct engine: recreating a resource re-keys the parent but not its permissions/grants child, whose state entry keeps the old id. DoRead echoes that stale id back, so the child re-proposes the same update on every deploy and never converges. The ACL itself is applied correctly, so this is stale bookkeeping rather than lost access. Terraform converges." + Cloud = true RecordRequests = false RunsOnDbr = true From 80996ed8e3d4e0680d5b7851ae8b813438373aad Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 11:14:07 +0200 Subject: [PATCH 002/118] acc: fold permissions into the model invariant config model_with_permissions.yml.tmpl was model.yml.tmpl plus a permissions block, so the two tested the same resource twice. Invariant configs carry permissions or grants by default; a permissions-free variant is not a separate case. continue_293 now excludes the model config outright, since model permissions did not work until 0.297.0. Co-authored-by: Isaac --- acceptance/bundle/invariant/configs/model.yml.tmpl | 3 +++ .../invariant/configs/model_with_permissions.yml.tmpl | 10 ---------- acceptance/bundle/invariant/continue_293/out.test.toml | 1 - acceptance/bundle/invariant/continue_293/test.toml | 6 ++++-- .../bundle/invariant/delete_idempotent/out.test.toml | 1 - .../bundle/invariant/destroy_idempotent/out.test.toml | 1 - acceptance/bundle/invariant/migrate/out.test.toml | 1 - acceptance/bundle/invariant/no_drift/out.test.toml | 1 - acceptance/bundle/invariant/test.toml | 1 - 9 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl diff --git a/acceptance/bundle/invariant/configs/model.yml.tmpl b/acceptance/bundle/invariant/configs/model.yml.tmpl index e105a731a33..44e6621ac1f 100644 --- a/acceptance/bundle/invariant/configs/model.yml.tmpl +++ b/acceptance/bundle/invariant/configs/model.yml.tmpl @@ -5,3 +5,6 @@ resources: models: foo: name: test-model-$UNIQUE_NAME + permissions: + - level: CAN_READ + group_name: users diff --git a/acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl b/acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl deleted file mode 100644 index 44e6621ac1f..00000000000 --- a/acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl +++ /dev/null @@ -1,10 +0,0 @@ -bundle: - name: test-bundle-$UNIQUE_NAME - -resources: - models: - foo: - name: test-model-$UNIQUE_NAME - permissions: - - level: CAN_READ - group_name: users diff --git a/acceptance/bundle/invariant/continue_293/out.test.toml b/acceptance/bundle/invariant/continue_293/out.test.toml index f5883975fcd..7cf38456fd5 100644 --- a/acceptance/bundle/invariant/continue_293/out.test.toml +++ b/acceptance/bundle/invariant/continue_293/out.test.toml @@ -20,7 +20,6 @@ EnvMatrix.INPUT_CONFIG = [ "job_run_job_ref.yml.tmpl", "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", - "model.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", diff --git a/acceptance/bundle/invariant/continue_293/test.toml b/acceptance/bundle/invariant/continue_293/test.toml index f174ba144c4..aebc2f7e89d 100644 --- a/acceptance/bundle/invariant/continue_293/test.toml +++ b/acceptance/bundle/invariant/continue_293/test.toml @@ -3,8 +3,10 @@ EnvMatrixExclude.no_permission_ref = ["INPUT_CONFIG=job_permission_ref.yml.tmpl" EnvMatrixExclude.no_cross_resource_ref = ["INPUT_CONFIG=job_cross_resource_ref.yml.tmpl"] EnvMatrixExclude.no_grant_ref = ["INPUT_CONFIG=schema_grant_ref.yml.tmpl"] -# Model permissions did not work until 0.297.0 https://github.com/databricks/cli/pull/4941 -EnvMatrixExclude.no_model_with_permissions = ["INPUT_CONFIG=model_with_permissions.yml.tmpl"] +# Model permissions did not work until 0.297.0 https://github.com/databricks/cli/pull/4941, +# and every invariant config carries permissions or grants, so the whole model config is +# excluded here rather than kept as a permissions-free variant. +EnvMatrixExclude.no_model = ["INPUT_CONFIG=model.yml.tmpl"] # Vector Search resources are not supported on v0.293.0 EnvMatrixExclude.no_vector_search_endpoint = ["INPUT_CONFIG=vector_search_endpoint.yml.tmpl"] diff --git a/acceptance/bundle/invariant/delete_idempotent/out.test.toml b/acceptance/bundle/invariant/delete_idempotent/out.test.toml index 8ed109d4820..82839d1c143 100644 --- a/acceptance/bundle/invariant/delete_idempotent/out.test.toml +++ b/acceptance/bundle/invariant/delete_idempotent/out.test.toml @@ -28,7 +28,6 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", - "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", diff --git a/acceptance/bundle/invariant/destroy_idempotent/out.test.toml b/acceptance/bundle/invariant/destroy_idempotent/out.test.toml index 8ed109d4820..82839d1c143 100644 --- a/acceptance/bundle/invariant/destroy_idempotent/out.test.toml +++ b/acceptance/bundle/invariant/destroy_idempotent/out.test.toml @@ -28,7 +28,6 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", - "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", diff --git a/acceptance/bundle/invariant/migrate/out.test.toml b/acceptance/bundle/invariant/migrate/out.test.toml index de39decabc5..7dcb237b857 100644 --- a/acceptance/bundle/invariant/migrate/out.test.toml +++ b/acceptance/bundle/invariant/migrate/out.test.toml @@ -18,7 +18,6 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", - "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "pipeline_allow_duplicate_names.yml.tmpl", diff --git a/acceptance/bundle/invariant/no_drift/out.test.toml b/acceptance/bundle/invariant/no_drift/out.test.toml index 8ed109d4820..82839d1c143 100644 --- a/acceptance/bundle/invariant/no_drift/out.test.toml +++ b/acceptance/bundle/invariant/no_drift/out.test.toml @@ -28,7 +28,6 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", - "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", diff --git a/acceptance/bundle/invariant/test.toml b/acceptance/bundle/invariant/test.toml index ac4584e034d..6bc15f822ef 100644 --- a/acceptance/bundle/invariant/test.toml +++ b/acceptance/bundle/invariant/test.toml @@ -46,7 +46,6 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", - "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", From 85129776d88d98d55ba2af7af140d7995781e16d Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 11:31:28 +0200 Subject: [PATCH 003/118] Catalog how the direct engine handles every settable resource field Moves every field of every single-resource invariant config through each ordered pair of a small value set -- absent included, so add and remove are just the pairs with absent on one side -- and records whether the plan proposed anything, the apply succeeded, and the next plan was clean. Runs in-process against CalculatePlan/Apply rather than shelling out to bundle deploy, so 5502 observations across 22 resource types take 35s instead of being dominated by bundle file sync. Permissions and grants are stripped first; a bad outcome does not fail the test, it lands in out..fields.txt, and the committed goldens are what turn an SDK bump into a visible diff. Co-authored-by: Isaac --- Taskfile.yml | 15 +- bundle/direct/dresources_test/.gitignore | 4 + bundle/direct/dresources_test/README.md | 104 + bundle/direct/dresources_test/corpus_test.go | 170 ++ bundle/direct/dresources_test/fields/apps.yml | 14 + .../dresources_test/fields/catalogs.yml | 3 + .../dresources_test/fields/experiments.yml | 3 + bundle/direct/dresources_test/fields/jobs.yml | 23 + .../fields/model_serving_endpoints.yml | 9 + .../direct/dresources_test/fields/models.yml | 3 + .../fields/postgres_projects.yml | 9 + .../fields/registered_models.yml | 3 + .../direct/dresources_test/fields/schemas.yml | 12 + .../direct/dresources_test/fields/volumes.yml | 12 + bundle/direct/dresources_test/fields_test.go | 342 +++ bundle/direct/dresources_test/harness_test.go | 530 +++++ .../dresources_test/out.alerts.fields.txt | 201 ++ .../dresources_test/out.apps.fields.txt | 566 +++++ .../dresources_test/out.catalogs.fields.txt | 155 ++ .../out.cluster_policies.fields.txt | 64 + .../dresources_test/out.clusters.fields.txt | 777 +++++++ bundle/direct/dresources_test/out.configs.txt | 60 + .../dresources_test/out.dashboards.fields.txt | 97 + .../out.database_instances.fields.txt | 186 ++ .../out.experiments.fields.txt | 44 + .../out.external_locations.fields.txt | 264 +++ .../out.genie_spaces.fields.txt | 55 + .../out.instance_pools.fields.txt | 162 ++ .../dresources_test/out.jobs.fields.txt | 2055 +++++++++++++++++ .../out.model_serving_endpoints.fields.txt | 396 ++++ .../dresources_test/out.models.fields.txt | 28 + .../dresources_test/out.pipelines.fields.txt | 1406 +++++++++++ .../out.postgres_projects.fields.txt | 78 + .../out.postgres_synced_tables.fields.txt | 96 + .../out.registered_models.fields.txt | 102 + .../dresources_test/out.schemas.fields.txt | 80 + .../out.secret_scopes.fields.txt | 49 + .../out.sql_warehouses.fields.txt | 111 + .../dresources_test/out.volumes.fields.txt | 52 + bundle/direct/dresources_test/report_test.go | 228 ++ bundle/direct/dresources_test/values_test.go | 233 ++ 41 files changed, 8798 insertions(+), 3 deletions(-) create mode 100644 bundle/direct/dresources_test/.gitignore create mode 100644 bundle/direct/dresources_test/README.md create mode 100644 bundle/direct/dresources_test/corpus_test.go create mode 100644 bundle/direct/dresources_test/fields/apps.yml create mode 100644 bundle/direct/dresources_test/fields/catalogs.yml create mode 100644 bundle/direct/dresources_test/fields/experiments.yml create mode 100644 bundle/direct/dresources_test/fields/jobs.yml create mode 100644 bundle/direct/dresources_test/fields/model_serving_endpoints.yml create mode 100644 bundle/direct/dresources_test/fields/models.yml create mode 100644 bundle/direct/dresources_test/fields/postgres_projects.yml create mode 100644 bundle/direct/dresources_test/fields/registered_models.yml create mode 100644 bundle/direct/dresources_test/fields/schemas.yml create mode 100644 bundle/direct/dresources_test/fields/volumes.yml create mode 100644 bundle/direct/dresources_test/fields_test.go create mode 100644 bundle/direct/dresources_test/harness_test.go create mode 100644 bundle/direct/dresources_test/out.alerts.fields.txt create mode 100644 bundle/direct/dresources_test/out.apps.fields.txt create mode 100644 bundle/direct/dresources_test/out.catalogs.fields.txt create mode 100644 bundle/direct/dresources_test/out.cluster_policies.fields.txt create mode 100644 bundle/direct/dresources_test/out.clusters.fields.txt create mode 100644 bundle/direct/dresources_test/out.configs.txt create mode 100644 bundle/direct/dresources_test/out.dashboards.fields.txt create mode 100644 bundle/direct/dresources_test/out.database_instances.fields.txt create mode 100644 bundle/direct/dresources_test/out.experiments.fields.txt create mode 100644 bundle/direct/dresources_test/out.external_locations.fields.txt create mode 100644 bundle/direct/dresources_test/out.genie_spaces.fields.txt create mode 100644 bundle/direct/dresources_test/out.instance_pools.fields.txt create mode 100644 bundle/direct/dresources_test/out.jobs.fields.txt create mode 100644 bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt create mode 100644 bundle/direct/dresources_test/out.models.fields.txt create mode 100644 bundle/direct/dresources_test/out.pipelines.fields.txt create mode 100644 bundle/direct/dresources_test/out.postgres_projects.fields.txt create mode 100644 bundle/direct/dresources_test/out.postgres_synced_tables.fields.txt create mode 100644 bundle/direct/dresources_test/out.registered_models.fields.txt create mode 100644 bundle/direct/dresources_test/out.schemas.fields.txt create mode 100644 bundle/direct/dresources_test/out.secret_scopes.fields.txt create mode 100644 bundle/direct/dresources_test/out.sql_warehouses.fields.txt create mode 100644 bundle/direct/dresources_test/out.volumes.fields.txt create mode 100644 bundle/direct/dresources_test/report_test.go create mode 100644 bundle/direct/dresources_test/values_test.go diff --git a/Taskfile.yml b/Taskfile.yml index f5702ca6db3..b00dac1cc77 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -8,6 +8,9 @@ vars: # .github/workflows/check.yml and python/Taskfile.yml. RUFF: uvx ruff@0.15.17 TEST_PACKAGES: ./acceptance/internal ./libs/... ./internal/... ./cmd/... ./bundle/... ./experimental/ssh/... . + # Field-support catalog. Covered by TEST_PACKAGES for local/CI runs via ./bundle/..., + # but named separately because the cloud tasks below list their packages explicitly. + FIELDS_PACKAGE: ./bundle/direct/dresources_test ACCEPTANCE_TEST_FILTER: "" # Single brace-expansion glob covering every //go:embed target in the repo, # computed by grepping `//go:embed` directives. Evaluated lazily by Task so @@ -582,6 +585,11 @@ tasks: cmds: - "go test ./acceptance -run '^TestAccept$' -update -timeout=${LOCAL_TIMEOUT:-60m}" + test-update-fields: + desc: Update the resource field-support catalog (local) + cmds: + - "go test {{.FIELDS_PACKAGE}} -run '^TestFields$' -update -timeout=${LOCAL_TIMEOUT:-60m}" + test-update-templates: desc: Update acceptance test template output sources: *ACC_SOURCES_UPDATE @@ -593,6 +601,7 @@ tasks: desc: Update all acceptance test outputs cmds: - task: test-update + - task: test-update-fields # Follows upstream HEAD, so its result changes over time: keep it out of # `generate-check`, which requires byte-for-byte reproducible output. @@ -747,7 +756,7 @@ tasks: --format github-actions \ --rerun-fails \ --jsonfile output.json \ - --packages "./acceptance ./integration/..." \ + --packages "./acceptance ./integration/... {{.FIELDS_PACKAGE}}" \ -- -parallel 4 -timeout=12h integration-short: @@ -761,7 +770,7 @@ tasks: --format github-actions \ --rerun-fails \ --jsonfile output.json \ - --packages "./acceptance ./integration/..." \ + --packages "./acceptance ./integration/... {{.FIELDS_PACKAGE}}" \ -- -parallel 4 -timeout=12h -short integration-short-skiplocal: @@ -775,7 +784,7 @@ tasks: --format github-actions \ --rerun-fails \ --jsonfile output.json \ - --packages "./acceptance ./integration/..." \ + --packages "./acceptance ./integration/... {{.FIELDS_PACKAGE}}" \ -- -parallel 4 -timeout=2h -short dbr-integration: diff --git a/bundle/direct/dresources_test/.gitignore b/bundle/direct/dresources_test/.gitignore new file mode 100644 index 00000000000..ae9f4974f37 --- /dev/null +++ b/bundle/direct/dresources_test/.gitignore @@ -0,0 +1,4 @@ +# Cloud runs record what a real backend accepted, which varies by workspace. +out.*.fields.aws.txt +out.*.fields.azure.txt +out.*.fields.gcp.txt diff --git a/bundle/direct/dresources_test/README.md b/bundle/direct/dresources_test/README.md new file mode 100644 index 00000000000..3cb25b1e893 --- /dev/null +++ b/bundle/direct/dresources_test/README.md @@ -0,0 +1,104 @@ +# Resource field-support catalog + +This suite answers one question for every field a user can set on a bundle resource: +**if I change this field, does the direct engine notice, apply it, and converge?** + +The answer for every field lands in `out..fields.txt`. Those files are +the deliverable — a map of where field support is solid and where it is not. A bad +outcome does not fail the test; a *changed* outcome does, because the goldens are +committed. That makes the suite a regression detector for things like an SDK bump +changing a field's type. + +## What it does + +For each config in `acceptance/bundle/invariant/configs` that describes a single +resource, the suite deploys it once, then walks the resource's input struct the way +`cmd/bundle/debug` refschema does. For each scalar field it moves the field through +every ordered pair of a small value set — with `absent` in the set, so adding and +removing a field are just the pairs with `absent` on one side — and records three +things per move: did the plan propose anything, did the apply succeed, and was the +next plan clean. + +Everything runs in-process against the direct engine's own `CalculatePlan` and +`Apply`. There is no CLI subprocess and no bundle-file upload: at thousands of +permutations, a `bundle deploy` each would be dominated by sync. + +## Verdicts + +| verdict | meaning | +| --- | --- | +| `OK` | planned, applied, next plan clean | +| `OK_RECREATE` | same, but the engine replaced the resource | +| `SUPPRESSED` | the planner diffed the field and dropped the change; detail is the engine's own reason | +| `NOT_OBSERVABLE` | the two values are identical in the state sent to the API, so there is nothing to see — an unset bool and an explicit `false` are the usual case | +| `NO_PLAN` | the field diff exists but no action was planned | +| `DRIFT` | applied, yet a later plan still wants the same change: deploying never converges | +| `DRIFT_CHILD` | the field converged but another node of the resource did not; a guard that should stay empty | +| `BACKEND_ERROR` | the API rejected the value — usually the value library needs a valid value for this field | +| `DEPLOY_ERROR` | apply failed for a non-API reason | +| `PLAN_ERROR` | planning failed | +| `UNSETTABLE` | the value could not be written into the config at all | +| `BASE_ERROR` | the transition's starting point would not deploy, so nothing was observed | +| `NOT_IN_STATE` | the field exists in bundle config but not in the state type, so it never reaches the API | + +`SUPPRESSED` and `DRIFT` are where the interesting gaps are. `NOT_OBSERVABLE` is not a +gap; it is the wire format telling the truth. + +The suite deliberately does **not** read `resources.yml`. That file is the engine's +answer to many of these cases, so consulting it would just restate the implementation. +The `SUPPRESSED` reason string is the engine explaining itself, which is different. + +## Value library + +`fields/.yml` supplies values the generic per-kind defaults cannot +guess — enums, ids, anything the backend constrains — and opts a resource type into +cloud runs: + +```yaml +cloud: true # include in cloud runs; off by default +skip: + name: rename waits out an asynchronous delete +fields: + compute_size: [MEDIUM, LARGE] +``` + +A field with no entry gets two values for its Go kind, which is enough to see a +value-to-value move on top of add and remove, so a file that only says `cloud: true` is +a normal thing to have. + +`cloud` is off by default because a cloud run pays real API latency on every transition: +`jobs` alone takes ~17 minutes against a live workspace, against 40 seconds for the +whole suite locally. Turn it on for a resource type once you have checked what it costs. + +`skip` is for a field no single-field edit can exercise — one that only validates as +part of a set (a job's `git_source`), or whose change is correct but ruinously slow (an +app rename waits out an asynchronous delete). Each entry needs a reason, and shows up in +the report as `SKIPPED` so it is not mistaken for coverage. + +## Running it + +```bash +go test ./bundle/direct/dresources_test # against the testserver +go test ./bundle/direct/dresources_test -run 'TestFields/schemas' -v +./task test-update-fields # regenerate the goldens +``` + +One transition is addressable on its own: + +``` +TestFields/schemas/schema.yml.tmpl/comment/x->absent +``` + +On cloud (`CLOUD_ENV` set) only resource types with `cloud: true` run, and the results +go to `out..fields..txt`, which is not committed: which values a +real backend accepts depends on the workspace. + +## Out of scope for now + +- remote drift — a change made outside the bundle; this suite only edits config +- `permissions` and `grants`, which are stripped from every config before planning: they + are separate plan nodes describing an ACL, and leaving them in made every recreate + report a drifted child against whichever field triggered it. Two configs that differ + only in those blocks therefore collapse to one, recorded in `out.configs.txt`. +- configs with more than one resource, or with an `-init.sh` +- fields under a slice or map, listed at the end of each report diff --git a/bundle/direct/dresources_test/corpus_test.go b/bundle/direct/dresources_test/corpus_test.go new file mode 100644 index 00000000000..b922f408e1a --- /dev/null +++ b/bundle/direct/dresources_test/corpus_test.go @@ -0,0 +1,170 @@ +package dresources_test + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + "testing" + + bundleconfig "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/direct/dresources" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/yamlloader" + "github.com/stretchr/testify/require" +) + +// The suite reuses the acceptance invariant corpus rather than growing a second set +// of bundle fixtures: those configs are already known-deployable and maintained. +const ( + invariantDir = "../../../acceptance/bundle/invariant" + configsDir = invariantDir + "/configs" + dataDir = invariantDir + "/data" +) + +// testConfig is one usable entry of the corpus. +type testConfig struct { + name string // "schema.yml.tmpl" + resourceType string // "schemas" +} + +// discoverConfigs lists the corpus entries this suite can drive today, and returns the +// rest with the reason they were left out so the report can account for every config. +// +// Out of scope for now: +// - an -init.sh counterpart: the config needs shell setup this suite does not run +// - more than one resource: field edits would need the whole dependency graph +// - permissions and grants sub-resources: they are separate plan nodes whose fields +// describe an ACL rather than the resource, and they need a parent to attach to +func discoverConfigs(t *testing.T) (usable []testConfig, skipped map[string]string) { + entries, err := filepath.Glob(filepath.Join(configsDir, "*.yml.tmpl")) + require.NoError(t, err) + require.NotEmpty(t, entries) + + skipped = map[string]string{} + seen := map[string]string{} + for _, path := range entries { + name := filepath.Base(path) + + if _, err := os.Stat(path + "-init.sh"); err == nil { + skipped[name] = "has -init.sh" + continue + } + + nodes, err := resourceNodes(path) + if err != nil { + skipped[name] = "unparseable: " + err.Error() + continue + } + if len(nodes) != 1 { + skipped[name] = "has " + strconv.Itoa(len(nodes)) + " resources" + continue + } + resourceType := bundleconfig.GetResourceTypeFromKey(nodes[0]) + if _, ok := dresources.SupportedResources[resourceType]; !ok { + skipped[name] = "unsupported resource type " + resourceType + continue + } + + // The suite strips permissions and grants, so two configs differing only in + // those blocks would test exactly the same fields twice. + fingerprint, err := resourceFingerprint(path) + if err != nil { + skipped[name] = "unfingerprintable: " + err.Error() + continue + } + if first, dup := seen[fingerprint]; dup { + skipped[name] = "same resource as " + first + " once permissions and grants are removed" + continue + } + seen[fingerprint] = name + + usable = append(usable, testConfig{name: name, resourceType: resourceType}) + } + + slices.SortFunc(usable, func(a, b testConfig) int { return strings.Compare(a.name, b.name) }) + return usable, skipped +} + +// resourceFingerprint identifies the resources a config declares, as this suite will +// actually deploy them: sub-resources removed, and the bundle block ignored because it +// carries only the run's unique name. +func resourceFingerprint(path string) (string, error) { + root, err := loadTemplate(path) + if err != nil { + return "", err + } + stripped, err := withoutSubResources(root) + if err != nil { + return "", err + } + resources, err := dyn.Get(stripped, "resources") + if err != nil { + return "", err + } + encoded, err := json.Marshal(resources.AsAny()) + if err != nil { + return "", err + } + return string(encoded), nil +} + +// loadTemplate parses a config template with its variables blanked out; only the shape +// matters to the callers here. +func loadTemplate(path string) (dyn.Value, error) { + data, err := os.ReadFile(path) + if err != nil { + return dyn.InvalidValue, err + } + yml := os.Expand(string(data), func(string) string { return "x" }) + return yamlloader.LoadYAML(path, strings.NewReader(yml)) +} + +// resourceNodes returns the "resources.." keys declared by a config +// template, ignoring the permissions and grants sub-blocks. +func resourceNodes(path string) ([]string, error) { + root, err := loadTemplate(path) + if err != nil { + return nil, err + } + + var nodes []string + _, err = dyn.MapByPattern(root, + dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), + func(p dyn.Path, v dyn.Value) (dyn.Value, error) { + nodes = append(nodes, p.String()) + return v, nil + }) + if err != nil { + return nil, err + } + slices.Sort(nodes) + return nodes, nil +} + +// soleResourceNode returns the single resource node of an initialized config. +func soleResourceNode(root *bundleconfig.Root) (string, error) { + nodes, err := initializedNodes(root) + if err != nil { + return "", err + } + if len(nodes) != 1 { + return "", fmt.Errorf("expected exactly one resource node, got %v", nodes) + } + return nodes[0], nil +} + +func initializedNodes(root *bundleconfig.Root) ([]string, error) { + var nodes []string + _, err := dyn.MapByPattern(root.Value(), + dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), + func(p dyn.Path, v dyn.Value) (dyn.Value, error) { + nodes = append(nodes, p.String()) + return v, nil + }) + slices.Sort(nodes) + return nodes, err +} diff --git a/bundle/direct/dresources_test/fields/apps.yml b/bundle/direct/dresources_test/fields/apps.yml new file mode 100644 index 00000000000..66c5f2ee38f --- /dev/null +++ b/bundle/direct/dresources_test/fields/apps.yml @@ -0,0 +1,14 @@ +# Value library for apps. See ../README.md. +# +# Cloud is off: an app create provisions compute, so a per-transition rebuild costs +# minutes rather than milliseconds. +cloud: false + +fields: + compute_size: [MEDIUM, LARGE] + +skip: + # Renaming an app recreates it, and an app delete is asynchronous: the name stays + # reserved while the old app sits in DELETING, so every rename waits out the delete. + # Recreate-on-rename is covered by acceptance/bundle/resources/apps. + name: rename waits out an asynchronous delete diff --git a/bundle/direct/dresources_test/fields/catalogs.yml b/bundle/direct/dresources_test/fields/catalogs.yml new file mode 100644 index 00000000000..c4170e0883a --- /dev/null +++ b/bundle/direct/dresources_test/fields/catalogs.yml @@ -0,0 +1,3 @@ +# Value library for catalogs. See ../README.md. +# No explicit values needed; the per-kind defaults cover every field. +cloud: true diff --git a/bundle/direct/dresources_test/fields/experiments.yml b/bundle/direct/dresources_test/fields/experiments.yml new file mode 100644 index 00000000000..89d9b583650 --- /dev/null +++ b/bundle/direct/dresources_test/fields/experiments.yml @@ -0,0 +1,3 @@ +# Value library for experiments. See ../README.md. +# No explicit values needed; the per-kind defaults cover every field. +cloud: true diff --git a/bundle/direct/dresources_test/fields/jobs.yml b/bundle/direct/dresources_test/fields/jobs.yml new file mode 100644 index 00000000000..b75eba5361a --- /dev/null +++ b/bundle/direct/dresources_test/fields/jobs.yml @@ -0,0 +1,23 @@ +# Value library for jobs. See ../README.md. +cloud: true + +skip: + # git_source only validates as a coherent set: a provider, a url, and exactly one of + # branch/tag/commit. Moving one of its fields at a time always produces an invalid + # combination, so every value comes back rejected and tells us nothing about field + # support. Covered instead by acceptance/bundle/resources/jobs. + git_source.git_provider: needs a coherent git_source block + git_source.git_url: needs a coherent git_source block + git_source.git_branch: needs a coherent git_source block + git_source.git_tag: needs a coherent git_source block + git_source.git_commit: needs a coherent git_source block + git_source.git_snapshot.used_commit: needs a coherent git_source block + git_source.job_source.job_config_path: needs a coherent git_source block + git_source.job_source.import_from_git_branch: needs a coherent git_source block + git_source.job_source.dirty_state: needs a coherent git_source block + +fields: + format: [SINGLE_TASK, MULTI_TASK] + performance_target: [PERFORMANCE_OPTIMIZED, STANDARD] + max_concurrent_runs: [1, 5] + timeout_seconds: [600, 1200] diff --git a/bundle/direct/dresources_test/fields/model_serving_endpoints.yml b/bundle/direct/dresources_test/fields/model_serving_endpoints.yml new file mode 100644 index 00000000000..6a9928972f9 --- /dev/null +++ b/bundle/direct/dresources_test/fields/model_serving_endpoints.yml @@ -0,0 +1,9 @@ +# Value library for model serving endpoints. See ../README.md. +# +# Cloud is off: creating an endpoint provisions serving compute. +cloud: false + +skip: + # The inference table name is only accepted alongside a catalog and schema that exist + # and are writable, which one-field-at-a-time cannot arrange. + telemetry_config.inference_table_config.name: needs a writable catalog and schema diff --git a/bundle/direct/dresources_test/fields/models.yml b/bundle/direct/dresources_test/fields/models.yml new file mode 100644 index 00000000000..16d0966761f --- /dev/null +++ b/bundle/direct/dresources_test/fields/models.yml @@ -0,0 +1,3 @@ +# Value library for models. See ../README.md. +# No explicit values needed; the per-kind defaults cover every field. +cloud: true diff --git a/bundle/direct/dresources_test/fields/postgres_projects.yml b/bundle/direct/dresources_test/fields/postgres_projects.yml new file mode 100644 index 00000000000..807a4650bb9 --- /dev/null +++ b/bundle/direct/dresources_test/fields/postgres_projects.yml @@ -0,0 +1,9 @@ +# Value library for postgres projects. See ../README.md. +# +# Cloud is off: Lakebase is AWS-only and provisioning a project is slow. +cloud: false + +fields: + # project_id is part of the resource's hierarchical name, so a change recreates it. + project_id: [pgproj1, pgproj2] + pg_version: [16, 17] diff --git a/bundle/direct/dresources_test/fields/registered_models.yml b/bundle/direct/dresources_test/fields/registered_models.yml new file mode 100644 index 00000000000..cff26271f0c --- /dev/null +++ b/bundle/direct/dresources_test/fields/registered_models.yml @@ -0,0 +1,3 @@ +# Value library for registered_models. See ../README.md. +# No explicit values needed; the per-kind defaults cover every field. +cloud: true diff --git a/bundle/direct/dresources_test/fields/schemas.yml b/bundle/direct/dresources_test/fields/schemas.yml new file mode 100644 index 00000000000..bd7c7c2115c --- /dev/null +++ b/bundle/direct/dresources_test/fields/schemas.yml @@ -0,0 +1,12 @@ +# Value library for schemas. See ../README.md. +cloud: true + +skip: + # storage_root must point at a real external location. + storage_root: needs an external location + +fields: + # A real workspace has no catalog named "x", so the generic string values would only + # ever report BACKEND_ERROR. "main" exists everywhere; with one value the field still + # gets its add and remove transitions. + catalog_name: [main] diff --git a/bundle/direct/dresources_test/fields/volumes.yml b/bundle/direct/dresources_test/fields/volumes.yml new file mode 100644 index 00000000000..cd7d215618a --- /dev/null +++ b/bundle/direct/dresources_test/fields/volumes.yml @@ -0,0 +1,12 @@ +# Value library for volumes. See ../README.md. +cloud: true + +skip: + # An external volume needs a storage location backed by a real external location and + # cloud IAM, which this suite does not provision. + storage_location: needs an external location + +fields: + # EXTERNAL is only valid together with storage_location, so only MANAGED is + # deployable on its own. + volume_type: [MANAGED] diff --git a/bundle/direct/dresources_test/fields_test.go b/bundle/direct/dresources_test/fields_test.go new file mode 100644 index 00000000000..fc34ed7abde --- /dev/null +++ b/bundle/direct/dresources_test/fields_test.go @@ -0,0 +1,342 @@ +// Package dresources_test catalogs how the direct engine handles a change to every +// field a user can set on a bundle resource. +// +// For each resource it deploys a base config once, then walks the resource's input +// struct and, per field, moves the field through every ordered pair of a small set of +// values -- "absent" included, so adding and removing a field are just the pairs with +// absent on one side. Each move is observed three ways: did the plan propose +// anything, did the apply succeed, and was the next plan clean. +// +// Nothing here shells out to the CLI. A `bundle deploy` per permutation would be +// dominated by bundle-file sync, and there are thousands of permutations. +// +// A bad outcome does not fail the test. The suite is a catalog: every result lands in +// out..fields.txt, and that golden is the report. A regression shows up +// as a diff in it -- for instance after an SDK bump moves a field's type. +// +// Permissions and grants are out of scope, and are stripped from every config before +// anything is planned: they are separate plan nodes describing an ACL rather than the +// resource, and left in place they make every recreate report a drifted child node +// against whichever field happened to trigger the recreate. +// +// Also out of scope for now, tracked as follow-ups: +// - remote drift (a change made outside the bundle); this suite only edits config +// - configs with more than one resource, or with an -init.sh +// - fields under a slice or map (listed at the end of each report) +package dresources_test + +import ( + "errors" + "fmt" + "regexp" + "slices" + "strings" + "testing" + + "github.com/databricks/cli/bundle/deployplan" + "github.com/databricks/cli/bundle/direct/dresources" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/iam" + "github.com/stretchr/testify/require" +) + +func TestFields(t *testing.T) { + usable, skipped := discoverConfigs(t) + + byType := map[string][]testConfig{} + for _, c := range usable { + byType[c.resourceType] = append(byType[c.resourceType], c) + } + + writeCorpusReport(t, usable, skipped) + + for resourceType, configs := range byType { + t.Run(resourceType, func(t *testing.T) { + // One resource type per goroutine: the types are independent, and the + // slowest type then sets the wall time instead of their sum. + t.Parallel() + + fv, err := loadFieldValues(resourceType) + require.NoError(t, err) + if isCloud() && !fv.cloud { + t.Skipf("not enabled for cloud runs; set cloud: true in fields/%s.yml", resourceType) + } + + // A testserver per type keeps the parallel runs from sharing workspace + // state. On cloud this is the same real workspace either way. + client := newClient(t) + user := workspaceUser(t, client) + + rep := &report{resourceType: resourceType} //exhaustruct:ignore + for _, cfg := range configs { + t.Run(cfg.name, func(t *testing.T) { + runConfig(t, client, user, cfg, fv, rep) + }) + } + rep.write(t) + }) + } +} + +func runConfig(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, rep *report) { + adapter, err := dresources.NewAdapter(dresources.SupportedResources[cfg.resourceType], cfg.resourceType, client) + require.NoError(t, err) + + fields, wildcard := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv) + rep.wildcard = wildcard + + // The base deploy establishes that the config itself is deployable. If it is not, + // nothing below can be attributed to a field. + h, err := newBaseline(t, client, user, cfg) + if err != nil { + rep.add(result{cfg.name, "(base config)", "create", verdictBaseError, oneLine(err.Error())}) + return + } + + base := h.fieldSnapshot() + + for _, f := range fields { + if reason, ok := fv.skip[f.path]; ok { + rep.add(result{cfg.name, f.path, "-", verdictSkipped, reason}) + continue + } + t.Run(f.path, func(t *testing.T) { + for _, tr := range f.transitions() { + t.Run(tr.label(), func(t *testing.T) { + res := runTransition(h, cfg.name, f.path, tr) + rep.add(res) + if res.verdict.leavesResourceUsable() { + return + } + // The resource is in an unknown state; carrying it into the next + // transition would turn one failure into a run of them. + rebuilt, err := rebuild(t, client, user, cfg, h) + if err != nil { + t.Skipf("could not rebuild baseline: %s", err) + } + h = rebuilt + base = h.fieldSnapshot() + }) + } + }) + // Put the field back and confirm the resource converged. A field the API cannot + // clear leaves it drifted for good, which would otherwise be blamed on every + // field tested afterwards -- so start over on a fresh resource. A new name is + // what makes that cheap: reusing the old one waits out an asynchronous delete. + h.restoreField(base, f.path) + if !h.converged() { + rebuilt, err := rebuild(t, client, user, cfg, h) + if err != nil { + rep.add(result{cfg.name, f.path, "-", verdictBaseError, oneLine(err.Error())}) + return + } + h = rebuilt + base = h.fieldSnapshot() + } + } +} + +// runTransition moves one field from one value to another and reports what happened. +func runTransition(h *bundleHarness, config, path string, tr transition) result { + from, to := tr.from, tr.to + res := result{config: config, field: path, transition: tr.label()} //exhaustruct:ignore + + // Reach the starting value. + if err := h.setField(path, from); err != nil { + res.verdict = verdictUnsettable + res.detail = err.Error() + return res + } + if _, diags := h.deploy(); diags.HasError() { + res.verdict = verdictBaseError + res.detail = firstError(diags) + return res + } + + // Now the change under test. + if err := h.setField(path, to); err != nil { + res.verdict = verdictUnsettable + res.detail = err.Error() + return res + } + + pending, plan, diags := h.plan() + if diags.HasError() { + res.verdict = verdictPlanError + res.detail = firstError(diags) + return res + } + action := nodeAction(plan, h.node) + if action == deployplan.Skip { + res.verdict, res.detail = explainSkip(plan, h.node, path) + return res + } + + if diags := h.apply(pending, plan); diags.HasError() { + res.verdict = verdictDeployError + if isAPIError(diags) { + res.verdict = verdictBackendError + } + res.detail = firstError(diags) + return res + } + + _, after, diags := h.plan() + if diags.HasError() { + res.verdict = verdictPlanError + res.detail = firstError(diags) + return res + } + if own, child := driftDetail(after, h.node); own != "" || child != "" { + // Attribute to the field only when the field's own node is still drifted; a + // child left behind by a recreate is a different problem with a different fix. + res.verdict, res.detail = verdictDrift, own + if own == "" { + res.verdict, res.detail = verdictDriftChild, child + } + return res + } + + res.verdict = verdictOK + if action == deployplan.Recreate { + res.verdict = verdictRecreate + } + return res +} + +// newBaseline builds a harness and deploys the config as written. +func newBaseline(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig) (*bundleHarness, error) { + h, err := newHarness(t, client, user, cfg.name, uniqueName()) + if err != nil { + return nil, err + } + t.Cleanup(func() { _ = h.destroy() }) + + action, diags := h.deploy() + if diags.HasError() { + return nil, errors.New(firstError(diags)) + } + if action != deployplan.Create { + return nil, fmt.Errorf("expected create, got %s", action) + } + return h, nil +} + +// rebuild starts over on a fresh resource, leaving the old one to be destroyed at the +// end of the test. A new name is what makes this cheap: reusing the old one can mean +// waiting out an asynchronous delete. +func rebuild(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, old *bundleHarness) (*bundleHarness, error) { + t.Cleanup(func() { _ = old.destroy() }) + return newBaseline(t, client, user, cfg) +} + +// explainSkip says why a plan came back with nothing to do for the field under test. +// The distinction matters: a field the planner never diffed is not a finding (an unset +// bool and an explicit false are the same on the wire), whereas a field it diffed and +// then dropped is, and the engine records its own reason for that. +func explainSkip(plan *deployplan.Plan, node, path string) (verdict, string) { + entry, ok := plan.Plan[node] + if !ok { + return verdictNoPlan, "no plan entry" + } + change, ok := entry.Changes[path] + if !ok { + return verdictNotObservable, "" + } + if change.Reason != "" { + return verdictSuppressed, change.Reason + } + return verdictSuppressed, string(change.Action) +} + +// valueLabel renders a value for the subtest name and the report. +func valueLabel(v dyn.Value) string { + if !v.IsValid() { + return "absent" + } + switch s := fmt.Sprintf("%v", v.AsAny()); s { + case "": + return "empty" + default: + return strings.ReplaceAll(s, " ", "_") + } +} + +func firstError(diags diag.Diagnostics) string { + for _, d := range diags { + if d.Severity == diag.Error { + return oneLine(d.Summary) + } + } + return "" +} + +// apiErrorCodes are the error codes that mean the backend rejected the request rather +// than the CLI failing. Diagnostics carry a rendered message, not the error value, so +// there is nothing to match with errors.As by the time we see them. +var apiErrorCodes = []string{ + "INVALID_PARAMETER_VALUE", + "BAD_REQUEST", + "RESOURCE_DOES_NOT_EXIST", + "RESOURCE_ALREADY_EXISTS", + "PERMISSION_DENIED", + "REQUEST_LIMIT_EXCEEDED", + "FEATURE_DISABLED", + "NOT_FOUND", +} + +func isAPIError(diags diag.Diagnostics) bool { + for _, d := range diags { + for _, code := range apiErrorCodes { + if strings.Contains(d.Summary, code) { + return true + } + } + } + return false +} + +// driftDetail describes what a post-deploy plan still wants to change, split into drift +// on the node under test (own: the field paths that did not stick) and drift on any +// other node (child: how a recreate orphaning a permissions or grants node shows up). +// Both are "" when the deploy converged. +func driftDetail(plan *deployplan.Plan, node string) (own, child string) { + var ownPaths, childNodes []string + for key, entry := range plan.Plan { + if entry.Action == deployplan.Skip { + continue + } + if key != node { + childNodes = append(childNodes, strings.TrimPrefix(key, node+".")+":"+string(entry.Action)) + continue + } + for p, ch := range entry.Changes { + if ch.Action != deployplan.Skip { + ownPaths = append(ownPaths, p) + } + } + if len(entry.Changes) == 0 { + ownPaths = append(ownPaths, string(entry.Action)) + } + } + slices.Sort(ownPaths) + slices.Sort(childNodes) + return strings.Join(ownPaths, ","), strings.Join(childNodes, " ") +} + +// idPattern matches the unique suffix this suite gives every resource it creates. The +// suffix is random per run, and error messages quote resource names and ids, so it has +// to be redacted or the goldens differ on every run. +var idPattern = regexp.MustCompile(`f[0-9a-f]{20}`) + +func oneLine(s string) string { + s = idPattern.ReplaceAllString(s, "[UNIQUE_NAME]") + s = strings.ReplaceAll(s, "\n", " ") + s = strings.Join(strings.Fields(s), " ") + if len(s) > 140 { + s = s[:140] + "..." + } + return s +} diff --git a/bundle/direct/dresources_test/harness_test.go b/bundle/direct/dresources_test/harness_test.go new file mode 100644 index 00000000000..207f4447a5d --- /dev/null +++ b/bundle/direct/dresources_test/harness_test.go @@ -0,0 +1,530 @@ +package dresources_test + +import ( + "context" + "fmt" + "io" + "math" + "os" + "path/filepath" + "slices" + "strings" + "testing" + + "github.com/databricks/cli/bundle" + bundleconfig "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/deployplan" + "github.com/databricks/cli/bundle/direct" + "github.com/databricks/cli/bundle/direct/dstate" + bundleenv "github.com/databricks/cli/bundle/env" + "github.com/databricks/cli/bundle/phases" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/dbr" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/env" + "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/iam" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +// bundleHarness drives the direct engine over one bundle config in-process. Nothing +// here shells out to the CLI or uploads files: the point of this suite is to exercise +// plan and apply for thousands of field permutations, and a `bundle deploy` per +// permutation would be dominated by bundle-file sync. +type bundleHarness struct { + t *testing.T + ctx context.Context + client *databricks.WorkspaceClient + bundle *bundle.Bundle + node string // "resources.schemas.foo" + statePath string +} + +func newClient(t *testing.T) *databricks.WorkspaceClient { + if isCloud() { + w, err := databricks.NewWorkspaceClient() + require.NoError(t, err) + return w + } + server := testserver.New(tolerantT{t}) + testserver.AddDefaultHandlers(server) + //exhaustruct:ignore // an SDK config needs only these three fields to reach a fake server + w, err := databricks.NewWorkspaceClient(&databricks.Config{ + Host: server.URL, + Token: "testtoken", + RateLimitPerSecond: math.MaxInt, + }) + require.NoError(t, err) + return w +} + +func isCloud() bool { return os.Getenv("CLOUD_ENV") != "" } + +// workspaceUser is resolved once per resource type and handed to every harness, so +// PopulateCurrentUser never reaches the API. Locally the name is pinned, which keeps +// workspace paths stable between runs. +func workspaceUser(t *testing.T, client *databricks.WorkspaceClient) *iam.User { + if !isCloud() { + return &iam.User{UserName: testUserName} //exhaustruct:ignore + } + user, err := client.CurrentUser.Me(t.Context(), iam.MeRequest{}) //exhaustruct:ignore + require.NoError(t, err) + return user +} + +// testUserName is pinned locally so workspace paths are stable between runs. +const testUserName = "tester@databricks.com" + +// templateVars mirrors the variables acceptance/acceptance_test.go exports to the +// invariant configs. On cloud the harness that launched us provides the real ids. +func templateVars(uniqueName, userName string) map[string]string { + vars := map[string]string{ + "UNIQUE_NAME": uniqueName, + // Matches defaultSparkVersion in acceptance/acceptance_test.go. + "DEFAULT_SPARK_VERSION": "13.3.x-snapshot-scala2.12", + "NODE_TYPE_ID": nodeTypeID(), + "CURRENT_USER_NAME": userName, + "TEST_DEFAULT_WAREHOUSE_ID": testserver.TestDefaultWarehouseId, + "TEST_INSTANCE_POOL_ID": testserver.TestDefaultInstancePoolId, + } + if !isCloud() { + return vars + } + for key := range vars { + // UNIQUE_NAME is this run's own, and CURRENT_USER_NAME is already the resolved + // workspace user, which is more reliable than an env var that may not be set. + if key == "UNIQUE_NAME" || key == "CURRENT_USER_NAME" { + continue + } + if value := os.Getenv(key); value != "" { + vars[key] = value + } + } + return vars +} + +// nodeTypeID mirrors getNodeTypeID in acceptance/acceptance_test.go. +func nodeTypeID() string { + switch cloudName() { + case "azure": + return "Standard_E4ds_v5" + case "gcp": + return "n1-standard-4" + default: + return "i3.xlarge" + } +} + +func retryIntervalMs() string { + if isCloud() { + return "2000" + } + return "10" +} + +// maxWaitSeconds caps the per-resource readiness wait. The testserver answers +// immediately, so anything non-zero there only guards against a polling loop. +func maxWaitSeconds() string { + if isCloud() { + return "300" + } + // Against the fake server every legitimate wait finishes at once, so this cap only + // bounds the waits that cannot succeed -- an app delete, for one, which the fake + // server holds in DELETING the way the real API does. + return "2" +} + +// newHarness renders one invariant config into a temp dir and runs the bundle +// initialize phase over it, leaving a fully-resolved config.Root ready to plan. +// newHarness returns an error rather than failing the test: a harness that cannot be +// built is usually the environment (an expired token, a workspace that rejects the +// config), and the run is more useful if that lands in the report as one bad verdict +// than if it aborts thousands of pending observations. +func newHarness(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string) (*bundleHarness, error) { + dir := t.TempDir() + if err := copyDir(dataDir, dir); err != nil { + return nil, err + } + + src, err := os.ReadFile(filepath.Join(configsDir, configName)) + if err != nil { + return nil, err + } + vars := templateVars(uniqueName, user.UserName) + var missing string + yml := os.Expand(string(src), func(key string) string { + value, ok := vars[key] + if !ok { + // Expanding an unknown variable to "" turns a required field into null, and + // the failure then surfaces as a confusing validation error much later. + missing = key + } + return value + }) + if missing != "" { + return nil, fmt.Errorf("%s uses $%s, which this suite does not provide", configName, missing) + } + if err := os.WriteFile(filepath.Join(dir, "databricks.yml"), []byte(yml), 0o600); err != nil { + return nil, err + } + + ctx := dbr.MockRuntime(t.Context(), dbr.Environment{}) //exhaustruct:ignore + // Thousands of deploys run through here, so cap how long any one of them waits for + // a resource to become ready. Without a cap a resource that never reaches its + // terminal state stalls the whole suite instead of showing up as one bad verdict. + ctx = env.Set(ctx, bundleenv.ResourceMaxWaitVariable, maxWaitSeconds()) + // The engine's default retry interval is 15s. Recreating a resource whose delete is + // asynchronous (apps) retries on purpose, and at 15s a run costs hours. + ctx = env.Set(ctx, bundleenv.RetryIntervalMsVariable, retryIntervalMs()) + ctx = logdiag.InitContext(ctx) + logdiag.SetCollect(ctx, true) + ctx = cmdctx.SetWorkspaceClient(ctx, client) + // Some applies log progress through cmdio (app deployments, for one), which panics + // without an IO in the context. Discard it: the report is the output that matters. + ctx = cmdio.InContext(ctx, cmdio.NewTestIO(strings.NewReader(""), io.Discard, io.Discard)) + + b, err := bundle.Load(ctx, dir) + if err != nil { + return nil, err + } + b.SetWorkpaceClient(client) + b.AutoApprove = true + + phases.LoadDefaultTarget(ctx, b) + if diags := logdiag.FlushCollected(ctx); diags.HasError() { + return nil, fmt.Errorf("loading %s: %s", configName, firstError(diags)) + } + + // Pin the user so PopulateCurrentUser makes no API call: a harness is built per + // config and again on every rebuild, which on cloud would be thousands of identical + // CurrentUser.Me requests. + bundle.ApplyFuncContext(ctx, b, func(ctx context.Context, b *bundle.Bundle) { + b.Config.Workspace.CurrentUser = &bundleconfig.User{User: user} //exhaustruct:ignore + }) + + phases.Initialize(ctx, b) + if diags := logdiag.FlushCollected(ctx); diags.HasError() { + return nil, fmt.Errorf("initializing %s: %s", configName, firstError(diags)) + } + + // Drop the sub-resource blocks before anything is planned. They are separate plan + // nodes describing an ACL rather than the resource, and they are out of scope here. + // + // Leaving them in also mis-attributes a known bug: a recreate re-keys the parent but + // not its child's state entry, so the child re-proposes the same update forever -- + // which would be reported against whichever field happened to trigger the recreate, + // once per field. That bug has its own coverage in + // acceptance/bundle/resources/volumes/recreate. + if err := stripSubResources(&b.Config); err != nil { + return nil, err + } + + node, err := soleResourceNode(&b.Config) + if err != nil { + return nil, err + } + + return &bundleHarness{ + t: t, + ctx: ctx, + client: client, + bundle: b, + node: node, + statePath: filepath.Join(dir, "resources.json"), + }, nil +} + +// cachedUser resolves the workspace user once per process: a harness is built per +// config and again on every rebuild, so on cloud this would otherwise be thousands of +// identical requests. Locally the name is pinned, which also keeps workspace paths +// stable between runs. + +// opCtx returns a context with a fresh diagnostics sink. logdiag's error flag is sticky +// -- FlushCollected empties the collected diagnostics but leaves HasError true -- and +// CalculatePlan short-circuits to a bare "planning failed" whenever that flag is set. A +// per-operation context is what keeps one failed transition from poisoning every plan +// after it. +func (h *bundleHarness) opCtx() context.Context { + ctx := logdiag.IsolatedContext(h.ctx) + logdiag.SetCollect(ctx, true) + return ctx +} + +// plan opens the state fresh each time: dstate.Open panics on an already-open +// receiver, and Finalize resets it, so one DeploymentBundle serves one operation. +func (h *bundleHarness) plan() (*pendingApply, *deployplan.Plan, diag.Diagnostics) { + ctx := h.opCtx() + db := &direct.DeploymentBundle{} //exhaustruct:ignore + if err := db.StateDB.Open(ctx, h.statePath, dstate.WithRecovery(false), dstate.WithWrite(false)); err != nil { + return nil, nil, diag.FromErr(err) + } + plan, err := db.CalculatePlan(ctx, h.client, &h.bundle.Config) + diags := logdiag.FlushCollected(ctx) + if err != nil && !diags.HasError() { + diags = append(diags, diag.FromErr(err)...) + } + return &pendingApply{ctx: ctx, db: db}, plan, diags +} + +// pendingApply carries a planned DeploymentBundle together with the context it was +// planned under, so the apply reports into the same isolated diagnostics sink. +type pendingApply struct { + ctx context.Context + db *direct.DeploymentBundle +} + +// apply consumes the DeploymentBundle returned by plan. +func (h *bundleHarness) apply(p *pendingApply, plan *deployplan.Plan) diag.Diagnostics { + ctx, db := p.ctx, p.db + if err := db.StateDB.UpgradeToWrite(); err != nil { + return diag.FromErr(err) + } + if err := db.InitForApply(ctx, h.client, plan); err != nil { + _, _ = db.StateDB.Finalize(ctx) + return diag.FromErr(err) + } + db.Apply(ctx, h.client, plan) + diags := logdiag.FlushCollected(ctx) + if _, err := db.StateDB.Finalize(ctx); err != nil { + diags = append(diags, diag.FromErr(err)...) + } + return diags +} + +// deploy plans and applies, returning the planned action for the node under test. +func (h *bundleHarness) deploy() (deployplan.ActionType, diag.Diagnostics) { + pending, plan, diags := h.plan() + if diags.HasError() { + return deployplan.Undefined, diags + } + return nodeAction(plan, h.node), h.apply(pending, plan) +} + +// destroy deletes everything in state. A nil configRoot makes the planner treat +// every state entry as a delete (same call phases.Destroy makes). +func (h *bundleHarness) destroy() diag.Diagnostics { + ctx := h.opCtx() + db := &direct.DeploymentBundle{} //exhaustruct:ignore + if err := db.StateDB.Open(ctx, h.statePath, dstate.WithRecovery(false), dstate.WithWrite(false)); err != nil { + return diag.FromErr(err) + } + plan, err := db.CalculatePlan(ctx, h.client, nil) + if err != nil { + _, _ = db.StateDB.Finalize(ctx) + return diag.FromErr(err) + } + return h.apply(&pendingApply{ctx: ctx, db: db}, plan) +} + +func nodeAction(plan *deployplan.Plan, node string) deployplan.ActionType { + if plan == nil { + return deployplan.Undefined + } + entry, ok := plan.Plan[node] + if !ok { + return deployplan.Undefined + } + return entry.Action +} + +// hasDrift reports whether any node still has work planned. +func hasDrift(plan *deployplan.Plan) bool { + for _, entry := range plan.Plan { + if entry.Action != deployplan.Skip { + return true + } + } + return false +} + +// mutate edits the bundle's dynamic config, which is what GetResourceConfig reads. +// Editing the typed structs would not be seen by the planner. +func (h *bundleHarness) mutate(fn func(dyn.Value) (dyn.Value, error)) error { + return h.bundle.Config.Mutate(fn) +} + +func copyDir(src, dst string) error { + return filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + rel, err := filepath.Rel(src, path) + if err != nil { + return err + } + target := filepath.Join(dst, rel) + if d.IsDir() { + return os.MkdirAll(target, 0o700) + } + data, err := os.ReadFile(path) + if err != nil { + return err + } + return os.WriteFile(target, data, 0o600) + }) +} + +// setField writes a value into the resource under test, or removes the field when the +// value is absent. Edits go through the dynamic config because that is what the +// planner reads (config.Root.GetResourceConfig). +func (h *bundleHarness) setField(path string, value dyn.Value) error { + full, err := dyn.NewPathFromString(h.node + "." + path) + if err != nil { + return err + } + return h.mutate(func(root dyn.Value) (dyn.Value, error) { + if !value.IsValid() { + return deletePath(root, full) + } + if err := ensureParents(&root, full); err != nil { + return dyn.InvalidValue, err + } + return dyn.SetByPath(root, full, value) + }) +} + +// fieldSnapshot captures the resource node as deployed, so a field can be put back to +// its base value once its transitions are done. +func (h *bundleHarness) fieldSnapshot() dyn.Value { + node, err := dyn.NewPathFromString(h.node) + require.NoError(h.t, err) + v, err := dyn.GetByPath(h.bundle.Config.Value(), node) + require.NoError(h.t, err) + return v +} + +// restoreField resets one field to whatever the base config had, including absent. +func (h *bundleHarness) restoreField(base dyn.Value, path string) { + rel, err := dyn.NewPathFromString(path) + if err != nil { + return + } + value, err := dyn.GetByPath(base, rel) + if err != nil { + value = dyn.InvalidValue + } + _ = h.setField(path, value) +} + +// ensureParents creates any missing intermediate maps so a nested field can be set, +// which is what writing the same nested key into databricks.yml would do. +func ensureParents(root *dyn.Value, p dyn.Path) error { + for i := 1; i < len(p); i++ { + prefix := p[:i] + if _, err := dyn.GetByPath(*root, prefix); err == nil { + continue + } + if prefix[len(prefix)-1].Key() == "" { + // An index component: growing a sequence would invent an element the + // config never declared. + return fmt.Errorf("cannot create %s: parent is a sequence", prefix) + } + updated, err := dyn.SetByPath(*root, prefix, dyn.V(map[string]dyn.Value{})) + if err != nil { + return err + } + *root = updated + } + return nil +} + +// subResourceKinds are the child plan nodes this suite removes from every config; see +// stripSubResources. +var subResourceKinds = []string{"permissions", "grants"} + +func stripSubResources(root *bundleconfig.Root) error { + return root.Mutate(withoutSubResources) +} + +// withoutSubResources returns the config with every permissions and grants block removed. +func withoutSubResources(v dyn.Value) (dyn.Value, error) { + for _, kind := range subResourceKinds { + pattern := dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey(), dyn.Key(kind)) + + // Collect first: deleting during the walk would mutate what it is walking. + var paths []dyn.Path + _, err := dyn.MapByPattern(v, pattern, func(p dyn.Path, found dyn.Value) (dyn.Value, error) { + paths = append(paths, slices.Clone(p)) + return found, nil + }) + if err != nil { + return dyn.InvalidValue, err + } + + for _, path := range paths { + v, err = deletePath(v, path) + if err != nil { + return dyn.InvalidValue, err + } + } + } + return v, nil +} + +// deletePath removes a key from its parent map. dyn has no delete, so the parent is +// rebuilt without the key, preserving order. +func deletePath(root dyn.Value, p dyn.Path) (dyn.Value, error) { + key := p[len(p)-1].Key() + if key == "" { + return dyn.InvalidValue, fmt.Errorf("cannot remove %s: not a map key", p) + } + parentPath := p[:len(p)-1] + + parent, err := dyn.GetByPath(root, parentPath) + if dyn.IsNoSuchKeyError(err) || dyn.IsCannotTraverseNilError(err) { + // The parent object is not in this config, so the field is already absent. + return root, nil + } + if err != nil { + return dyn.InvalidValue, err + } + mapping, ok := parent.AsMap() + if !ok { + return dyn.InvalidValue, fmt.Errorf("cannot remove %s: parent is %s", p, parent.Kind()) + } + if _, found := mapping.GetByString(key); !found { + return root, nil + } + + trimmed := dyn.NewMapping() + for _, pair := range mapping.Pairs() { + if k, ok := pair.Key.AsString(); ok && k == key { + continue + } + k, _ := pair.Key.AsString() + trimmed.SetLoc(k, pair.Key.Locations(), pair.Value) + } + return dyn.SetByPath(root, parentPath, dyn.NewValue(trimmed, parent.Locations())) +} + +// uniqueName keeps cloud resources from colliding between runs and between the +// parallel subtests of one run. +func uniqueName() string { + return "f" + strings.ToLower(strings.ReplaceAll(uuid.NewString(), "-", ""))[:20] +} + +// converged deploys the current config and reports whether a following plan is clean. +func (h *bundleHarness) converged() bool { + if _, diags := h.deploy(); diags.HasError() { + return false + } + _, plan, diags := h.plan() + return !diags.HasError() && plan != nil && !hasDrift(plan) +} + +// tolerantT wraps a *testing.T for the fake server. This suite deliberately drives the +// engine with odd field values, and some of them make it issue a request the fake +// server has no route for -- an empty name lands on "GET /api/2.0/serving-endpoints/". +// The fake server reports that with Errorf, which would fail the whole run; here it is +// just another observation, so errors are downgraded to log lines. +type tolerantT struct { + *testing.T +} + +func (tolerantT) Error(args ...any) {} +func (tolerantT) Errorf(format string, args ...any) {} diff --git a/bundle/direct/dresources_test/out.alerts.fields.txt b/bundle/direct/dresources_test/out.alerts.fields.txt new file mode 100644 index 00000000000..c374b32a3a1 --- /dev/null +++ b/bundle/direct/dresources_test/out.alerts.fields.txt @@ -0,0 +1,201 @@ +=== alert.yml.tmpl +create_time absent->x OK +create_time absent->y OK +create_time x->absent OK +create_time x->y OK +create_time y->absent OK +create_time y->x OK +custom_description absent->x OK +custom_description absent->y OK +custom_description x->absent OK +custom_description x->y OK +custom_description y->absent OK +custom_description y->x OK +custom_summary absent->x OK +custom_summary absent->y OK +custom_summary x->absent OK +custom_summary x->y OK +custom_summary y->absent OK +custom_summary y->x OK +display_name x->y OK +display_name y->x OK +effective_run_as.service_principal_name absent->x OK +effective_run_as.service_principal_name absent->y OK +effective_run_as.service_principal_name x->absent OK +effective_run_as.service_principal_name x->y OK +effective_run_as.service_principal_name y->absent OK +effective_run_as.service_principal_name y->x OK +effective_run_as.user_name absent->x OK +effective_run_as.user_name absent->y OK +effective_run_as.user_name x->absent OK +effective_run_as.user_name x->y OK +effective_run_as.user_name y->absent OK +effective_run_as.user_name y->x OK +evaluation.comparison_operator x->y OK +evaluation.comparison_operator y->x OK +evaluation.empty_result_state absent->x OK +evaluation.empty_result_state absent->y OK +evaluation.empty_result_state x->absent OK +evaluation.empty_result_state x->y OK +evaluation.empty_result_state y->absent OK +evaluation.empty_result_state y->x OK +evaluation.last_evaluated_at absent->x OK +evaluation.last_evaluated_at absent->y OK +evaluation.last_evaluated_at x->absent OK +evaluation.last_evaluated_at x->y OK +evaluation.last_evaluated_at y->absent OK +evaluation.last_evaluated_at y->x OK +evaluation.notification.notify_on_ok absent->false NOT_OBSERVABLE +evaluation.notification.notify_on_ok absent->true OK +evaluation.notification.notify_on_ok false->absent SUPPRESSED empty +evaluation.notification.notify_on_ok false->true OK +evaluation.notification.notify_on_ok true->absent OK +evaluation.notification.notify_on_ok true->false OK +evaluation.notification.retrigger_seconds 1->2 OK +evaluation.notification.retrigger_seconds 1->absent OK +evaluation.notification.retrigger_seconds 2->1 OK +evaluation.notification.retrigger_seconds 2->absent OK +evaluation.notification.retrigger_seconds absent->1 OK +evaluation.notification.retrigger_seconds absent->2 OK +evaluation.source.aggregation absent->x OK +evaluation.source.aggregation absent->y OK +evaluation.source.aggregation x->absent OK +evaluation.source.aggregation x->y OK +evaluation.source.aggregation y->absent OK +evaluation.source.aggregation y->x OK +evaluation.source.display absent->x OK +evaluation.source.display absent->y OK +evaluation.source.display x->absent OK +evaluation.source.display x->y OK +evaluation.source.display y->absent OK +evaluation.source.display y->x OK +evaluation.source.name x->y OK +evaluation.source.name y->x OK +evaluation.state absent->x OK +evaluation.state absent->y OK +evaluation.state x->absent OK +evaluation.state x->y OK +evaluation.state y->absent OK +evaluation.state y->x OK +evaluation.threshold.column.aggregation absent->x OK +evaluation.threshold.column.aggregation absent->y OK +evaluation.threshold.column.aggregation x->absent OK +evaluation.threshold.column.aggregation x->y OK +evaluation.threshold.column.aggregation y->absent OK +evaluation.threshold.column.aggregation y->x OK +evaluation.threshold.column.display absent->x OK +evaluation.threshold.column.display absent->y OK +evaluation.threshold.column.display x->absent OK +evaluation.threshold.column.display x->y OK +evaluation.threshold.column.display y->absent OK +evaluation.threshold.column.display y->x OK +evaluation.threshold.column.name x->y OK +evaluation.threshold.column.name y->x OK +evaluation.threshold.value.bool_value absent->false SUPPRESSED empty +evaluation.threshold.value.bool_value absent->true OK +evaluation.threshold.value.bool_value false->absent SUPPRESSED empty +evaluation.threshold.value.bool_value false->true OK +evaluation.threshold.value.bool_value true->absent OK +evaluation.threshold.value.bool_value true->false OK +evaluation.threshold.value.double_value 1->2 OK +evaluation.threshold.value.double_value 1->absent OK +evaluation.threshold.value.double_value 2->1 OK +evaluation.threshold.value.double_value 2->absent OK +evaluation.threshold.value.double_value absent->1 OK +evaluation.threshold.value.double_value absent->2 OK +evaluation.threshold.value.string_value absent->x OK +evaluation.threshold.value.string_value absent->y OK +evaluation.threshold.value.string_value x->absent OK +evaluation.threshold.value.string_value x->y OK +evaluation.threshold.value.string_value y->absent OK +evaluation.threshold.value.string_value y->x OK +file_path absent->x NOT_OBSERVABLE +file_path absent->y NOT_OBSERVABLE +file_path x->absent NOT_OBSERVABLE +file_path x->y NOT_OBSERVABLE +file_path y->absent NOT_OBSERVABLE +file_path y->x NOT_OBSERVABLE +id absent->x SUPPRESSED spec:output_only +id absent->y SUPPRESSED spec:output_only +id x->absent SUPPRESSED spec:output_only +id x->y SUPPRESSED spec:output_only +id y->absent SUPPRESSED spec:output_only +id y->x SUPPRESSED spec:output_only +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle_state absent->x OK +lifecycle_state absent->y OK +lifecycle_state x->absent OK +lifecycle_state x->y OK +lifecycle_state y->absent OK +lifecycle_state y->x OK +owner_user_name absent->x OK +owner_user_name absent->y OK +owner_user_name x->absent OK +owner_user_name x->y OK +owner_user_name y->absent OK +owner_user_name y->x OK +parent_path absent->x OK +parent_path absent->y OK +parent_path x->absent OK +parent_path x->y OK +parent_path y->absent OK +parent_path y->x OK +query_text x->y OK +query_text y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +run_as_user_name absent->x OK +run_as_user_name absent->y OK +run_as_user_name x->absent OK +run_as_user_name x->y OK +run_as_user_name y->absent OK +run_as_user_name y->x OK +schedule.pause_status absent->x OK +schedule.pause_status absent->y OK +schedule.pause_status x->absent OK +schedule.pause_status x->y OK +schedule.pause_status y->absent OK +schedule.pause_status y->x OK +schedule.quartz_cron_schedule x->y OK +schedule.quartz_cron_schedule y->x OK +schedule.timezone_id x->y OK +schedule.timezone_id y->x OK +update_time absent->x OK +update_time absent->y OK +update_time x->absent OK +update_time x->y OK +update_time y->absent OK +update_time y->x OK +warehouse_id x->y OK +warehouse_id y->x OK + +=== summary +OK 162 +SUPPRESSED 9 +NOT_OBSERVABLE 13 + +=== not covered: 9 fields under a slice or map +evaluation.notification.subscriptions[*].destination_id +evaluation.notification.subscriptions[*].user_email +parameters[*].name +parameters[*].type +parameters[*].value +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/dresources_test/out.apps.fields.txt b/bundle/direct/dresources_test/out.apps.fields.txt new file mode 100644 index 00000000000..07b1c9c31ac --- /dev/null +++ b/bundle/direct/dresources_test/out.apps.fields.txt @@ -0,0 +1,566 @@ +=== app.yml.tmpl +active_deployment.create_time absent->x OK +active_deployment.create_time absent->y OK +active_deployment.create_time x->absent OK +active_deployment.create_time x->y OK +active_deployment.create_time y->absent OK +active_deployment.create_time y->x OK +active_deployment.creator absent->x OK +active_deployment.creator absent->y OK +active_deployment.creator x->absent OK +active_deployment.creator x->y OK +active_deployment.creator y->absent OK +active_deployment.creator y->x OK +active_deployment.deployment_artifacts.source_code_path absent->x OK +active_deployment.deployment_artifacts.source_code_path absent->y OK +active_deployment.deployment_artifacts.source_code_path x->absent OK +active_deployment.deployment_artifacts.source_code_path x->y OK +active_deployment.deployment_artifacts.source_code_path y->absent OK +active_deployment.deployment_artifacts.source_code_path y->x OK +active_deployment.deployment_id absent->x OK +active_deployment.deployment_id absent->y OK +active_deployment.deployment_id x->absent OK +active_deployment.deployment_id x->y OK +active_deployment.deployment_id y->absent OK +active_deployment.deployment_id y->x OK +active_deployment.git_source.branch absent->x OK +active_deployment.git_source.branch absent->y OK +active_deployment.git_source.branch x->absent OK +active_deployment.git_source.branch x->y OK +active_deployment.git_source.branch y->absent OK +active_deployment.git_source.branch y->x OK +active_deployment.git_source.commit absent->x OK +active_deployment.git_source.commit absent->y OK +active_deployment.git_source.commit x->absent OK +active_deployment.git_source.commit x->y OK +active_deployment.git_source.commit y->absent OK +active_deployment.git_source.commit y->x OK +active_deployment.git_source.git_repository.auto_deploy absent->false OK +active_deployment.git_source.git_repository.auto_deploy absent->true OK +active_deployment.git_source.git_repository.auto_deploy false->absent OK +active_deployment.git_source.git_repository.auto_deploy false->true OK +active_deployment.git_source.git_repository.auto_deploy true->absent OK +active_deployment.git_source.git_repository.auto_deploy true->false OK +active_deployment.git_source.git_repository.caller_credential_id 1->2 OK +active_deployment.git_source.git_repository.caller_credential_id 1->absent OK +active_deployment.git_source.git_repository.caller_credential_id 2->1 OK +active_deployment.git_source.git_repository.caller_credential_id 2->absent OK +active_deployment.git_source.git_repository.caller_credential_id absent->1 OK +active_deployment.git_source.git_repository.caller_credential_id absent->2 OK +active_deployment.git_source.git_repository.provider x->y OK +active_deployment.git_source.git_repository.provider y->x OK +active_deployment.git_source.git_repository.url x->y OK +active_deployment.git_source.git_repository.url y->x OK +active_deployment.git_source.resolved_commit absent->x OK +active_deployment.git_source.resolved_commit absent->y OK +active_deployment.git_source.resolved_commit x->absent OK +active_deployment.git_source.resolved_commit x->y OK +active_deployment.git_source.resolved_commit y->absent OK +active_deployment.git_source.resolved_commit y->x OK +active_deployment.git_source.source_code_path absent->x OK +active_deployment.git_source.source_code_path absent->y OK +active_deployment.git_source.source_code_path x->absent OK +active_deployment.git_source.source_code_path x->y OK +active_deployment.git_source.source_code_path y->absent OK +active_deployment.git_source.source_code_path y->x OK +active_deployment.git_source.tag absent->x OK +active_deployment.git_source.tag absent->y OK +active_deployment.git_source.tag x->absent OK +active_deployment.git_source.tag x->y OK +active_deployment.git_source.tag y->absent OK +active_deployment.git_source.tag y->x OK +active_deployment.mode absent->x OK +active_deployment.mode absent->y OK +active_deployment.mode x->absent OK +active_deployment.mode x->y OK +active_deployment.mode y->absent OK +active_deployment.mode y->x OK +active_deployment.source_code_path absent->x OK +active_deployment.source_code_path absent->y OK +active_deployment.source_code_path x->absent OK +active_deployment.source_code_path x->y OK +active_deployment.source_code_path y->absent OK +active_deployment.source_code_path y->x OK +active_deployment.status.message absent->x OK +active_deployment.status.message absent->y OK +active_deployment.status.message x->absent OK +active_deployment.status.message x->y OK +active_deployment.status.message y->absent OK +active_deployment.status.message y->x OK +active_deployment.status.state absent->x OK +active_deployment.status.state absent->y OK +active_deployment.status.state x->absent OK +active_deployment.status.state x->y OK +active_deployment.status.state y->absent OK +active_deployment.status.state y->x OK +active_deployment.update_time absent->x OK +active_deployment.update_time absent->y OK +active_deployment.update_time x->absent OK +active_deployment.update_time x->y OK +active_deployment.update_time y->absent OK +active_deployment.update_time y->x OK +app_status.message absent->x OK +app_status.message absent->y OK +app_status.message x->absent OK +app_status.message x->y OK +app_status.message y->absent OK +app_status.message y->x OK +app_status.running_instances 1->2 OK +app_status.running_instances 1->absent OK +app_status.running_instances 2->1 OK +app_status.running_instances 2->absent OK +app_status.running_instances absent->1 OK +app_status.running_instances absent->2 OK +app_status.state absent->x OK +app_status.state absent->y OK +app_status.state x->absent OK +app_status.state x->y OK +app_status.state y->absent OK +app_status.state y->x OK +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent DRIFT budget_policy_id +budget_policy_id x->y OK +budget_policy_id y->absent DRIFT budget_policy_id +budget_policy_id y->x OK +compute_max_instances 1->2 OK +compute_max_instances 1->absent DRIFT compute_max_instances +compute_max_instances 2->1 OK +compute_max_instances 2->absent DRIFT compute_max_instances +compute_max_instances absent->1 OK +compute_max_instances absent->2 OK +compute_min_instances 1->2 OK +compute_min_instances 1->absent DRIFT compute_min_instances +compute_min_instances 2->1 OK +compute_min_instances 2->absent DRIFT compute_min_instances +compute_min_instances absent->1 OK +compute_min_instances absent->2 OK +compute_size LARGE->MEDIUM OK +compute_size LARGE->absent OK +compute_size MEDIUM->LARGE OK +compute_size MEDIUM->absent OK +compute_size absent->LARGE OK +compute_size absent->MEDIUM SUPPRESSED remote_already_set +compute_status.active_instances 1->2 OK +compute_status.active_instances 1->absent OK +compute_status.active_instances 2->1 OK +compute_status.active_instances 2->absent OK +compute_status.active_instances absent->1 OK +compute_status.active_instances absent->2 OK +compute_status.message absent->x OK +compute_status.message absent->y OK +compute_status.message x->absent OK +compute_status.message x->y OK +compute_status.message y->absent OK +compute_status.message y->x OK +compute_status.state absent->x OK +compute_status.state absent->y OK +compute_status.state x->absent OK +compute_status.state x->y OK +compute_status.state y->absent OK +compute_status.state y->x OK +create_time absent->x OK +create_time absent->y OK +create_time x->absent OK +create_time x->y OK +create_time y->absent OK +create_time y->x OK +creator absent->x OK +creator absent->y OK +creator x->absent OK +creator x->y OK +creator y->absent OK +creator y->x OK +default_git_source.branch absent->x OK +default_git_source.branch absent->y OK +default_git_source.branch x->absent OK +default_git_source.branch x->y OK +default_git_source.branch y->absent OK +default_git_source.branch y->x OK +default_git_source.commit absent->x OK +default_git_source.commit absent->y OK +default_git_source.commit x->absent OK +default_git_source.commit x->y OK +default_git_source.commit y->absent OK +default_git_source.commit y->x OK +default_git_source.git_repository.auto_deploy absent->false OK +default_git_source.git_repository.auto_deploy absent->true OK +default_git_source.git_repository.auto_deploy false->absent OK +default_git_source.git_repository.auto_deploy false->true OK +default_git_source.git_repository.auto_deploy true->absent OK +default_git_source.git_repository.auto_deploy true->false OK +default_git_source.git_repository.caller_credential_id 1->2 OK +default_git_source.git_repository.caller_credential_id 1->absent OK +default_git_source.git_repository.caller_credential_id 2->1 OK +default_git_source.git_repository.caller_credential_id 2->absent OK +default_git_source.git_repository.caller_credential_id absent->1 OK +default_git_source.git_repository.caller_credential_id absent->2 OK +default_git_source.git_repository.provider x->y OK +default_git_source.git_repository.provider y->x OK +default_git_source.git_repository.url x->y OK +default_git_source.git_repository.url y->x OK +default_git_source.resolved_commit absent->x OK +default_git_source.resolved_commit absent->y OK +default_git_source.resolved_commit x->absent OK +default_git_source.resolved_commit x->y OK +default_git_source.resolved_commit y->absent OK +default_git_source.resolved_commit y->x OK +default_git_source.source_code_path absent->x OK +default_git_source.source_code_path absent->y OK +default_git_source.source_code_path x->absent OK +default_git_source.source_code_path x->y OK +default_git_source.source_code_path y->absent OK +default_git_source.source_code_path y->x OK +default_git_source.tag absent->x OK +default_git_source.tag absent->y OK +default_git_source.tag x->absent OK +default_git_source.tag x->y OK +default_git_source.tag y->absent OK +default_git_source.tag y->x OK +default_source_code_path absent->x OK +default_source_code_path absent->y OK +default_source_code_path x->absent OK +default_source_code_path x->y OK +default_source_code_path y->absent OK +default_source_code_path y->x OK +description absent->x OK +description absent->y OK +description x->absent DRIFT description +description x->y OK +description y->absent DRIFT description +description y->x OK +effective_budget_policy_id absent->x OK +effective_budget_policy_id absent->y OK +effective_budget_policy_id x->absent OK +effective_budget_policy_id x->y OK +effective_budget_policy_id y->absent OK +effective_budget_policy_id y->x OK +effective_usage_policy_id absent->x OK +effective_usage_policy_id absent->y OK +effective_usage_policy_id x->absent OK +effective_usage_policy_id x->y OK +effective_usage_policy_id y->absent OK +effective_usage_policy_id y->x OK +forward_user_access_token absent->false OK +forward_user_access_token absent->true OK +forward_user_access_token false->absent SUPPRESSED empty +forward_user_access_token false->true OK +forward_user_access_token true->absent OK +forward_user_access_token true->false OK +git_repository.auto_deploy absent->false OK +git_repository.auto_deploy absent->true OK +git_repository.auto_deploy false->absent SUPPRESSED empty +git_repository.auto_deploy false->true OK +git_repository.auto_deploy true->absent DRIFT git_repository.auto_deploy +git_repository.auto_deploy true->false OK +git_repository.caller_credential_id 1->2 OK +git_repository.caller_credential_id 1->absent DRIFT git_repository.caller_credential_id +git_repository.caller_credential_id 2->1 OK +git_repository.caller_credential_id 2->absent DRIFT git_repository.caller_credential_id +git_repository.caller_credential_id absent->1 OK +git_repository.caller_credential_id absent->2 OK +git_repository.provider x->y OK +git_repository.provider y->x OK +git_repository.url x->y OK +git_repository.url y->x OK +git_source.branch absent->x NOT_OBSERVABLE +git_source.branch absent->y NOT_OBSERVABLE +git_source.branch x->absent NOT_OBSERVABLE +git_source.branch x->y NOT_OBSERVABLE +git_source.branch y->absent NOT_OBSERVABLE +git_source.branch y->x NOT_OBSERVABLE +git_source.commit absent->x NOT_OBSERVABLE +git_source.commit absent->y NOT_OBSERVABLE +git_source.commit x->absent NOT_OBSERVABLE +git_source.commit x->y NOT_OBSERVABLE +git_source.commit y->absent NOT_OBSERVABLE +git_source.commit y->x NOT_OBSERVABLE +git_source.git_repository.auto_deploy absent->false NOT_OBSERVABLE +git_source.git_repository.auto_deploy absent->true NOT_OBSERVABLE +git_source.git_repository.auto_deploy false->absent NOT_OBSERVABLE +git_source.git_repository.auto_deploy false->true NOT_OBSERVABLE +git_source.git_repository.auto_deploy true->absent NOT_OBSERVABLE +git_source.git_repository.auto_deploy true->false NOT_OBSERVABLE +git_source.git_repository.caller_credential_id 1->2 NOT_OBSERVABLE +git_source.git_repository.caller_credential_id 1->absent NOT_OBSERVABLE +git_source.git_repository.caller_credential_id 2->1 NOT_OBSERVABLE +git_source.git_repository.caller_credential_id 2->absent NOT_OBSERVABLE +git_source.git_repository.caller_credential_id absent->1 NOT_OBSERVABLE +git_source.git_repository.caller_credential_id absent->2 NOT_OBSERVABLE +git_source.git_repository.provider x->y NOT_OBSERVABLE +git_source.git_repository.provider y->x NOT_OBSERVABLE +git_source.git_repository.url x->y NOT_OBSERVABLE +git_source.git_repository.url y->x NOT_OBSERVABLE +git_source.resolved_commit absent->x NOT_OBSERVABLE +git_source.resolved_commit absent->y NOT_OBSERVABLE +git_source.resolved_commit x->absent NOT_OBSERVABLE +git_source.resolved_commit x->y NOT_OBSERVABLE +git_source.resolved_commit y->absent NOT_OBSERVABLE +git_source.resolved_commit y->x NOT_OBSERVABLE +git_source.source_code_path absent->x NOT_OBSERVABLE +git_source.source_code_path absent->y NOT_OBSERVABLE +git_source.source_code_path x->absent NOT_OBSERVABLE +git_source.source_code_path x->y NOT_OBSERVABLE +git_source.source_code_path y->absent NOT_OBSERVABLE +git_source.source_code_path y->x NOT_OBSERVABLE +git_source.tag absent->x NOT_OBSERVABLE +git_source.tag absent->y NOT_OBSERVABLE +git_source.tag x->absent NOT_OBSERVABLE +git_source.tag x->y NOT_OBSERVABLE +git_source.tag y->absent NOT_OBSERVABLE +git_source.tag y->x NOT_OBSERVABLE +id absent->x SUPPRESSED spec:output_only +id absent->y SUPPRESSED spec:output_only +id x->absent SUPPRESSED spec:output_only +id x->y SUPPRESSED spec:output_only +id y->absent SUPPRESSED spec:output_only +id y->x SUPPRESSED spec:output_only +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle.started absent->false NOT_OBSERVABLE +lifecycle.started absent->true OK +lifecycle.started false->absent OK +lifecycle.started false->true OK +lifecycle.started true->absent OK +lifecycle.started true->false OK +name - SKIPPED rename waits out an asynchronous delete +oauth2_app_client_id absent->x OK +oauth2_app_client_id absent->y OK +oauth2_app_client_id x->absent OK +oauth2_app_client_id x->y OK +oauth2_app_client_id y->absent OK +oauth2_app_client_id y->x OK +oauth2_app_integration_id absent->x OK +oauth2_app_integration_id absent->y OK +oauth2_app_integration_id x->absent OK +oauth2_app_integration_id x->y OK +oauth2_app_integration_id y->absent OK +oauth2_app_integration_id y->x OK +pending_deployment.create_time absent->x OK +pending_deployment.create_time absent->y OK +pending_deployment.create_time x->absent OK +pending_deployment.create_time x->y OK +pending_deployment.create_time y->absent OK +pending_deployment.create_time y->x OK +pending_deployment.creator absent->x OK +pending_deployment.creator absent->y OK +pending_deployment.creator x->absent OK +pending_deployment.creator x->y OK +pending_deployment.creator y->absent OK +pending_deployment.creator y->x OK +pending_deployment.deployment_artifacts.source_code_path absent->x OK +pending_deployment.deployment_artifacts.source_code_path absent->y OK +pending_deployment.deployment_artifacts.source_code_path x->absent OK +pending_deployment.deployment_artifacts.source_code_path x->y OK +pending_deployment.deployment_artifacts.source_code_path y->absent OK +pending_deployment.deployment_artifacts.source_code_path y->x OK +pending_deployment.deployment_id absent->x OK +pending_deployment.deployment_id absent->y OK +pending_deployment.deployment_id x->absent OK +pending_deployment.deployment_id x->y OK +pending_deployment.deployment_id y->absent OK +pending_deployment.deployment_id y->x OK +pending_deployment.git_source.branch absent->x OK +pending_deployment.git_source.branch absent->y OK +pending_deployment.git_source.branch x->absent OK +pending_deployment.git_source.branch x->y OK +pending_deployment.git_source.branch y->absent OK +pending_deployment.git_source.branch y->x OK +pending_deployment.git_source.commit absent->x OK +pending_deployment.git_source.commit absent->y OK +pending_deployment.git_source.commit x->absent OK +pending_deployment.git_source.commit x->y OK +pending_deployment.git_source.commit y->absent OK +pending_deployment.git_source.commit y->x OK +pending_deployment.git_source.git_repository.auto_deploy absent->false OK +pending_deployment.git_source.git_repository.auto_deploy absent->true OK +pending_deployment.git_source.git_repository.auto_deploy false->absent OK +pending_deployment.git_source.git_repository.auto_deploy false->true OK +pending_deployment.git_source.git_repository.auto_deploy true->absent OK +pending_deployment.git_source.git_repository.auto_deploy true->false OK +pending_deployment.git_source.git_repository.caller_credential_id 1->2 OK +pending_deployment.git_source.git_repository.caller_credential_id 1->absent OK +pending_deployment.git_source.git_repository.caller_credential_id 2->1 OK +pending_deployment.git_source.git_repository.caller_credential_id 2->absent OK +pending_deployment.git_source.git_repository.caller_credential_id absent->1 OK +pending_deployment.git_source.git_repository.caller_credential_id absent->2 OK +pending_deployment.git_source.git_repository.provider x->y OK +pending_deployment.git_source.git_repository.provider y->x OK +pending_deployment.git_source.git_repository.url x->y OK +pending_deployment.git_source.git_repository.url y->x OK +pending_deployment.git_source.resolved_commit absent->x OK +pending_deployment.git_source.resolved_commit absent->y OK +pending_deployment.git_source.resolved_commit x->absent OK +pending_deployment.git_source.resolved_commit x->y OK +pending_deployment.git_source.resolved_commit y->absent OK +pending_deployment.git_source.resolved_commit y->x OK +pending_deployment.git_source.source_code_path absent->x OK +pending_deployment.git_source.source_code_path absent->y OK +pending_deployment.git_source.source_code_path x->absent OK +pending_deployment.git_source.source_code_path x->y OK +pending_deployment.git_source.source_code_path y->absent OK +pending_deployment.git_source.source_code_path y->x OK +pending_deployment.git_source.tag absent->x OK +pending_deployment.git_source.tag absent->y OK +pending_deployment.git_source.tag x->absent OK +pending_deployment.git_source.tag x->y OK +pending_deployment.git_source.tag y->absent OK +pending_deployment.git_source.tag y->x OK +pending_deployment.mode absent->x OK +pending_deployment.mode absent->y OK +pending_deployment.mode x->absent OK +pending_deployment.mode x->y OK +pending_deployment.mode y->absent OK +pending_deployment.mode y->x OK +pending_deployment.source_code_path absent->x OK +pending_deployment.source_code_path absent->y OK +pending_deployment.source_code_path x->absent OK +pending_deployment.source_code_path x->y OK +pending_deployment.source_code_path y->absent OK +pending_deployment.source_code_path y->x OK +pending_deployment.status.message absent->x OK +pending_deployment.status.message absent->y OK +pending_deployment.status.message x->absent OK +pending_deployment.status.message x->y OK +pending_deployment.status.message y->absent OK +pending_deployment.status.message y->x OK +pending_deployment.status.state absent->x OK +pending_deployment.status.state absent->y OK +pending_deployment.status.state x->absent OK +pending_deployment.status.state x->y OK +pending_deployment.status.state y->absent OK +pending_deployment.status.state y->x OK +pending_deployment.update_time absent->x OK +pending_deployment.update_time absent->y OK +pending_deployment.update_time x->absent OK +pending_deployment.update_time x->y OK +pending_deployment.update_time y->absent OK +pending_deployment.update_time y->x OK +service_principal_client_id absent->x OK +service_principal_client_id absent->y OK +service_principal_client_id x->absent OK +service_principal_client_id x->y OK +service_principal_client_id y->absent OK +service_principal_client_id y->x OK +service_principal_id 1->2 OK +service_principal_id 1->absent OK +service_principal_id 2->1 OK +service_principal_id 2->absent OK +service_principal_id absent->1 OK +service_principal_id absent->2 OK +service_principal_name absent->x OK +service_principal_name absent->y OK +service_principal_name x->absent OK +service_principal_name x->y OK +service_principal_name y->absent OK +service_principal_name y->x OK +source_code_path absent->x SUPPRESSED no active deployment +source_code_path absent->y SUPPRESSED no active deployment +source_code_path x->absent SUPPRESSED no active deployment +source_code_path x->y SUPPRESSED no active deployment +source_code_path y->absent SUPPRESSED no active deployment +source_code_path y->x SUPPRESSED no active deployment +space absent->x OK +space absent->y OK +space x->absent OK +space x->y OK +space y->absent OK +space y->x OK +thumbnail_url absent->x OK +thumbnail_url absent->y OK +thumbnail_url x->absent OK +thumbnail_url x->y OK +thumbnail_url y->absent OK +thumbnail_url y->x OK +update_time absent->x OK +update_time absent->y OK +update_time x->absent OK +update_time x->y OK +update_time y->absent OK +update_time y->x OK +updater absent->x OK +updater absent->y OK +updater x->absent OK +updater x->y OK +updater y->absent OK +updater y->x OK +url absent->x SUPPRESSED spec:output_only +url absent->y SUPPRESSED spec:output_only +url x->absent SUPPRESSED spec:output_only +url x->y SUPPRESSED spec:output_only +url y->absent SUPPRESSED spec:output_only +url y->x SUPPRESSED spec:output_only +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent DRIFT usage_policy_id +usage_policy_id x->y OK +usage_policy_id y->absent DRIFT usage_policy_id +usage_policy_id y->x OK + +=== summary +OK 413 +SUPPRESSED 21 +NOT_OBSERVABLE 59 +DRIFT 13 +SKIPPED 1 + +=== not covered: 49 fields under a slice or map +active_deployment.command[*] +active_deployment.env_vars[*].name +active_deployment.env_vars[*].value +active_deployment.env_vars[*].value_from +config.command[*] +config.env[*].name +config.env[*].value +config.env[*].value_from +effective_user_api_scopes[*] +pending_deployment.command[*] +pending_deployment.env_vars[*].name +pending_deployment.env_vars[*].value +pending_deployment.env_vars[*].value_from +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +resources[*].app.name +resources[*].app.permission +resources[*].database.database_name +resources[*].database.instance_name +resources[*].database.permission +resources[*].description +resources[*].experiment.experiment_id +resources[*].experiment.permission +resources[*].genie_space.name +resources[*].genie_space.permission +resources[*].genie_space.space_id +resources[*].job.id +resources[*].job.permission +resources[*].name +resources[*].postgres.branch +resources[*].postgres.database +resources[*].postgres.permission +resources[*].secret.key +resources[*].secret.permission +resources[*].secret.scope +resources[*].serving_endpoint.name +resources[*].serving_endpoint.permission +resources[*].sql_warehouse.id +resources[*].sql_warehouse.permission +resources[*].uc_securable.permission +resources[*].uc_securable.securable_full_name +resources[*].uc_securable.securable_kind +resources[*].uc_securable.securable_type +telemetry_export_destinations[*].unity_catalog.logs_table +telemetry_export_destinations[*].unity_catalog.metrics_table +telemetry_export_destinations[*].unity_catalog.traces_table +user_api_scopes[*] diff --git a/bundle/direct/dresources_test/out.catalogs.fields.txt b/bundle/direct/dresources_test/out.catalogs.fields.txt new file mode 100644 index 00000000000..4d7657670b5 --- /dev/null +++ b/bundle/direct/dresources_test/out.catalogs.fields.txt @@ -0,0 +1,155 @@ +=== catalog.yml.tmpl +comment absent->x OK +comment absent->y OK +comment x->absent DRIFT comment +comment x->y OK +comment y->absent DRIFT comment +comment y->x OK +connection_name absent->x OK_RECREATE +connection_name absent->y OK_RECREATE +connection_name x->absent OK_RECREATE +connection_name x->y OK_RECREATE +connection_name y->absent OK_RECREATE +connection_name y->x OK_RECREATE +custom_max_retention_hours 1->2 OK +custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours +custom_max_retention_hours 2->1 OK +custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours +custom_max_retention_hours absent->1 OK +custom_max_retention_hours absent->2 OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent->x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent->y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x->absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x->y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y->absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y->x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent->x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent->y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x->absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x->y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y->absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y->x OK +managed_encryption_settings.azure_encryption_settings.azure_tenant_id x->y OK +managed_encryption_settings.azure_encryption_settings.azure_tenant_id y->x OK +managed_encryption_settings.azure_key_vault_key_id absent->x OK +managed_encryption_settings.azure_key_vault_key_id absent->y OK +managed_encryption_settings.azure_key_vault_key_id x->absent OK +managed_encryption_settings.azure_key_vault_key_id x->y OK +managed_encryption_settings.azure_key_vault_key_id y->absent OK +managed_encryption_settings.azure_key_vault_key_id y->x OK +managed_encryption_settings.customer_managed_key_id absent->x OK +managed_encryption_settings.customer_managed_key_id absent->y OK +managed_encryption_settings.customer_managed_key_id x->absent OK +managed_encryption_settings.customer_managed_key_id x->y OK +managed_encryption_settings.customer_managed_key_id y->absent OK +managed_encryption_settings.customer_managed_key_id y->x OK +name x->y OK +name y->x OK +provider_name absent->x OK_RECREATE +provider_name absent->y OK_RECREATE +provider_name x->absent OK_RECREATE +provider_name x->y OK_RECREATE +provider_name y->absent OK_RECREATE +provider_name y->x OK_RECREATE +share_name absent->x OK_RECREATE +share_name absent->y OK_RECREATE +share_name x->absent OK_RECREATE +share_name x->y OK_RECREATE +share_name y->absent OK_RECREATE +share_name y->x OK_RECREATE +storage_root absent->x OK_RECREATE +storage_root absent->y OK_RECREATE +storage_root x->absent OK_RECREATE +storage_root x->y OK_RECREATE +storage_root y->absent OK_RECREATE +storage_root y->x OK_RECREATE + +=== catalog_optional_fields.yml.tmpl +comment absent->x OK +comment absent->y OK +comment x->absent DRIFT comment +comment x->y OK +comment y->absent DRIFT comment +comment y->x OK +connection_name absent->x OK_RECREATE +connection_name absent->y OK_RECREATE +connection_name x->absent OK_RECREATE +connection_name x->y OK_RECREATE +connection_name y->absent OK_RECREATE +connection_name y->x OK_RECREATE +custom_max_retention_hours 1->2 OK +custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours +custom_max_retention_hours 2->1 OK +custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours +custom_max_retention_hours absent->1 OK +custom_max_retention_hours absent->2 OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent->x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent->y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x->absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x->y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y->absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y->x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent->x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent->y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x->absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x->y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y->absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y->x OK +managed_encryption_settings.azure_encryption_settings.azure_tenant_id x->y OK +managed_encryption_settings.azure_encryption_settings.azure_tenant_id y->x OK +managed_encryption_settings.azure_key_vault_key_id absent->x OK +managed_encryption_settings.azure_key_vault_key_id absent->y OK +managed_encryption_settings.azure_key_vault_key_id x->absent OK +managed_encryption_settings.azure_key_vault_key_id x->y OK +managed_encryption_settings.azure_key_vault_key_id y->absent OK +managed_encryption_settings.azure_key_vault_key_id y->x OK +managed_encryption_settings.customer_managed_key_id absent->x OK +managed_encryption_settings.customer_managed_key_id absent->y OK +managed_encryption_settings.customer_managed_key_id x->absent OK +managed_encryption_settings.customer_managed_key_id x->y OK +managed_encryption_settings.customer_managed_key_id y->absent OK +managed_encryption_settings.customer_managed_key_id y->x OK +name x->y OK +name y->x OK +provider_name absent->x OK_RECREATE +provider_name absent->y OK_RECREATE +provider_name x->absent OK_RECREATE +provider_name x->y OK_RECREATE +provider_name y->absent OK_RECREATE +provider_name y->x OK_RECREATE +share_name absent->x OK_RECREATE +share_name absent->y OK_RECREATE +share_name x->absent OK_RECREATE +share_name x->y OK_RECREATE +share_name y->absent OK_RECREATE +share_name y->x OK_RECREATE +storage_root absent->x OK_RECREATE +storage_root absent->y OK_RECREATE +storage_root x->absent OK_RECREATE +storage_root x->y OK_RECREATE +storage_root y->absent OK_RECREATE +storage_root y->x OK_RECREATE + +=== summary +OK 72 +OK_RECREATE 48 +NOT_OBSERVABLE 12 +DRIFT 8 + +=== not covered: 4 fields under a slice or map +grants[*].principal +grants[*].privileges[*] +options.* +properties.* diff --git a/bundle/direct/dresources_test/out.cluster_policies.fields.txt b/bundle/direct/dresources_test/out.cluster_policies.fields.txt new file mode 100644 index 00000000000..758917cead1 --- /dev/null +++ b/bundle/direct/dresources_test/out.cluster_policies.fields.txt @@ -0,0 +1,64 @@ +=== cluster_policy.yml.tmpl +definition absent->x OK +definition absent->y OK +definition x->absent OK +definition x->y OK +definition y->absent OK +definition y->x OK +description absent->x OK +description absent->y OK +description x->absent OK +description x->y OK +description y->absent OK +description y->x OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +max_clusters_per_user 1->2 OK +max_clusters_per_user 1->absent OK +max_clusters_per_user 2->1 OK +max_clusters_per_user 2->absent OK +max_clusters_per_user absent->1 OK +max_clusters_per_user absent->2 OK +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +policy_family_definition_overrides absent->x OK +policy_family_definition_overrides absent->y OK +policy_family_definition_overrides x->absent OK +policy_family_definition_overrides x->y OK +policy_family_definition_overrides y->absent OK +policy_family_definition_overrides y->x OK +policy_family_id absent->x OK +policy_family_id absent->y OK +policy_family_id x->absent OK +policy_family_id x->y OK +policy_family_id y->absent OK +policy_family_id y->x OK + +=== summary +OK 36 +NOT_OBSERVABLE 6 + +=== not covered: 15 fields under a slice or map +libraries[*].cran.package +libraries[*].cran.repo +libraries[*].egg +libraries[*].jar +libraries[*].maven.coordinates +libraries[*].maven.exclusions[*] +libraries[*].maven.repo +libraries[*].pypi.package +libraries[*].pypi.repo +libraries[*].requirements +libraries[*].whl +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/dresources_test/out.clusters.fields.txt b/bundle/direct/dresources_test/out.clusters.fields.txt new file mode 100644 index 00000000000..2e123e1c86e --- /dev/null +++ b/bundle/direct/dresources_test/out.clusters.fields.txt @@ -0,0 +1,777 @@ +=== cluster.yml.tmpl +apply_policy_default_values absent->false SUPPRESSED empty +apply_policy_default_values absent->true OK +apply_policy_default_values false->absent SUPPRESSED remote_already_set +apply_policy_default_values false->true OK +apply_policy_default_values true->absent OK +apply_policy_default_values true->false OK +autoscale.max_workers 1->2 OK +autoscale.max_workers 1->absent OK +autoscale.max_workers 2->1 OK +autoscale.max_workers 2->absent OK +autoscale.max_workers absent->1 OK +autoscale.max_workers absent->2 OK +autoscale.min_workers 1->2 OK +autoscale.min_workers 1->absent OK +autoscale.min_workers 2->1 OK +autoscale.min_workers 2->absent OK +autoscale.min_workers absent->1 OK +autoscale.min_workers absent->2 OK +autotermination_minutes 1->2 OK +autotermination_minutes 1->absent OK +autotermination_minutes 2->1 OK +autotermination_minutes 2->absent OK +autotermination_minutes absent->1 OK +autotermination_minutes absent->2 OK +aws_attributes.availability absent->x OK +aws_attributes.availability absent->y OK +aws_attributes.availability x->absent OK +aws_attributes.availability x->y OK +aws_attributes.availability y->absent OK +aws_attributes.availability y->x OK +aws_attributes.ebs_volume_count 1->2 OK +aws_attributes.ebs_volume_count 1->absent OK +aws_attributes.ebs_volume_count 2->1 OK +aws_attributes.ebs_volume_count 2->absent OK +aws_attributes.ebs_volume_count absent->1 OK +aws_attributes.ebs_volume_count absent->2 OK +aws_attributes.ebs_volume_iops 1->2 OK +aws_attributes.ebs_volume_iops 1->absent OK +aws_attributes.ebs_volume_iops 2->1 OK +aws_attributes.ebs_volume_iops 2->absent OK +aws_attributes.ebs_volume_iops absent->1 OK +aws_attributes.ebs_volume_iops absent->2 OK +aws_attributes.ebs_volume_size 1->2 OK +aws_attributes.ebs_volume_size 1->absent OK +aws_attributes.ebs_volume_size 2->1 OK +aws_attributes.ebs_volume_size 2->absent OK +aws_attributes.ebs_volume_size absent->1 OK +aws_attributes.ebs_volume_size absent->2 OK +aws_attributes.ebs_volume_throughput 1->2 OK +aws_attributes.ebs_volume_throughput 1->absent OK +aws_attributes.ebs_volume_throughput 2->1 OK +aws_attributes.ebs_volume_throughput 2->absent OK +aws_attributes.ebs_volume_throughput absent->1 OK +aws_attributes.ebs_volume_throughput absent->2 OK +aws_attributes.ebs_volume_type absent->x OK +aws_attributes.ebs_volume_type absent->y OK +aws_attributes.ebs_volume_type x->absent OK +aws_attributes.ebs_volume_type x->y OK +aws_attributes.ebs_volume_type y->absent OK +aws_attributes.ebs_volume_type y->x OK +aws_attributes.first_on_demand 1->2 OK +aws_attributes.first_on_demand 1->absent OK +aws_attributes.first_on_demand 2->1 OK +aws_attributes.first_on_demand 2->absent OK +aws_attributes.first_on_demand absent->1 OK +aws_attributes.first_on_demand absent->2 OK +aws_attributes.instance_profile_arn absent->x OK +aws_attributes.instance_profile_arn absent->y OK +aws_attributes.instance_profile_arn x->absent OK +aws_attributes.instance_profile_arn x->y OK +aws_attributes.instance_profile_arn y->absent OK +aws_attributes.instance_profile_arn y->x OK +aws_attributes.spot_bid_price_percent 1->2 OK +aws_attributes.spot_bid_price_percent 1->absent OK +aws_attributes.spot_bid_price_percent 2->1 OK +aws_attributes.spot_bid_price_percent 2->absent OK +aws_attributes.spot_bid_price_percent absent->1 OK +aws_attributes.spot_bid_price_percent absent->2 OK +aws_attributes.zone_id absent->x OK +aws_attributes.zone_id absent->y OK +aws_attributes.zone_id x->absent OK +aws_attributes.zone_id x->y OK +aws_attributes.zone_id y->absent OK +aws_attributes.zone_id y->x OK +azure_attributes.availability absent->x OK +azure_attributes.availability absent->y OK +azure_attributes.availability x->absent OK +azure_attributes.availability x->y OK +azure_attributes.availability y->absent OK +azure_attributes.availability y->x OK +azure_attributes.capacity_reservation_group absent->x OK +azure_attributes.capacity_reservation_group absent->y OK +azure_attributes.capacity_reservation_group x->absent OK +azure_attributes.capacity_reservation_group x->y OK +azure_attributes.capacity_reservation_group y->absent OK +azure_attributes.capacity_reservation_group y->x OK +azure_attributes.first_on_demand 1->2 OK +azure_attributes.first_on_demand 1->absent OK +azure_attributes.first_on_demand 2->1 OK +azure_attributes.first_on_demand 2->absent OK +azure_attributes.first_on_demand absent->1 OK +azure_attributes.first_on_demand absent->2 OK +azure_attributes.log_analytics_info.log_analytics_primary_key absent->x OK +azure_attributes.log_analytics_info.log_analytics_primary_key absent->y OK +azure_attributes.log_analytics_info.log_analytics_primary_key x->absent OK +azure_attributes.log_analytics_info.log_analytics_primary_key x->y OK +azure_attributes.log_analytics_info.log_analytics_primary_key y->absent OK +azure_attributes.log_analytics_info.log_analytics_primary_key y->x OK +azure_attributes.log_analytics_info.log_analytics_workspace_id absent->x OK +azure_attributes.log_analytics_info.log_analytics_workspace_id absent->y OK +azure_attributes.log_analytics_info.log_analytics_workspace_id x->absent OK +azure_attributes.log_analytics_info.log_analytics_workspace_id x->y OK +azure_attributes.log_analytics_info.log_analytics_workspace_id y->absent OK +azure_attributes.log_analytics_info.log_analytics_workspace_id y->x OK +azure_attributes.spot_bid_max_price 1->2 OK +azure_attributes.spot_bid_max_price 1->absent OK +azure_attributes.spot_bid_max_price 2->1 OK +azure_attributes.spot_bid_max_price 2->absent OK +azure_attributes.spot_bid_max_price absent->1 OK +azure_attributes.spot_bid_max_price absent->2 OK +cluster_log_conf.dbfs.destination x->y OK +cluster_log_conf.dbfs.destination y->x OK +cluster_log_conf.s3.canned_acl absent->x OK +cluster_log_conf.s3.canned_acl absent->y OK +cluster_log_conf.s3.canned_acl x->absent OK +cluster_log_conf.s3.canned_acl x->y OK +cluster_log_conf.s3.canned_acl y->absent OK +cluster_log_conf.s3.canned_acl y->x OK +cluster_log_conf.s3.destination x->y OK +cluster_log_conf.s3.destination y->x OK +cluster_log_conf.s3.enable_encryption absent->false SUPPRESSED empty +cluster_log_conf.s3.enable_encryption absent->true OK +cluster_log_conf.s3.enable_encryption false->absent SUPPRESSED empty +cluster_log_conf.s3.enable_encryption false->true OK +cluster_log_conf.s3.enable_encryption true->absent OK +cluster_log_conf.s3.enable_encryption true->false OK +cluster_log_conf.s3.encryption_type absent->x OK +cluster_log_conf.s3.encryption_type absent->y OK +cluster_log_conf.s3.encryption_type x->absent OK +cluster_log_conf.s3.encryption_type x->y OK +cluster_log_conf.s3.encryption_type y->absent OK +cluster_log_conf.s3.encryption_type y->x OK +cluster_log_conf.s3.endpoint absent->x OK +cluster_log_conf.s3.endpoint absent->y OK +cluster_log_conf.s3.endpoint x->absent OK +cluster_log_conf.s3.endpoint x->y OK +cluster_log_conf.s3.endpoint y->absent OK +cluster_log_conf.s3.endpoint y->x OK +cluster_log_conf.s3.kms_key absent->x OK +cluster_log_conf.s3.kms_key absent->y OK +cluster_log_conf.s3.kms_key x->absent OK +cluster_log_conf.s3.kms_key x->y OK +cluster_log_conf.s3.kms_key y->absent OK +cluster_log_conf.s3.kms_key y->x OK +cluster_log_conf.s3.region absent->x OK +cluster_log_conf.s3.region absent->y OK +cluster_log_conf.s3.region x->absent OK +cluster_log_conf.s3.region x->y OK +cluster_log_conf.s3.region y->absent OK +cluster_log_conf.s3.region y->x OK +cluster_log_conf.volumes.destination x->y OK +cluster_log_conf.volumes.destination y->x OK +cluster_name absent->x OK +cluster_name absent->y OK +cluster_name x->absent OK +cluster_name x->y OK +cluster_name y->absent OK +cluster_name y->x OK +data_security_mode absent->x OK +data_security_mode absent->y OK +data_security_mode x->absent OK +data_security_mode x->y OK +data_security_mode y->absent OK +data_security_mode y->x OK +dependency_mode absent->x OK +dependency_mode absent->y OK +dependency_mode x->absent OK +dependency_mode x->y OK +dependency_mode y->absent OK +dependency_mode y->x OK +docker_image.basic_auth.password absent->x OK +docker_image.basic_auth.password absent->y OK +docker_image.basic_auth.password x->absent OK +docker_image.basic_auth.password x->y OK +docker_image.basic_auth.password y->absent OK +docker_image.basic_auth.password y->x OK +docker_image.basic_auth.username absent->x OK +docker_image.basic_auth.username absent->y OK +docker_image.basic_auth.username x->absent OK +docker_image.basic_auth.username x->y OK +docker_image.basic_auth.username y->absent OK +docker_image.basic_auth.username y->x OK +docker_image.url absent->x OK +docker_image.url absent->y OK +docker_image.url x->absent OK +docker_image.url x->y OK +docker_image.url y->absent OK +docker_image.url y->x OK +driver_instance_pool_id absent->x OK +driver_instance_pool_id absent->y OK +driver_instance_pool_id x->absent OK +driver_instance_pool_id x->y OK +driver_instance_pool_id y->absent OK +driver_instance_pool_id y->x OK +driver_node_type_id absent->x OK +driver_node_type_id absent->y OK +driver_node_type_id x->absent OK +driver_node_type_id x->y OK +driver_node_type_id y->absent OK +driver_node_type_id y->x OK +enable_elastic_disk absent->false SUPPRESSED remote_already_set +enable_elastic_disk absent->true OK +enable_elastic_disk false->absent SUPPRESSED empty +enable_elastic_disk false->true OK +enable_elastic_disk true->absent OK +enable_elastic_disk true->false OK +enable_local_disk_encryption absent->false SUPPRESSED empty +enable_local_disk_encryption absent->true OK +enable_local_disk_encryption false->absent SUPPRESSED empty +enable_local_disk_encryption false->true OK +enable_local_disk_encryption true->absent OK +enable_local_disk_encryption true->false OK +gcp_attributes.availability absent->x OK +gcp_attributes.availability absent->y OK +gcp_attributes.availability x->absent OK +gcp_attributes.availability x->y OK +gcp_attributes.availability y->absent OK +gcp_attributes.availability y->x OK +gcp_attributes.boot_disk_size 1->2 OK +gcp_attributes.boot_disk_size 1->absent OK +gcp_attributes.boot_disk_size 2->1 OK +gcp_attributes.boot_disk_size 2->absent OK +gcp_attributes.boot_disk_size absent->1 OK +gcp_attributes.boot_disk_size absent->2 OK +gcp_attributes.confidential_compute_type absent->x OK +gcp_attributes.confidential_compute_type absent->y OK +gcp_attributes.confidential_compute_type x->absent OK +gcp_attributes.confidential_compute_type x->y OK +gcp_attributes.confidential_compute_type y->absent OK +gcp_attributes.confidential_compute_type y->x OK +gcp_attributes.first_on_demand 1->2 OK +gcp_attributes.first_on_demand 1->absent OK +gcp_attributes.first_on_demand 2->1 OK +gcp_attributes.first_on_demand 2->absent OK +gcp_attributes.first_on_demand absent->1 OK +gcp_attributes.first_on_demand absent->2 OK +gcp_attributes.google_service_account absent->x OK +gcp_attributes.google_service_account absent->y OK +gcp_attributes.google_service_account x->absent OK +gcp_attributes.google_service_account x->y OK +gcp_attributes.google_service_account y->absent OK +gcp_attributes.google_service_account y->x OK +gcp_attributes.local_ssd_count 1->2 OK +gcp_attributes.local_ssd_count 1->absent OK +gcp_attributes.local_ssd_count 2->1 OK +gcp_attributes.local_ssd_count 2->absent OK +gcp_attributes.local_ssd_count absent->1 OK +gcp_attributes.local_ssd_count absent->2 OK +gcp_attributes.use_preemptible_executors absent->false SUPPRESSED empty +gcp_attributes.use_preemptible_executors absent->true OK +gcp_attributes.use_preemptible_executors false->absent SUPPRESSED empty +gcp_attributes.use_preemptible_executors false->true OK +gcp_attributes.use_preemptible_executors true->absent OK +gcp_attributes.use_preemptible_executors true->false OK +gcp_attributes.zone_id absent->x OK +gcp_attributes.zone_id absent->y OK +gcp_attributes.zone_id x->absent OK +gcp_attributes.zone_id x->y OK +gcp_attributes.zone_id y->absent OK +gcp_attributes.zone_id y->x OK +instance_pool_id absent->x OK +instance_pool_id absent->y OK +instance_pool_id x->absent OK +instance_pool_id x->y OK +instance_pool_id y->absent OK +instance_pool_id y->x OK +is_single_node absent->false SUPPRESSED empty +is_single_node absent->true OK +is_single_node false->absent SUPPRESSED empty +is_single_node false->true OK +is_single_node true->absent OK +is_single_node true->false OK +kind absent->x OK +kind absent->y OK +kind x->absent OK +kind x->y OK +kind y->absent OK +kind y->x OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle.started absent->false OK +lifecycle.started absent->true OK +lifecycle.started false->absent OK +lifecycle.started false->true OK +lifecycle.started true->absent OK +lifecycle.started true->false OK +node_type_id absent->x OK +node_type_id absent->y OK +node_type_id x->absent OK +node_type_id x->y OK +node_type_id y->absent OK +node_type_id y->x OK +num_workers 1->2 OK +num_workers 1->absent OK +num_workers 2->1 OK +num_workers 2->absent OK +num_workers absent->1 OK +num_workers absent->2 OK +policy_id absent->x OK +policy_id absent->y OK +policy_id x->absent OK +policy_id x->y OK +policy_id y->absent OK +policy_id y->x OK +remote_disk_throughput 1->2 OK +remote_disk_throughput 1->absent OK +remote_disk_throughput 2->1 OK +remote_disk_throughput 2->absent OK +remote_disk_throughput absent->1 OK +remote_disk_throughput absent->2 OK +runtime_engine absent->x OK +runtime_engine absent->y OK +runtime_engine x->absent OK +runtime_engine x->y OK +runtime_engine y->absent OK +runtime_engine y->x OK +single_user_name absent->x OK +single_user_name absent->y OK +single_user_name x->absent OK +single_user_name x->y OK +single_user_name y->absent OK +single_user_name y->x OK +spark_version absent->x OK +spark_version absent->y OK +spark_version x->absent OK +spark_version x->y OK +spark_version y->absent OK +spark_version y->x OK +total_initial_remote_disk_size 1->2 OK +total_initial_remote_disk_size 1->absent OK +total_initial_remote_disk_size 2->1 OK +total_initial_remote_disk_size 2->absent OK +total_initial_remote_disk_size absent->1 OK +total_initial_remote_disk_size absent->2 OK +use_ml_runtime absent->false SUPPRESSED empty +use_ml_runtime absent->true OK +use_ml_runtime false->absent SUPPRESSED empty +use_ml_runtime false->true OK +use_ml_runtime true->absent OK +use_ml_runtime true->false OK +workload_type.clients.jobs absent->false OK +workload_type.clients.jobs absent->true OK +workload_type.clients.jobs false->absent SUPPRESSED empty +workload_type.clients.jobs false->true OK +workload_type.clients.jobs true->absent OK +workload_type.clients.jobs true->false OK +workload_type.clients.notebooks absent->false SUPPRESSED empty +workload_type.clients.notebooks absent->true OK +workload_type.clients.notebooks false->absent SUPPRESSED empty +workload_type.clients.notebooks false->true OK +workload_type.clients.notebooks true->absent OK +workload_type.clients.notebooks true->false OK + +=== cluster_apply_policy_default_values.yml.tmpl +apply_policy_default_values absent->false SUPPRESSED empty +apply_policy_default_values absent->true OK +apply_policy_default_values false->absent SUPPRESSED remote_already_set +apply_policy_default_values false->true OK +apply_policy_default_values true->absent OK +apply_policy_default_values true->false OK +autoscale.max_workers 1->2 OK +autoscale.max_workers 1->absent OK +autoscale.max_workers 2->1 OK +autoscale.max_workers 2->absent OK +autoscale.max_workers absent->1 OK +autoscale.max_workers absent->2 OK +autoscale.min_workers 1->2 OK +autoscale.min_workers 1->absent OK +autoscale.min_workers 2->1 OK +autoscale.min_workers 2->absent OK +autoscale.min_workers absent->1 OK +autoscale.min_workers absent->2 OK +autotermination_minutes 1->2 OK +autotermination_minutes 1->absent OK +autotermination_minutes 2->1 OK +autotermination_minutes 2->absent OK +autotermination_minutes absent->1 OK +autotermination_minutes absent->2 OK +aws_attributes.availability absent->x OK +aws_attributes.availability absent->y OK +aws_attributes.availability x->absent OK +aws_attributes.availability x->y OK +aws_attributes.availability y->absent OK +aws_attributes.availability y->x OK +aws_attributes.ebs_volume_count 1->2 OK +aws_attributes.ebs_volume_count 1->absent OK +aws_attributes.ebs_volume_count 2->1 OK +aws_attributes.ebs_volume_count 2->absent OK +aws_attributes.ebs_volume_count absent->1 OK +aws_attributes.ebs_volume_count absent->2 OK +aws_attributes.ebs_volume_iops 1->2 OK +aws_attributes.ebs_volume_iops 1->absent OK +aws_attributes.ebs_volume_iops 2->1 OK +aws_attributes.ebs_volume_iops 2->absent OK +aws_attributes.ebs_volume_iops absent->1 OK +aws_attributes.ebs_volume_iops absent->2 OK +aws_attributes.ebs_volume_size 1->2 OK +aws_attributes.ebs_volume_size 1->absent OK +aws_attributes.ebs_volume_size 2->1 OK +aws_attributes.ebs_volume_size 2->absent OK +aws_attributes.ebs_volume_size absent->1 OK +aws_attributes.ebs_volume_size absent->2 OK +aws_attributes.ebs_volume_throughput 1->2 OK +aws_attributes.ebs_volume_throughput 1->absent OK +aws_attributes.ebs_volume_throughput 2->1 OK +aws_attributes.ebs_volume_throughput 2->absent OK +aws_attributes.ebs_volume_throughput absent->1 OK +aws_attributes.ebs_volume_throughput absent->2 OK +aws_attributes.ebs_volume_type absent->x OK +aws_attributes.ebs_volume_type absent->y OK +aws_attributes.ebs_volume_type x->absent OK +aws_attributes.ebs_volume_type x->y OK +aws_attributes.ebs_volume_type y->absent OK +aws_attributes.ebs_volume_type y->x OK +aws_attributes.first_on_demand 1->2 OK +aws_attributes.first_on_demand 1->absent OK +aws_attributes.first_on_demand 2->1 OK +aws_attributes.first_on_demand 2->absent OK +aws_attributes.first_on_demand absent->1 OK +aws_attributes.first_on_demand absent->2 OK +aws_attributes.instance_profile_arn absent->x OK +aws_attributes.instance_profile_arn absent->y OK +aws_attributes.instance_profile_arn x->absent OK +aws_attributes.instance_profile_arn x->y OK +aws_attributes.instance_profile_arn y->absent OK +aws_attributes.instance_profile_arn y->x OK +aws_attributes.spot_bid_price_percent 1->2 OK +aws_attributes.spot_bid_price_percent 1->absent OK +aws_attributes.spot_bid_price_percent 2->1 OK +aws_attributes.spot_bid_price_percent 2->absent OK +aws_attributes.spot_bid_price_percent absent->1 OK +aws_attributes.spot_bid_price_percent absent->2 OK +aws_attributes.zone_id absent->x OK +aws_attributes.zone_id absent->y OK +aws_attributes.zone_id x->absent OK +aws_attributes.zone_id x->y OK +aws_attributes.zone_id y->absent OK +aws_attributes.zone_id y->x OK +azure_attributes.availability absent->x OK +azure_attributes.availability absent->y OK +azure_attributes.availability x->absent OK +azure_attributes.availability x->y OK +azure_attributes.availability y->absent OK +azure_attributes.availability y->x OK +azure_attributes.capacity_reservation_group absent->x OK +azure_attributes.capacity_reservation_group absent->y OK +azure_attributes.capacity_reservation_group x->absent OK +azure_attributes.capacity_reservation_group x->y OK +azure_attributes.capacity_reservation_group y->absent OK +azure_attributes.capacity_reservation_group y->x OK +azure_attributes.first_on_demand 1->2 OK +azure_attributes.first_on_demand 1->absent OK +azure_attributes.first_on_demand 2->1 OK +azure_attributes.first_on_demand 2->absent OK +azure_attributes.first_on_demand absent->1 OK +azure_attributes.first_on_demand absent->2 OK +azure_attributes.log_analytics_info.log_analytics_primary_key absent->x OK +azure_attributes.log_analytics_info.log_analytics_primary_key absent->y OK +azure_attributes.log_analytics_info.log_analytics_primary_key x->absent OK +azure_attributes.log_analytics_info.log_analytics_primary_key x->y OK +azure_attributes.log_analytics_info.log_analytics_primary_key y->absent OK +azure_attributes.log_analytics_info.log_analytics_primary_key y->x OK +azure_attributes.log_analytics_info.log_analytics_workspace_id absent->x OK +azure_attributes.log_analytics_info.log_analytics_workspace_id absent->y OK +azure_attributes.log_analytics_info.log_analytics_workspace_id x->absent OK +azure_attributes.log_analytics_info.log_analytics_workspace_id x->y OK +azure_attributes.log_analytics_info.log_analytics_workspace_id y->absent OK +azure_attributes.log_analytics_info.log_analytics_workspace_id y->x OK +azure_attributes.spot_bid_max_price 1->2 OK +azure_attributes.spot_bid_max_price 1->absent OK +azure_attributes.spot_bid_max_price 2->1 OK +azure_attributes.spot_bid_max_price 2->absent OK +azure_attributes.spot_bid_max_price absent->1 OK +azure_attributes.spot_bid_max_price absent->2 OK +cluster_log_conf.dbfs.destination x->y OK +cluster_log_conf.dbfs.destination y->x OK +cluster_log_conf.s3.canned_acl absent->x OK +cluster_log_conf.s3.canned_acl absent->y OK +cluster_log_conf.s3.canned_acl x->absent OK +cluster_log_conf.s3.canned_acl x->y OK +cluster_log_conf.s3.canned_acl y->absent OK +cluster_log_conf.s3.canned_acl y->x OK +cluster_log_conf.s3.destination x->y OK +cluster_log_conf.s3.destination y->x OK +cluster_log_conf.s3.enable_encryption absent->false SUPPRESSED empty +cluster_log_conf.s3.enable_encryption absent->true OK +cluster_log_conf.s3.enable_encryption false->absent SUPPRESSED empty +cluster_log_conf.s3.enable_encryption false->true OK +cluster_log_conf.s3.enable_encryption true->absent OK +cluster_log_conf.s3.enable_encryption true->false OK +cluster_log_conf.s3.encryption_type absent->x OK +cluster_log_conf.s3.encryption_type absent->y OK +cluster_log_conf.s3.encryption_type x->absent OK +cluster_log_conf.s3.encryption_type x->y OK +cluster_log_conf.s3.encryption_type y->absent OK +cluster_log_conf.s3.encryption_type y->x OK +cluster_log_conf.s3.endpoint absent->x OK +cluster_log_conf.s3.endpoint absent->y OK +cluster_log_conf.s3.endpoint x->absent OK +cluster_log_conf.s3.endpoint x->y OK +cluster_log_conf.s3.endpoint y->absent OK +cluster_log_conf.s3.endpoint y->x OK +cluster_log_conf.s3.kms_key absent->x OK +cluster_log_conf.s3.kms_key absent->y OK +cluster_log_conf.s3.kms_key x->absent OK +cluster_log_conf.s3.kms_key x->y OK +cluster_log_conf.s3.kms_key y->absent OK +cluster_log_conf.s3.kms_key y->x OK +cluster_log_conf.s3.region absent->x OK +cluster_log_conf.s3.region absent->y OK +cluster_log_conf.s3.region x->absent OK +cluster_log_conf.s3.region x->y OK +cluster_log_conf.s3.region y->absent OK +cluster_log_conf.s3.region y->x OK +cluster_log_conf.volumes.destination x->y OK +cluster_log_conf.volumes.destination y->x OK +cluster_name absent->x OK +cluster_name absent->y OK +cluster_name x->absent OK +cluster_name x->y OK +cluster_name y->absent OK +cluster_name y->x OK +data_security_mode absent->x OK +data_security_mode absent->y OK +data_security_mode x->absent OK +data_security_mode x->y OK +data_security_mode y->absent OK +data_security_mode y->x OK +dependency_mode absent->x OK +dependency_mode absent->y OK +dependency_mode x->absent OK +dependency_mode x->y OK +dependency_mode y->absent OK +dependency_mode y->x OK +docker_image.basic_auth.password absent->x OK +docker_image.basic_auth.password absent->y OK +docker_image.basic_auth.password x->absent OK +docker_image.basic_auth.password x->y OK +docker_image.basic_auth.password y->absent OK +docker_image.basic_auth.password y->x OK +docker_image.basic_auth.username absent->x OK +docker_image.basic_auth.username absent->y OK +docker_image.basic_auth.username x->absent OK +docker_image.basic_auth.username x->y OK +docker_image.basic_auth.username y->absent OK +docker_image.basic_auth.username y->x OK +docker_image.url absent->x OK +docker_image.url absent->y OK +docker_image.url x->absent OK +docker_image.url x->y OK +docker_image.url y->absent OK +docker_image.url y->x OK +driver_instance_pool_id absent->x OK +driver_instance_pool_id absent->y OK +driver_instance_pool_id x->absent OK +driver_instance_pool_id x->y OK +driver_instance_pool_id y->absent OK +driver_instance_pool_id y->x OK +driver_node_type_id absent->x OK +driver_node_type_id absent->y OK +driver_node_type_id x->absent OK +driver_node_type_id x->y OK +driver_node_type_id y->absent OK +driver_node_type_id y->x OK +enable_elastic_disk absent->false SUPPRESSED remote_already_set +enable_elastic_disk absent->true OK +enable_elastic_disk false->absent SUPPRESSED empty +enable_elastic_disk false->true OK +enable_elastic_disk true->absent OK +enable_elastic_disk true->false OK +enable_local_disk_encryption absent->false SUPPRESSED empty +enable_local_disk_encryption absent->true OK +enable_local_disk_encryption false->absent SUPPRESSED empty +enable_local_disk_encryption false->true OK +enable_local_disk_encryption true->absent OK +enable_local_disk_encryption true->false OK +gcp_attributes.availability absent->x OK +gcp_attributes.availability absent->y OK +gcp_attributes.availability x->absent OK +gcp_attributes.availability x->y OK +gcp_attributes.availability y->absent OK +gcp_attributes.availability y->x OK +gcp_attributes.boot_disk_size 1->2 OK +gcp_attributes.boot_disk_size 1->absent OK +gcp_attributes.boot_disk_size 2->1 OK +gcp_attributes.boot_disk_size 2->absent OK +gcp_attributes.boot_disk_size absent->1 OK +gcp_attributes.boot_disk_size absent->2 OK +gcp_attributes.confidential_compute_type absent->x OK +gcp_attributes.confidential_compute_type absent->y OK +gcp_attributes.confidential_compute_type x->absent OK +gcp_attributes.confidential_compute_type x->y OK +gcp_attributes.confidential_compute_type y->absent OK +gcp_attributes.confidential_compute_type y->x OK +gcp_attributes.first_on_demand 1->2 OK +gcp_attributes.first_on_demand 1->absent OK +gcp_attributes.first_on_demand 2->1 OK +gcp_attributes.first_on_demand 2->absent OK +gcp_attributes.first_on_demand absent->1 OK +gcp_attributes.first_on_demand absent->2 OK +gcp_attributes.google_service_account absent->x OK +gcp_attributes.google_service_account absent->y OK +gcp_attributes.google_service_account x->absent OK +gcp_attributes.google_service_account x->y OK +gcp_attributes.google_service_account y->absent OK +gcp_attributes.google_service_account y->x OK +gcp_attributes.local_ssd_count 1->2 OK +gcp_attributes.local_ssd_count 1->absent OK +gcp_attributes.local_ssd_count 2->1 OK +gcp_attributes.local_ssd_count 2->absent OK +gcp_attributes.local_ssd_count absent->1 OK +gcp_attributes.local_ssd_count absent->2 OK +gcp_attributes.use_preemptible_executors absent->false SUPPRESSED empty +gcp_attributes.use_preemptible_executors absent->true OK +gcp_attributes.use_preemptible_executors false->absent SUPPRESSED empty +gcp_attributes.use_preemptible_executors false->true OK +gcp_attributes.use_preemptible_executors true->absent OK +gcp_attributes.use_preemptible_executors true->false OK +gcp_attributes.zone_id absent->x OK +gcp_attributes.zone_id absent->y OK +gcp_attributes.zone_id x->absent OK +gcp_attributes.zone_id x->y OK +gcp_attributes.zone_id y->absent OK +gcp_attributes.zone_id y->x OK +instance_pool_id absent->x OK +instance_pool_id absent->y OK +instance_pool_id x->absent OK +instance_pool_id x->y OK +instance_pool_id y->absent OK +instance_pool_id y->x OK +is_single_node absent->false SUPPRESSED empty +is_single_node absent->true OK +is_single_node false->absent SUPPRESSED empty +is_single_node false->true OK +is_single_node true->absent OK +is_single_node true->false OK +kind absent->x OK +kind absent->y OK +kind x->absent OK +kind x->y OK +kind y->absent OK +kind y->x OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle.started absent->false OK +lifecycle.started absent->true OK +lifecycle.started false->absent OK +lifecycle.started false->true OK +lifecycle.started true->absent OK +lifecycle.started true->false OK +node_type_id absent->x OK +node_type_id absent->y OK +node_type_id x->absent OK +node_type_id x->y OK +node_type_id y->absent OK +node_type_id y->x OK +num_workers 1->2 OK +num_workers 1->absent OK +num_workers 2->1 OK +num_workers 2->absent OK +num_workers absent->1 OK +num_workers absent->2 OK +policy_id absent->x OK +policy_id absent->y OK +policy_id x->absent OK +policy_id x->y OK +policy_id y->absent OK +policy_id y->x OK +remote_disk_throughput 1->2 OK +remote_disk_throughput 1->absent OK +remote_disk_throughput 2->1 OK +remote_disk_throughput 2->absent OK +remote_disk_throughput absent->1 OK +remote_disk_throughput absent->2 OK +runtime_engine absent->x OK +runtime_engine absent->y OK +runtime_engine x->absent OK +runtime_engine x->y OK +runtime_engine y->absent OK +runtime_engine y->x OK +single_user_name absent->x OK +single_user_name absent->y OK +single_user_name x->absent OK +single_user_name x->y OK +single_user_name y->absent OK +single_user_name y->x OK +spark_version absent->x OK +spark_version absent->y OK +spark_version x->absent OK +spark_version x->y OK +spark_version y->absent OK +spark_version y->x OK +total_initial_remote_disk_size 1->2 OK +total_initial_remote_disk_size 1->absent OK +total_initial_remote_disk_size 2->1 OK +total_initial_remote_disk_size 2->absent OK +total_initial_remote_disk_size absent->1 OK +total_initial_remote_disk_size absent->2 OK +use_ml_runtime absent->false SUPPRESSED empty +use_ml_runtime absent->true OK +use_ml_runtime false->absent SUPPRESSED empty +use_ml_runtime false->true OK +use_ml_runtime true->absent OK +use_ml_runtime true->false OK +workload_type.clients.jobs absent->false OK +workload_type.clients.jobs absent->true OK +workload_type.clients.jobs false->absent SUPPRESSED empty +workload_type.clients.jobs false->true OK +workload_type.clients.jobs true->absent OK +workload_type.clients.jobs true->false OK +workload_type.clients.notebooks absent->false SUPPRESSED empty +workload_type.clients.notebooks absent->true OK +workload_type.clients.notebooks false->absent SUPPRESSED empty +workload_type.clients.notebooks false->true OK +workload_type.clients.notebooks true->absent OK +workload_type.clients.notebooks true->false OK + +=== summary +OK 686 +SUPPRESSED 34 +NOT_OBSERVABLE 24 + +=== not covered: 23 fields under a slice or map +custom_tags.* +driver_node_type_flexibility.alternate_node_type_ids[*] +init_scripts[*].abfss.destination +init_scripts[*].dbfs.destination +init_scripts[*].file.destination +init_scripts[*].gcs.destination +init_scripts[*].s3.canned_acl +init_scripts[*].s3.destination +init_scripts[*].s3.enable_encryption +init_scripts[*].s3.encryption_type +init_scripts[*].s3.endpoint +init_scripts[*].s3.kms_key +init_scripts[*].s3.region +init_scripts[*].volumes.destination +init_scripts[*].workspace.destination +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +spark_conf.* +spark_env_vars.* +ssh_public_keys[*] +worker_node_type_flexibility.alternate_node_type_ids[*] diff --git a/bundle/direct/dresources_test/out.configs.txt b/bundle/direct/dresources_test/out.configs.txt new file mode 100644 index 00000000000..5a415688616 --- /dev/null +++ b/bundle/direct/dresources_test/out.configs.txt @@ -0,0 +1,60 @@ +=== covered +alert.yml.tmpl alerts +app.yml.tmpl apps +catalog.yml.tmpl catalogs +catalog_optional_fields.yml.tmpl catalogs +cluster.yml.tmpl clusters +cluster_apply_policy_default_values.yml.tmpl clusters +cluster_policy.yml.tmpl cluster_policies +dashboard.yml.tmpl dashboards +database_instance.yml.tmpl database_instances +experiment.yml.tmpl experiments +external_location.yml.tmpl external_locations +genie_space.yml.tmpl genie_spaces +instance_pool.yml.tmpl instance_pools +job.yml.tmpl jobs +job_apply_policy_default_values_for_each_task.yml.tmpl jobs +job_apply_policy_default_values_job_cluster.yml.tmpl jobs +job_apply_policy_default_values_task_cluster.yml.tmpl jobs +job_with_depends_on.yml.tmpl jobs +job_with_task.yml.tmpl jobs +model.yml.tmpl models +model_serving_endpoint.yml.tmpl model_serving_endpoints +model_serving_endpoint_telemetry.yml.tmpl model_serving_endpoints +pipeline.yml.tmpl pipelines +pipeline_allow_duplicate_names.yml.tmpl pipelines +pipeline_apply_policy_default_values.yml.tmpl pipelines +postgres_project.yml.tmpl postgres_projects +postgres_synced_table.yml.tmpl postgres_synced_tables +registered_model.yml.tmpl registered_models +schema.yml.tmpl schemas +schema_empty_grants.yml.tmpl schemas +schema_uppercase_name.yml.tmpl schemas +secret_scope.yml.tmpl secret_scopes +secret_scope_default_backend_type.yml.tmpl secret_scopes +sql_warehouse.yml.tmpl sql_warehouses +volume.yml.tmpl volumes +volume_external.yml.tmpl volumes + +=== not covered +app_lifecycle_ref.yml.tmpl has 2 resources +database_catalog.yml.tmpl has 2 resources +job_cross_resource_ref.yml.tmpl has 3 resources +job_permission_ref.yml.tmpl has 2 resources +job_pydabs_1000_tasks.yml.tmpl has -init.sh +job_pydabs_10_tasks.yml.tmpl has -init.sh +job_run.yml.tmpl has 2 resources +job_run_job_ref.yml.tmpl has 2 resources +pipeline_config_dots.yml.tmpl has 2 resources +postgres_branch.yml.tmpl has 2 resources +postgres_catalog.yml.tmpl has 2 resources +postgres_database.yml.tmpl has 4 resources +postgres_endpoint.yml.tmpl has 3 resources +postgres_role.yml.tmpl has 3 resources +schema_grant_ref.yml.tmpl has 2 resources +secret.yml.tmpl has -init.sh +synced_database_table.yml.tmpl has -init.sh +vector_search_endpoint.yml.tmpl has 2 resources +vector_search_index.yml.tmpl has -init.sh +volume_path_job_ref.yml.tmpl has 3 resources +volume_uppercase_name.yml.tmpl has 2 resources diff --git a/bundle/direct/dresources_test/out.dashboards.fields.txt b/bundle/direct/dresources_test/out.dashboards.fields.txt new file mode 100644 index 00000000000..fa73a1d5b1a --- /dev/null +++ b/bundle/direct/dresources_test/out.dashboards.fields.txt @@ -0,0 +1,97 @@ +=== dashboard.yml.tmpl +create_time absent->x OK +create_time absent->y OK +create_time x->absent OK +create_time x->y OK +create_time y->absent OK +create_time y->x OK +dashboard_id absent->x OK +dashboard_id absent->y OK +dashboard_id x->absent OK +dashboard_id x->y OK +dashboard_id y->absent OK +dashboard_id y->x OK +dataset_catalog absent->x OK +dataset_catalog absent->y OK +dataset_catalog x->absent OK +dataset_catalog x->y OK +dataset_catalog y->absent OK +dataset_catalog y->x OK +dataset_schema absent->x OK +dataset_schema absent->y OK +dataset_schema x->absent OK +dataset_schema x->y OK +dataset_schema y->absent OK +dataset_schema y->x OK +display_name absent->x OK +display_name absent->y OK +display_name x->absent DRIFT display_name +display_name x->y OK +display_name y->absent DRIFT display_name +display_name y->x OK +embed_credentials absent->false NOT_OBSERVABLE +embed_credentials absent->true OK +embed_credentials false->absent SUPPRESSED empty +embed_credentials false->true OK +embed_credentials true->absent OK +embed_credentials true->false OK +etag absent->x SUPPRESSED custom +etag absent->y SUPPRESSED custom +etag x->absent SUPPRESSED custom +etag x->y SUPPRESSED custom +etag y->absent SUPPRESSED custom +etag y->x SUPPRESSED custom +file_path absent->x NOT_OBSERVABLE +file_path absent->y NOT_OBSERVABLE +file_path x->absent NOT_OBSERVABLE +file_path x->y NOT_OBSERVABLE +file_path y->absent NOT_OBSERVABLE +file_path y->x NOT_OBSERVABLE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle_state absent->x OK +lifecycle_state absent->y OK +lifecycle_state x->absent OK +lifecycle_state x->y OK +lifecycle_state y->absent OK +lifecycle_state y->x OK +parent_path absent->x DRIFT parent_path +parent_path absent->y DRIFT parent_path +parent_path x->absent DRIFT parent_path +parent_path x->y DRIFT parent_path +parent_path y->absent DRIFT parent_path +parent_path y->x DRIFT parent_path +path absent->x OK +path absent->y OK +path x->absent OK +path x->y OK +path y->absent OK +path y->x OK +update_time absent->x OK +update_time absent->y OK +update_time x->absent OK +update_time x->y OK +update_time y->absent OK +update_time y->x OK +warehouse_id absent->x OK +warehouse_id absent->y OK +warehouse_id x->absent OK +warehouse_id x->y OK +warehouse_id y->absent OK +warehouse_id y->x OK + +=== summary +OK 56 +SUPPRESSED 7 +NOT_OBSERVABLE 13 +DRIFT 8 + +=== not covered: 4 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/dresources_test/out.database_instances.fields.txt b/bundle/direct/dresources_test/out.database_instances.fields.txt new file mode 100644 index 00000000000..55386f507ee --- /dev/null +++ b/bundle/direct/dresources_test/out.database_instances.fields.txt @@ -0,0 +1,186 @@ +=== database_instance.yml.tmpl +capacity absent->x DRIFT capacity +capacity absent->y DRIFT capacity +capacity x->absent SUPPRESSED remote_already_set +capacity x->y DRIFT capacity +capacity y->absent SUPPRESSED remote_already_set +capacity y->x DRIFT capacity +creation_time absent->x DRIFT capacity +creation_time absent->y DRIFT capacity +creation_time x->absent DRIFT capacity +creation_time x->y DRIFT capacity +creation_time y->absent DRIFT capacity +creation_time y->x DRIFT capacity +creator absent->x DRIFT capacity +creator absent->y DRIFT capacity +creator x->absent DRIFT capacity +creator x->y DRIFT capacity +creator y->absent DRIFT capacity +creator y->x DRIFT capacity +effective_capacity absent->x DRIFT capacity +effective_capacity absent->y DRIFT capacity +effective_capacity x->absent DRIFT capacity +effective_capacity x->y DRIFT capacity +effective_capacity y->absent DRIFT capacity +effective_capacity y->x DRIFT capacity +effective_enable_pg_native_login absent->false SUPPRESSED empty +effective_enable_pg_native_login absent->true DRIFT capacity +effective_enable_pg_native_login false->absent DRIFT capacity +effective_enable_pg_native_login false->true DRIFT capacity +effective_enable_pg_native_login true->absent DRIFT capacity +effective_enable_pg_native_login true->false DRIFT capacity +effective_enable_readable_secondaries absent->false SUPPRESSED empty +effective_enable_readable_secondaries absent->true DRIFT capacity +effective_enable_readable_secondaries false->absent DRIFT capacity +effective_enable_readable_secondaries false->true DRIFT capacity +effective_enable_readable_secondaries true->absent DRIFT capacity +effective_enable_readable_secondaries true->false DRIFT capacity +effective_node_count 1->2 DRIFT capacity +effective_node_count 1->absent DRIFT capacity +effective_node_count 2->1 DRIFT capacity +effective_node_count 2->absent DRIFT capacity +effective_node_count absent->1 DRIFT capacity +effective_node_count absent->2 DRIFT capacity +effective_retention_window_in_days 1->2 DRIFT capacity +effective_retention_window_in_days 1->absent DRIFT capacity +effective_retention_window_in_days 2->1 DRIFT capacity +effective_retention_window_in_days 2->absent DRIFT capacity +effective_retention_window_in_days absent->1 DRIFT capacity +effective_retention_window_in_days absent->2 DRIFT capacity +effective_stopped absent->false SUPPRESSED empty +effective_stopped absent->true DRIFT capacity +effective_stopped false->absent DRIFT capacity +effective_stopped false->true DRIFT capacity +effective_stopped true->absent DRIFT capacity +effective_stopped true->false DRIFT capacity +effective_usage_policy_id absent->x DRIFT capacity +effective_usage_policy_id absent->y DRIFT capacity +effective_usage_policy_id x->absent DRIFT capacity +effective_usage_policy_id x->y DRIFT capacity +effective_usage_policy_id y->absent DRIFT capacity +effective_usage_policy_id y->x DRIFT capacity +enable_pg_native_login absent->false SUPPRESSED empty +enable_pg_native_login absent->true DRIFT capacity +enable_pg_native_login false->absent DRIFT capacity +enable_pg_native_login false->true DRIFT capacity +enable_pg_native_login true->absent DRIFT capacity +enable_pg_native_login true->false DRIFT capacity +enable_readable_secondaries absent->false SUPPRESSED empty +enable_readable_secondaries absent->true DRIFT capacity +enable_readable_secondaries false->absent DRIFT capacity +enable_readable_secondaries false->true DRIFT capacity +enable_readable_secondaries true->absent DRIFT capacity +enable_readable_secondaries true->false DRIFT capacity +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK_RECREATE +name y->x OK_RECREATE +node_count 1->2 DRIFT capacity +node_count 1->absent DRIFT capacity +node_count 2->1 DRIFT capacity +node_count 2->absent DRIFT capacity +node_count absent->1 DRIFT capacity +node_count absent->2 DRIFT capacity +parent_instance_ref.branch_time absent->x OK_RECREATE +parent_instance_ref.branch_time absent->y OK_RECREATE +parent_instance_ref.branch_time x->absent OK_RECREATE +parent_instance_ref.branch_time x->y OK_RECREATE +parent_instance_ref.branch_time y->absent OK_RECREATE +parent_instance_ref.branch_time y->x OK_RECREATE +parent_instance_ref.effective_lsn absent->x OK_RECREATE +parent_instance_ref.effective_lsn absent->y OK_RECREATE +parent_instance_ref.effective_lsn x->absent OK_RECREATE +parent_instance_ref.effective_lsn x->y OK_RECREATE +parent_instance_ref.effective_lsn y->absent OK_RECREATE +parent_instance_ref.effective_lsn y->x OK_RECREATE +parent_instance_ref.lsn absent->x OK_RECREATE +parent_instance_ref.lsn absent->y OK_RECREATE +parent_instance_ref.lsn x->absent OK_RECREATE +parent_instance_ref.lsn x->y OK_RECREATE +parent_instance_ref.lsn y->absent OK_RECREATE +parent_instance_ref.lsn y->x OK_RECREATE +parent_instance_ref.name absent->x OK_RECREATE +parent_instance_ref.name absent->y OK_RECREATE +parent_instance_ref.name x->absent OK_RECREATE +parent_instance_ref.name x->y OK_RECREATE +parent_instance_ref.name y->absent OK_RECREATE +parent_instance_ref.name y->x OK_RECREATE +parent_instance_ref.uid absent->x OK_RECREATE +parent_instance_ref.uid absent->y OK_RECREATE +parent_instance_ref.uid x->absent OK_RECREATE +parent_instance_ref.uid x->y OK_RECREATE +parent_instance_ref.uid y->absent OK_RECREATE +parent_instance_ref.uid y->x OK_RECREATE +pg_version absent->x DRIFT capacity +pg_version absent->y DRIFT capacity +pg_version x->absent DRIFT capacity +pg_version x->y DRIFT capacity +pg_version y->absent DRIFT capacity +pg_version y->x DRIFT capacity +read_only_dns absent->x DRIFT capacity +read_only_dns absent->y DRIFT capacity +read_only_dns x->absent DRIFT capacity +read_only_dns x->y DRIFT capacity +read_only_dns y->absent DRIFT capacity +read_only_dns y->x DRIFT capacity +read_write_dns absent->x DRIFT capacity +read_write_dns absent->y DRIFT capacity +read_write_dns x->absent DRIFT capacity +read_write_dns x->y DRIFT capacity +read_write_dns y->absent DRIFT capacity +read_write_dns y->x DRIFT capacity +retention_window_in_days 1->2 DRIFT capacity +retention_window_in_days 1->absent DRIFT capacity +retention_window_in_days 2->1 DRIFT capacity +retention_window_in_days 2->absent DRIFT capacity +retention_window_in_days absent->1 DRIFT capacity +retention_window_in_days absent->2 DRIFT capacity +state absent->x DRIFT capacity +state absent->y DRIFT capacity +state x->absent DRIFT capacity +state x->y DRIFT capacity +state y->absent DRIFT capacity +state y->x DRIFT capacity +stopped absent->false SUPPRESSED empty +stopped absent->true DRIFT capacity +stopped false->absent DRIFT capacity +stopped false->true DRIFT capacity +stopped true->absent DRIFT capacity +stopped true->false DRIFT capacity +uid absent->x DRIFT capacity +uid absent->y DRIFT capacity +uid x->absent DRIFT capacity +uid x->y DRIFT capacity +uid y->absent DRIFT capacity +uid y->x DRIFT capacity +usage_policy_id absent->x DRIFT capacity +usage_policy_id absent->y DRIFT capacity +usage_policy_id x->absent DRIFT capacity +usage_policy_id x->y DRIFT capacity +usage_policy_id y->absent DRIFT capacity +usage_policy_id y->x DRIFT capacity + +=== summary +OK_RECREATE 32 +SUPPRESSED 8 +NOT_OBSERVABLE 6 +DRIFT 118 + +=== not covered: 13 fields under a slice or map +child_instance_refs[*].branch_time +child_instance_refs[*].effective_lsn +child_instance_refs[*].lsn +child_instance_refs[*].name +child_instance_refs[*].uid +custom_tags[*].key +custom_tags[*].value +effective_custom_tags[*].key +effective_custom_tags[*].value +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/dresources_test/out.experiments.fields.txt b/bundle/direct/dresources_test/out.experiments.fields.txt new file mode 100644 index 00000000000..f0e2026c7ef --- /dev/null +++ b/bundle/direct/dresources_test/out.experiments.fields.txt @@ -0,0 +1,44 @@ +=== experiment.yml.tmpl +artifact_location absent->x OK_RECREATE +artifact_location absent->y OK_RECREATE +artifact_location x->absent OK_RECREATE +artifact_location x->y OK_RECREATE +artifact_location y->absent OK_RECREATE +artifact_location y->x OK_RECREATE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK +name y->x OK +trace_location.uc_trace_location.catalog x->y OK_RECREATE +trace_location.uc_trace_location.catalog y->x OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix absent->x OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix absent->y OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix x->absent OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix x->y OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix y->absent OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix y->x OK_RECREATE +trace_location.uc_trace_location.schema x->y OK_RECREATE +trace_location.uc_trace_location.schema y->x OK_RECREATE +trace_location.uc_trace_location.table_prefix absent->x OK_RECREATE +trace_location.uc_trace_location.table_prefix absent->y OK_RECREATE +trace_location.uc_trace_location.table_prefix x->absent OK_RECREATE +trace_location.uc_trace_location.table_prefix x->y OK_RECREATE +trace_location.uc_trace_location.table_prefix y->absent OK_RECREATE +trace_location.uc_trace_location.table_prefix y->x OK_RECREATE + +=== summary +OK 2 +OK_RECREATE 22 +NOT_OBSERVABLE 6 + +=== not covered: 6 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +tags[*].key +tags[*].value diff --git a/bundle/direct/dresources_test/out.external_locations.fields.txt b/bundle/direct/dresources_test/out.external_locations.fields.txt new file mode 100644 index 00000000000..5a0cd89c93e --- /dev/null +++ b/bundle/direct/dresources_test/out.external_locations.fields.txt @@ -0,0 +1,264 @@ +=== external_location.yml.tmpl +comment absent->x OK +comment absent->y OK +comment x->absent DRIFT comment +comment x->y OK +comment y->absent DRIFT comment +comment y->x OK +credential_name x->y OK_RECREATE +credential_name y->x OK_RECREATE +effective_enable_file_events absent->false SUPPRESSED empty +effective_enable_file_events absent->true OK +effective_enable_file_events false->absent SUPPRESSED empty +effective_enable_file_events false->true OK +effective_enable_file_events true->absent OK +effective_enable_file_events true->false OK +effective_file_event_queue.managed_aqs.managed_resource_id absent->x OK +effective_file_event_queue.managed_aqs.managed_resource_id absent->y OK +effective_file_event_queue.managed_aqs.managed_resource_id x->absent OK +effective_file_event_queue.managed_aqs.managed_resource_id x->y OK +effective_file_event_queue.managed_aqs.managed_resource_id y->absent OK +effective_file_event_queue.managed_aqs.managed_resource_id y->x OK +effective_file_event_queue.managed_aqs.queue_url absent->x OK +effective_file_event_queue.managed_aqs.queue_url absent->y OK +effective_file_event_queue.managed_aqs.queue_url x->absent OK +effective_file_event_queue.managed_aqs.queue_url x->y OK +effective_file_event_queue.managed_aqs.queue_url y->absent OK +effective_file_event_queue.managed_aqs.queue_url y->x OK +effective_file_event_queue.managed_aqs.resource_group absent->x OK +effective_file_event_queue.managed_aqs.resource_group absent->y OK +effective_file_event_queue.managed_aqs.resource_group x->absent OK +effective_file_event_queue.managed_aqs.resource_group x->y OK +effective_file_event_queue.managed_aqs.resource_group y->absent OK +effective_file_event_queue.managed_aqs.resource_group y->x OK +effective_file_event_queue.managed_aqs.subscription_id absent->x OK +effective_file_event_queue.managed_aqs.subscription_id absent->y OK +effective_file_event_queue.managed_aqs.subscription_id x->absent OK +effective_file_event_queue.managed_aqs.subscription_id x->y OK +effective_file_event_queue.managed_aqs.subscription_id y->absent OK +effective_file_event_queue.managed_aqs.subscription_id y->x OK +effective_file_event_queue.managed_pubsub.managed_resource_id absent->x OK +effective_file_event_queue.managed_pubsub.managed_resource_id absent->y OK +effective_file_event_queue.managed_pubsub.managed_resource_id x->absent OK +effective_file_event_queue.managed_pubsub.managed_resource_id x->y OK +effective_file_event_queue.managed_pubsub.managed_resource_id y->absent OK +effective_file_event_queue.managed_pubsub.managed_resource_id y->x OK +effective_file_event_queue.managed_pubsub.subscription_name absent->x OK +effective_file_event_queue.managed_pubsub.subscription_name absent->y OK +effective_file_event_queue.managed_pubsub.subscription_name x->absent OK +effective_file_event_queue.managed_pubsub.subscription_name x->y OK +effective_file_event_queue.managed_pubsub.subscription_name y->absent OK +effective_file_event_queue.managed_pubsub.subscription_name y->x OK +effective_file_event_queue.managed_sqs.managed_resource_id absent->x OK +effective_file_event_queue.managed_sqs.managed_resource_id absent->y OK +effective_file_event_queue.managed_sqs.managed_resource_id x->absent OK +effective_file_event_queue.managed_sqs.managed_resource_id x->y OK +effective_file_event_queue.managed_sqs.managed_resource_id y->absent OK +effective_file_event_queue.managed_sqs.managed_resource_id y->x OK +effective_file_event_queue.managed_sqs.queue_url absent->x OK +effective_file_event_queue.managed_sqs.queue_url absent->y OK +effective_file_event_queue.managed_sqs.queue_url x->absent OK +effective_file_event_queue.managed_sqs.queue_url x->y OK +effective_file_event_queue.managed_sqs.queue_url y->absent OK +effective_file_event_queue.managed_sqs.queue_url y->x OK +effective_file_event_queue.provided_aqs.managed_resource_id absent->x OK +effective_file_event_queue.provided_aqs.managed_resource_id absent->y OK +effective_file_event_queue.provided_aqs.managed_resource_id x->absent OK +effective_file_event_queue.provided_aqs.managed_resource_id x->y OK +effective_file_event_queue.provided_aqs.managed_resource_id y->absent OK +effective_file_event_queue.provided_aqs.managed_resource_id y->x OK +effective_file_event_queue.provided_aqs.queue_url absent->x OK +effective_file_event_queue.provided_aqs.queue_url absent->y OK +effective_file_event_queue.provided_aqs.queue_url x->absent OK +effective_file_event_queue.provided_aqs.queue_url x->y OK +effective_file_event_queue.provided_aqs.queue_url y->absent OK +effective_file_event_queue.provided_aqs.queue_url y->x OK +effective_file_event_queue.provided_aqs.resource_group absent->x OK +effective_file_event_queue.provided_aqs.resource_group absent->y OK +effective_file_event_queue.provided_aqs.resource_group x->absent OK +effective_file_event_queue.provided_aqs.resource_group x->y OK +effective_file_event_queue.provided_aqs.resource_group y->absent OK +effective_file_event_queue.provided_aqs.resource_group y->x OK +effective_file_event_queue.provided_aqs.subscription_id absent->x OK +effective_file_event_queue.provided_aqs.subscription_id absent->y OK +effective_file_event_queue.provided_aqs.subscription_id x->absent OK +effective_file_event_queue.provided_aqs.subscription_id x->y OK +effective_file_event_queue.provided_aqs.subscription_id y->absent OK +effective_file_event_queue.provided_aqs.subscription_id y->x OK +effective_file_event_queue.provided_pubsub.managed_resource_id absent->x OK +effective_file_event_queue.provided_pubsub.managed_resource_id absent->y OK +effective_file_event_queue.provided_pubsub.managed_resource_id x->absent OK +effective_file_event_queue.provided_pubsub.managed_resource_id x->y OK +effective_file_event_queue.provided_pubsub.managed_resource_id y->absent OK +effective_file_event_queue.provided_pubsub.managed_resource_id y->x OK +effective_file_event_queue.provided_pubsub.subscription_name absent->x OK +effective_file_event_queue.provided_pubsub.subscription_name absent->y OK +effective_file_event_queue.provided_pubsub.subscription_name x->absent OK +effective_file_event_queue.provided_pubsub.subscription_name x->y OK +effective_file_event_queue.provided_pubsub.subscription_name y->absent OK +effective_file_event_queue.provided_pubsub.subscription_name y->x OK +effective_file_event_queue.provided_sqs.managed_resource_id absent->x OK +effective_file_event_queue.provided_sqs.managed_resource_id absent->y OK +effective_file_event_queue.provided_sqs.managed_resource_id x->absent OK +effective_file_event_queue.provided_sqs.managed_resource_id x->y OK +effective_file_event_queue.provided_sqs.managed_resource_id y->absent OK +effective_file_event_queue.provided_sqs.managed_resource_id y->x OK +effective_file_event_queue.provided_sqs.queue_url absent->x OK +effective_file_event_queue.provided_sqs.queue_url absent->y OK +effective_file_event_queue.provided_sqs.queue_url x->absent OK +effective_file_event_queue.provided_sqs.queue_url x->y OK +effective_file_event_queue.provided_sqs.queue_url y->absent OK +effective_file_event_queue.provided_sqs.queue_url y->x OK +enable_file_events absent->false SUPPRESSED empty +enable_file_events absent->true OK +enable_file_events false->absent SUPPRESSED remote_already_set +enable_file_events false->true OK +enable_file_events true->absent OK +enable_file_events true->false OK +encryption_details.sse_encryption_details.algorithm absent->x OK_RECREATE +encryption_details.sse_encryption_details.algorithm absent->y OK_RECREATE +encryption_details.sse_encryption_details.algorithm x->absent OK_RECREATE +encryption_details.sse_encryption_details.algorithm x->y OK_RECREATE +encryption_details.sse_encryption_details.algorithm y->absent OK_RECREATE +encryption_details.sse_encryption_details.algorithm y->x OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn absent->x OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn absent->y OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn x->absent OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn x->y OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn y->absent OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn y->x OK_RECREATE +fallback absent->false SUPPRESSED empty +fallback absent->true OK +fallback false->absent SUPPRESSED remote_already_set +fallback false->true OK +fallback true->absent OK +fallback true->false OK +file_event_queue.managed_aqs.managed_resource_id absent->x OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id absent->y OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id x->absent OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id x->y OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id y->absent OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id y->x OK_RECREATE +file_event_queue.managed_aqs.queue_url absent->x OK_RECREATE +file_event_queue.managed_aqs.queue_url absent->y OK_RECREATE +file_event_queue.managed_aqs.queue_url x->absent OK_RECREATE +file_event_queue.managed_aqs.queue_url x->y OK_RECREATE +file_event_queue.managed_aqs.queue_url y->absent OK_RECREATE +file_event_queue.managed_aqs.queue_url y->x OK_RECREATE +file_event_queue.managed_aqs.resource_group absent->x OK_RECREATE +file_event_queue.managed_aqs.resource_group absent->y OK_RECREATE +file_event_queue.managed_aqs.resource_group x->absent OK_RECREATE +file_event_queue.managed_aqs.resource_group x->y OK_RECREATE +file_event_queue.managed_aqs.resource_group y->absent OK_RECREATE +file_event_queue.managed_aqs.resource_group y->x OK_RECREATE +file_event_queue.managed_aqs.subscription_id absent->x OK_RECREATE +file_event_queue.managed_aqs.subscription_id absent->y OK_RECREATE +file_event_queue.managed_aqs.subscription_id x->absent OK_RECREATE +file_event_queue.managed_aqs.subscription_id x->y OK_RECREATE +file_event_queue.managed_aqs.subscription_id y->absent OK_RECREATE +file_event_queue.managed_aqs.subscription_id y->x OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id absent->x OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id absent->y OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id x->absent OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id x->y OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id y->absent OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id y->x OK_RECREATE +file_event_queue.managed_pubsub.subscription_name absent->x OK_RECREATE +file_event_queue.managed_pubsub.subscription_name absent->y OK_RECREATE +file_event_queue.managed_pubsub.subscription_name x->absent OK_RECREATE +file_event_queue.managed_pubsub.subscription_name x->y OK_RECREATE +file_event_queue.managed_pubsub.subscription_name y->absent OK_RECREATE +file_event_queue.managed_pubsub.subscription_name y->x OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id absent->x OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id absent->y OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id x->absent OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id x->y OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id y->absent OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id y->x OK_RECREATE +file_event_queue.managed_sqs.queue_url absent->x OK_RECREATE +file_event_queue.managed_sqs.queue_url absent->y OK_RECREATE +file_event_queue.managed_sqs.queue_url x->absent OK_RECREATE +file_event_queue.managed_sqs.queue_url x->y OK_RECREATE +file_event_queue.managed_sqs.queue_url y->absent OK_RECREATE +file_event_queue.managed_sqs.queue_url y->x OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id absent->x OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id absent->y OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id x->absent OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id x->y OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id y->absent OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id y->x OK_RECREATE +file_event_queue.provided_aqs.queue_url absent->x OK_RECREATE +file_event_queue.provided_aqs.queue_url absent->y OK_RECREATE +file_event_queue.provided_aqs.queue_url x->absent OK_RECREATE +file_event_queue.provided_aqs.queue_url x->y OK_RECREATE +file_event_queue.provided_aqs.queue_url y->absent OK_RECREATE +file_event_queue.provided_aqs.queue_url y->x OK_RECREATE +file_event_queue.provided_aqs.resource_group absent->x OK_RECREATE +file_event_queue.provided_aqs.resource_group absent->y OK_RECREATE +file_event_queue.provided_aqs.resource_group x->absent OK_RECREATE +file_event_queue.provided_aqs.resource_group x->y OK_RECREATE +file_event_queue.provided_aqs.resource_group y->absent OK_RECREATE +file_event_queue.provided_aqs.resource_group y->x OK_RECREATE +file_event_queue.provided_aqs.subscription_id absent->x OK_RECREATE +file_event_queue.provided_aqs.subscription_id absent->y OK_RECREATE +file_event_queue.provided_aqs.subscription_id x->absent OK_RECREATE +file_event_queue.provided_aqs.subscription_id x->y OK_RECREATE +file_event_queue.provided_aqs.subscription_id y->absent OK_RECREATE +file_event_queue.provided_aqs.subscription_id y->x OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id absent->x OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id absent->y OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id x->absent OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id x->y OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id y->absent OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id y->x OK_RECREATE +file_event_queue.provided_pubsub.subscription_name absent->x OK_RECREATE +file_event_queue.provided_pubsub.subscription_name absent->y OK_RECREATE +file_event_queue.provided_pubsub.subscription_name x->absent OK_RECREATE +file_event_queue.provided_pubsub.subscription_name x->y OK_RECREATE +file_event_queue.provided_pubsub.subscription_name y->absent OK_RECREATE +file_event_queue.provided_pubsub.subscription_name y->x OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id absent->x OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id absent->y OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id x->absent OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id x->y OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id y->absent OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id y->x OK_RECREATE +file_event_queue.provided_sqs.queue_url absent->x OK_RECREATE +file_event_queue.provided_sqs.queue_url absent->y OK_RECREATE +file_event_queue.provided_sqs.queue_url x->absent OK_RECREATE +file_event_queue.provided_sqs.queue_url x->y OK_RECREATE +file_event_queue.provided_sqs.queue_url y->absent OK_RECREATE +file_event_queue.provided_sqs.queue_url y->x OK_RECREATE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK +name y->x OK +read_only absent->false SUPPRESSED empty +read_only absent->true OK +read_only false->absent SUPPRESSED remote_already_set +read_only false->true OK +read_only true->absent OK +read_only true->false OK +skip_validation absent->false SUPPRESSED empty +skip_validation absent->true OK +skip_validation false->absent SUPPRESSED empty +skip_validation false->true OK +skip_validation true->absent OK +skip_validation true->false OK +url x->y OK +url y->x OK + +=== summary +OK 124 +OK_RECREATE 110 +SUPPRESSED 10 +NOT_OBSERVABLE 6 +DRIFT 2 + +=== not covered: 2 fields under a slice or map +grants[*].principal +grants[*].privileges[*] diff --git a/bundle/direct/dresources_test/out.genie_spaces.fields.txt b/bundle/direct/dresources_test/out.genie_spaces.fields.txt new file mode 100644 index 00000000000..2f92d9755d1 --- /dev/null +++ b/bundle/direct/dresources_test/out.genie_spaces.fields.txt @@ -0,0 +1,55 @@ +=== genie_space.yml.tmpl +description absent->x OK +description absent->y OK +description x->absent DRIFT description +description x->y OK +description y->absent DRIFT description +description y->x OK +etag absent->x SUPPRESSED custom +etag absent->y SUPPRESSED custom +etag x->absent SUPPRESSED custom +etag x->y SUPPRESSED custom +etag y->absent SUPPRESSED custom +etag y->x SUPPRESSED custom +file_path absent->x NOT_OBSERVABLE +file_path absent->y NOT_OBSERVABLE +file_path x->absent NOT_OBSERVABLE +file_path x->y NOT_OBSERVABLE +file_path y->absent NOT_OBSERVABLE +file_path y->x NOT_OBSERVABLE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +parent_path absent->x DRIFT parent_path +parent_path absent->y DRIFT parent_path +parent_path x->absent DRIFT parent_path +parent_path x->y DRIFT parent_path +parent_path y->absent DRIFT parent_path +parent_path y->x DRIFT parent_path +title absent->x OK +title absent->y OK +title x->absent DRIFT title +title x->y OK +title y->absent DRIFT title +title y->x OK +warehouse_id absent->x OK +warehouse_id absent->y OK +warehouse_id x->absent DRIFT warehouse_id +warehouse_id x->y OK +warehouse_id y->absent DRIFT warehouse_id +warehouse_id y->x OK + +=== summary +OK 12 +SUPPRESSED 6 +NOT_OBSERVABLE 12 +DRIFT 12 + +=== not covered: 4 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/dresources_test/out.instance_pools.fields.txt b/bundle/direct/dresources_test/out.instance_pools.fields.txt new file mode 100644 index 00000000000..1d0e705a1c9 --- /dev/null +++ b/bundle/direct/dresources_test/out.instance_pools.fields.txt @@ -0,0 +1,162 @@ +=== instance_pool.yml.tmpl +aws_attributes.availability absent->x OK +aws_attributes.availability absent->y OK +aws_attributes.availability x->absent OK +aws_attributes.availability x->y OK +aws_attributes.availability y->absent OK +aws_attributes.availability y->x OK +aws_attributes.instance_profile_arn absent->x OK +aws_attributes.instance_profile_arn absent->y OK +aws_attributes.instance_profile_arn x->absent OK +aws_attributes.instance_profile_arn x->y OK +aws_attributes.instance_profile_arn y->absent OK +aws_attributes.instance_profile_arn y->x OK +aws_attributes.spot_bid_price_percent 1->2 OK +aws_attributes.spot_bid_price_percent 1->absent OK +aws_attributes.spot_bid_price_percent 2->1 OK +aws_attributes.spot_bid_price_percent 2->absent OK +aws_attributes.spot_bid_price_percent absent->1 OK +aws_attributes.spot_bid_price_percent absent->2 OK +aws_attributes.zone_id absent->x OK +aws_attributes.zone_id absent->y OK +aws_attributes.zone_id x->absent OK +aws_attributes.zone_id x->y OK +aws_attributes.zone_id y->absent OK +aws_attributes.zone_id y->x OK +azure_attributes.availability absent->x OK +azure_attributes.availability absent->y OK +azure_attributes.availability x->absent OK +azure_attributes.availability x->y OK +azure_attributes.availability y->absent OK +azure_attributes.availability y->x OK +azure_attributes.capacity_reservation_group absent->x OK +azure_attributes.capacity_reservation_group absent->y OK +azure_attributes.capacity_reservation_group x->absent OK +azure_attributes.capacity_reservation_group x->y OK +azure_attributes.capacity_reservation_group y->absent OK +azure_attributes.capacity_reservation_group y->x OK +azure_attributes.spot_bid_max_price 1->2 OK +azure_attributes.spot_bid_max_price 1->absent OK +azure_attributes.spot_bid_max_price 2->1 OK +azure_attributes.spot_bid_max_price 2->absent OK +azure_attributes.spot_bid_max_price absent->1 OK +azure_attributes.spot_bid_max_price absent->2 OK +disk_spec.disk_count 1->2 OK_RECREATE +disk_spec.disk_count 1->absent OK_RECREATE +disk_spec.disk_count 2->1 OK_RECREATE +disk_spec.disk_count 2->absent OK_RECREATE +disk_spec.disk_count absent->1 OK_RECREATE +disk_spec.disk_count absent->2 OK_RECREATE +disk_spec.disk_iops 1->2 OK_RECREATE +disk_spec.disk_iops 1->absent OK_RECREATE +disk_spec.disk_iops 2->1 OK_RECREATE +disk_spec.disk_iops 2->absent OK_RECREATE +disk_spec.disk_iops absent->1 OK_RECREATE +disk_spec.disk_iops absent->2 OK_RECREATE +disk_spec.disk_size 1->2 OK_RECREATE +disk_spec.disk_size 1->absent OK_RECREATE +disk_spec.disk_size 2->1 OK_RECREATE +disk_spec.disk_size 2->absent OK_RECREATE +disk_spec.disk_size absent->1 OK_RECREATE +disk_spec.disk_size absent->2 OK_RECREATE +disk_spec.disk_throughput 1->2 OK_RECREATE +disk_spec.disk_throughput 1->absent OK_RECREATE +disk_spec.disk_throughput 2->1 OK_RECREATE +disk_spec.disk_throughput 2->absent OK_RECREATE +disk_spec.disk_throughput absent->1 OK_RECREATE +disk_spec.disk_throughput absent->2 OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type absent->x OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type absent->y OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type x->absent OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type x->y OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type y->absent OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type y->x OK_RECREATE +disk_spec.disk_type.ebs_volume_type absent->x OK_RECREATE +disk_spec.disk_type.ebs_volume_type absent->y OK_RECREATE +disk_spec.disk_type.ebs_volume_type x->absent OK_RECREATE +disk_spec.disk_type.ebs_volume_type x->y OK_RECREATE +disk_spec.disk_type.ebs_volume_type y->absent OK_RECREATE +disk_spec.disk_type.ebs_volume_type y->x OK_RECREATE +enable_elastic_disk absent->false SUPPRESSED empty +enable_elastic_disk absent->true DRIFT enable_elastic_disk +enable_elastic_disk false->absent SUPPRESSED remote_already_set +enable_elastic_disk false->true DRIFT enable_elastic_disk +enable_elastic_disk true->absent SUPPRESSED remote_already_set +enable_elastic_disk true->false OK +gcp_attributes.gcp_availability absent->x OK +gcp_attributes.gcp_availability absent->y OK +gcp_attributes.gcp_availability x->absent OK +gcp_attributes.gcp_availability x->y OK +gcp_attributes.gcp_availability y->absent OK +gcp_attributes.gcp_availability y->x OK +gcp_attributes.local_ssd_count 1->2 OK +gcp_attributes.local_ssd_count 1->absent OK +gcp_attributes.local_ssd_count 2->1 OK +gcp_attributes.local_ssd_count 2->absent OK +gcp_attributes.local_ssd_count absent->1 OK +gcp_attributes.local_ssd_count absent->2 OK +gcp_attributes.zone_id absent->x OK +gcp_attributes.zone_id absent->y OK +gcp_attributes.zone_id x->absent OK +gcp_attributes.zone_id x->y OK +gcp_attributes.zone_id y->absent OK +gcp_attributes.zone_id y->x OK +idle_instance_autotermination_minutes 1->2 OK +idle_instance_autotermination_minutes 1->absent OK +idle_instance_autotermination_minutes 2->1 OK +idle_instance_autotermination_minutes 2->absent OK +idle_instance_autotermination_minutes absent->1 OK +idle_instance_autotermination_minutes absent->2 OK +instance_pool_name x->y OK +instance_pool_name y->x OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +max_capacity 1->2 OK +max_capacity 1->absent OK +max_capacity 2->1 OK +max_capacity 2->absent OK +max_capacity absent->1 OK +max_capacity absent->2 OK +min_idle_instances 1->2 OK +min_idle_instances 1->absent OK +min_idle_instances 2->1 OK +min_idle_instances 2->absent OK +min_idle_instances absent->1 OK +min_idle_instances absent->2 OK +node_type_id x->y OK +node_type_id y->x OK +remote_disk_throughput 1->2 OK +remote_disk_throughput 1->absent OK +remote_disk_throughput 2->1 OK +remote_disk_throughput 2->absent OK +remote_disk_throughput absent->1 OK +remote_disk_throughput absent->2 OK +total_initial_remote_disk_size 1->2 OK +total_initial_remote_disk_size 1->absent OK +total_initial_remote_disk_size 2->1 OK +total_initial_remote_disk_size 2->absent OK +total_initial_remote_disk_size absent->1 OK +total_initial_remote_disk_size absent->2 OK + +=== summary +OK 95 +OK_RECREATE 36 +SUPPRESSED 3 +NOT_OBSERVABLE 6 +DRIFT 2 + +=== not covered: 10 fields under a slice or map +custom_tags.* +node_type_flexibility.alternate_node_type_ids[*] +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +preloaded_docker_images[*].basic_auth.password +preloaded_docker_images[*].basic_auth.username +preloaded_docker_images[*].url +preloaded_spark_versions[*] diff --git a/bundle/direct/dresources_test/out.jobs.fields.txt b/bundle/direct/dresources_test/out.jobs.fields.txt new file mode 100644 index 00000000000..3921160ee74 --- /dev/null +++ b/bundle/direct/dresources_test/out.jobs.fields.txt @@ -0,0 +1,2055 @@ +=== job.yml.tmpl +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +continuous.pause_status absent->x OK +continuous.pause_status absent->y OK +continuous.pause_status x->absent OK +continuous.pause_status x->y OK +continuous.pause_status y->absent OK +continuous.pause_status y->x OK +continuous.task_retry_mode absent->x OK +continuous.task_retry_mode absent->y OK +continuous.task_retry_mode x->absent OK +continuous.task_retry_mode x->y OK +continuous.task_retry_mode y->absent OK +continuous.task_retry_mode y->x OK +deployment.deployment_id absent->x OK +deployment.deployment_id absent->y OK +deployment.deployment_id x->absent OK +deployment.deployment_id x->y OK +deployment.deployment_id y->absent OK +deployment.deployment_id y->x OK +deployment.kind x->y OK +deployment.kind y->x OK +deployment.metadata_file_path absent->x OK +deployment.metadata_file_path absent->y OK +deployment.metadata_file_path x->absent OK +deployment.metadata_file_path x->y OK +deployment.metadata_file_path y->absent OK +deployment.metadata_file_path y->x OK +deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service +deployment.version_id x->absent NOT_OBSERVABLE +deployment.version_id x->y SUPPRESSED managed by the deployment metadata service +deployment.version_id y->absent NOT_OBSERVABLE +deployment.version_id y->x SUPPRESSED managed by the deployment metadata service +description absent->x OK +description absent->y OK +description x->absent OK +description x->y OK +description y->absent OK +description y->x OK +edit_mode absent->x OK +edit_mode absent->y OK +edit_mode x->absent OK +edit_mode x->y OK +edit_mode y->absent OK +edit_mode y->x OK +email_notifications.no_alert_for_skipped_runs absent->false OK +email_notifications.no_alert_for_skipped_runs absent->true OK +email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false->true OK +email_notifications.no_alert_for_skipped_runs true->absent OK +email_notifications.no_alert_for_skipped_runs true->false OK +format MULTI_TASK->SINGLE_TASK OK +format MULTI_TASK->absent OK +format SINGLE_TASK->MULTI_TASK OK +format SINGLE_TASK->absent OK +format absent->MULTI_TASK OK +format absent->SINGLE_TASK OK +git_source.git_branch - SKIPPED needs a coherent git_source block +git_source.git_commit - SKIPPED needs a coherent git_source block +git_source.git_provider - SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block +git_source.git_tag - SKIPPED needs a coherent git_source block +git_source.git_url - SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +max_concurrent_runs 1->5 OK +max_concurrent_runs 1->absent OK +max_concurrent_runs 5->1 OK +max_concurrent_runs 5->absent OK +max_concurrent_runs absent->1 OK +max_concurrent_runs absent->5 OK +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +notification_settings.no_alert_for_canceled_runs absent->false OK +notification_settings.no_alert_for_canceled_runs absent->true OK +notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false->true OK +notification_settings.no_alert_for_canceled_runs true->absent OK +notification_settings.no_alert_for_canceled_runs true->false OK +notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent->true OK +notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false->true OK +notification_settings.no_alert_for_skipped_runs true->absent OK +notification_settings.no_alert_for_skipped_runs true->false OK +parent_path absent->x OK +parent_path absent->y OK +parent_path x->absent OK +parent_path x->y OK +parent_path y->absent OK +parent_path y->x OK +performance_target PERFORMANCE_OPTIMIZED->STANDARD OK +performance_target PERFORMANCE_OPTIMIZED->absent OK +performance_target STANDARD->PERFORMANCE_OPTIMIZED OK +performance_target STANDARD->absent OK +performance_target absent->PERFORMANCE_OPTIMIZED OK +performance_target absent->STANDARD OK +queue.enabled false->true OK +queue.enabled true->false OK +run_as.group_name absent->x OK +run_as.group_name absent->y OK +run_as.group_name x->absent OK +run_as.group_name x->y OK +run_as.group_name y->absent OK +run_as.group_name y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +schedule.pause_status absent->x OK +schedule.pause_status absent->y OK +schedule.pause_status x->absent OK +schedule.pause_status x->y OK +schedule.pause_status y->absent OK +schedule.pause_status y->x OK +schedule.quartz_cron_expression x->y OK +schedule.quartz_cron_expression y->x OK +schedule.sql_condition.sql_query_id x->y OK +schedule.sql_condition.sql_query_id y->x OK +schedule.sql_condition.trigger_mode absent->x OK +schedule.sql_condition.trigger_mode absent->y OK +schedule.sql_condition.trigger_mode x->absent OK +schedule.sql_condition.trigger_mode x->y OK +schedule.sql_condition.trigger_mode y->absent OK +schedule.sql_condition.trigger_mode y->x OK +schedule.sql_condition.warehouse_id x->y OK +schedule.sql_condition.warehouse_id y->x OK +schedule.timezone_id x->y OK +schedule.timezone_id y->x OK +timeout_seconds 1200->600 OK +timeout_seconds 1200->absent OK +timeout_seconds 600->1200 OK +timeout_seconds 600->absent OK +timeout_seconds absent->1200 OK +timeout_seconds absent->600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK +trigger.file_arrival.url x->y OK +trigger.file_arrival.url y->x OK +trigger.file_arrival.wait_after_last_change_seconds 1->2 OK +trigger.file_arrival.wait_after_last_change_seconds 1->absent OK +trigger.file_arrival.wait_after_last_change_seconds 2->1 OK +trigger.file_arrival.wait_after_last_change_seconds 2->absent OK +trigger.file_arrival.wait_after_last_change_seconds absent->1 OK +trigger.file_arrival.wait_after_last_change_seconds absent->2 OK +trigger.model.condition x->y OK +trigger.model.condition y->x OK +trigger.model.min_time_between_triggers_seconds 1->2 OK +trigger.model.min_time_between_triggers_seconds 1->absent OK +trigger.model.min_time_between_triggers_seconds 2->1 OK +trigger.model.min_time_between_triggers_seconds 2->absent OK +trigger.model.min_time_between_triggers_seconds absent->1 OK +trigger.model.min_time_between_triggers_seconds absent->2 OK +trigger.model.securable_name absent->x OK +trigger.model.securable_name absent->y OK +trigger.model.securable_name x->absent OK +trigger.model.securable_name x->y OK +trigger.model.securable_name y->absent OK +trigger.model.securable_name y->x OK +trigger.model.wait_after_last_change_seconds 1->2 OK +trigger.model.wait_after_last_change_seconds 1->absent OK +trigger.model.wait_after_last_change_seconds 2->1 OK +trigger.model.wait_after_last_change_seconds 2->absent OK +trigger.model.wait_after_last_change_seconds absent->1 OK +trigger.model.wait_after_last_change_seconds absent->2 OK +trigger.pause_status absent->x OK +trigger.pause_status absent->y OK +trigger.pause_status x->absent OK +trigger.pause_status x->y OK +trigger.pause_status y->absent OK +trigger.pause_status y->x OK +trigger.periodic.interval 1->2 OK +trigger.periodic.interval 2->1 OK +trigger.periodic.unit x->y OK +trigger.periodic.unit y->x OK +trigger.sql_condition.sql_query_id x->y OK +trigger.sql_condition.sql_query_id y->x OK +trigger.sql_condition.trigger_mode absent->x OK +trigger.sql_condition.trigger_mode absent->y OK +trigger.sql_condition.trigger_mode x->absent OK +trigger.sql_condition.trigger_mode x->y OK +trigger.sql_condition.trigger_mode y->absent OK +trigger.sql_condition.trigger_mode y->x OK +trigger.sql_condition.warehouse_id x->y OK +trigger.sql_condition.warehouse_id y->x OK +trigger.table_update.condition absent->x OK +trigger.table_update.condition absent->y OK +trigger.table_update.condition x->absent OK +trigger.table_update.condition x->y OK +trigger.table_update.condition y->absent OK +trigger.table_update.condition y->x OK +trigger.table_update.min_time_between_triggers_seconds 1->2 OK +trigger.table_update.min_time_between_triggers_seconds 1->absent OK +trigger.table_update.min_time_between_triggers_seconds 2->1 OK +trigger.table_update.min_time_between_triggers_seconds 2->absent OK +trigger.table_update.min_time_between_triggers_seconds absent->1 OK +trigger.table_update.min_time_between_triggers_seconds absent->2 OK +trigger.table_update.wait_after_last_change_seconds 1->2 OK +trigger.table_update.wait_after_last_change_seconds 1->absent OK +trigger.table_update.wait_after_last_change_seconds 2->1 OK +trigger.table_update.wait_after_last_change_seconds 2->absent OK +trigger.table_update.wait_after_last_change_seconds absent->1 OK +trigger.table_update.wait_after_last_change_seconds absent->2 OK +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent OK +usage_policy_id x->y OK +usage_policy_id y->absent OK +usage_policy_id y->x OK + +=== job_apply_policy_default_values_for_each_task.yml.tmpl +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +continuous.pause_status absent->x OK +continuous.pause_status absent->y OK +continuous.pause_status x->absent OK +continuous.pause_status x->y OK +continuous.pause_status y->absent OK +continuous.pause_status y->x OK +continuous.task_retry_mode absent->x OK +continuous.task_retry_mode absent->y OK +continuous.task_retry_mode x->absent OK +continuous.task_retry_mode x->y OK +continuous.task_retry_mode y->absent OK +continuous.task_retry_mode y->x OK +deployment.deployment_id absent->x OK +deployment.deployment_id absent->y OK +deployment.deployment_id x->absent OK +deployment.deployment_id x->y OK +deployment.deployment_id y->absent OK +deployment.deployment_id y->x OK +deployment.kind x->y OK +deployment.kind y->x OK +deployment.metadata_file_path absent->x OK +deployment.metadata_file_path absent->y OK +deployment.metadata_file_path x->absent OK +deployment.metadata_file_path x->y OK +deployment.metadata_file_path y->absent OK +deployment.metadata_file_path y->x OK +deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service +deployment.version_id x->absent NOT_OBSERVABLE +deployment.version_id x->y SUPPRESSED managed by the deployment metadata service +deployment.version_id y->absent NOT_OBSERVABLE +deployment.version_id y->x SUPPRESSED managed by the deployment metadata service +description absent->x OK +description absent->y OK +description x->absent OK +description x->y OK +description y->absent OK +description y->x OK +edit_mode absent->x OK +edit_mode absent->y OK +edit_mode x->absent OK +edit_mode x->y OK +edit_mode y->absent OK +edit_mode y->x OK +email_notifications.no_alert_for_skipped_runs absent->false OK +email_notifications.no_alert_for_skipped_runs absent->true OK +email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false->true OK +email_notifications.no_alert_for_skipped_runs true->absent OK +email_notifications.no_alert_for_skipped_runs true->false OK +format MULTI_TASK->SINGLE_TASK OK +format MULTI_TASK->absent OK +format SINGLE_TASK->MULTI_TASK OK +format SINGLE_TASK->absent OK +format absent->MULTI_TASK OK +format absent->SINGLE_TASK OK +git_source.git_branch - SKIPPED needs a coherent git_source block +git_source.git_commit - SKIPPED needs a coherent git_source block +git_source.git_provider - SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block +git_source.git_tag - SKIPPED needs a coherent git_source block +git_source.git_url - SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +max_concurrent_runs 1->5 OK +max_concurrent_runs 1->absent OK +max_concurrent_runs 5->1 OK +max_concurrent_runs 5->absent OK +max_concurrent_runs absent->1 OK +max_concurrent_runs absent->5 OK +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +notification_settings.no_alert_for_canceled_runs absent->false OK +notification_settings.no_alert_for_canceled_runs absent->true OK +notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false->true OK +notification_settings.no_alert_for_canceled_runs true->absent OK +notification_settings.no_alert_for_canceled_runs true->false OK +notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent->true OK +notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false->true OK +notification_settings.no_alert_for_skipped_runs true->absent OK +notification_settings.no_alert_for_skipped_runs true->false OK +parent_path absent->x OK +parent_path absent->y OK +parent_path x->absent OK +parent_path x->y OK +parent_path y->absent OK +parent_path y->x OK +performance_target PERFORMANCE_OPTIMIZED->STANDARD OK +performance_target PERFORMANCE_OPTIMIZED->absent OK +performance_target STANDARD->PERFORMANCE_OPTIMIZED OK +performance_target STANDARD->absent OK +performance_target absent->PERFORMANCE_OPTIMIZED OK +performance_target absent->STANDARD OK +queue.enabled false->true OK +queue.enabled true->false OK +run_as.group_name absent->x OK +run_as.group_name absent->y OK +run_as.group_name x->absent OK +run_as.group_name x->y OK +run_as.group_name y->absent OK +run_as.group_name y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +schedule.pause_status absent->x OK +schedule.pause_status absent->y OK +schedule.pause_status x->absent OK +schedule.pause_status x->y OK +schedule.pause_status y->absent OK +schedule.pause_status y->x OK +schedule.quartz_cron_expression x->y OK +schedule.quartz_cron_expression y->x OK +schedule.sql_condition.sql_query_id x->y OK +schedule.sql_condition.sql_query_id y->x OK +schedule.sql_condition.trigger_mode absent->x OK +schedule.sql_condition.trigger_mode absent->y OK +schedule.sql_condition.trigger_mode x->absent OK +schedule.sql_condition.trigger_mode x->y OK +schedule.sql_condition.trigger_mode y->absent OK +schedule.sql_condition.trigger_mode y->x OK +schedule.sql_condition.warehouse_id x->y OK +schedule.sql_condition.warehouse_id y->x OK +schedule.timezone_id x->y OK +schedule.timezone_id y->x OK +timeout_seconds 1200->600 OK +timeout_seconds 1200->absent OK +timeout_seconds 600->1200 OK +timeout_seconds 600->absent OK +timeout_seconds absent->1200 OK +timeout_seconds absent->600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK +trigger.file_arrival.url x->y OK +trigger.file_arrival.url y->x OK +trigger.file_arrival.wait_after_last_change_seconds 1->2 OK +trigger.file_arrival.wait_after_last_change_seconds 1->absent OK +trigger.file_arrival.wait_after_last_change_seconds 2->1 OK +trigger.file_arrival.wait_after_last_change_seconds 2->absent OK +trigger.file_arrival.wait_after_last_change_seconds absent->1 OK +trigger.file_arrival.wait_after_last_change_seconds absent->2 OK +trigger.model.condition x->y OK +trigger.model.condition y->x OK +trigger.model.min_time_between_triggers_seconds 1->2 OK +trigger.model.min_time_between_triggers_seconds 1->absent OK +trigger.model.min_time_between_triggers_seconds 2->1 OK +trigger.model.min_time_between_triggers_seconds 2->absent OK +trigger.model.min_time_between_triggers_seconds absent->1 OK +trigger.model.min_time_between_triggers_seconds absent->2 OK +trigger.model.securable_name absent->x OK +trigger.model.securable_name absent->y OK +trigger.model.securable_name x->absent OK +trigger.model.securable_name x->y OK +trigger.model.securable_name y->absent OK +trigger.model.securable_name y->x OK +trigger.model.wait_after_last_change_seconds 1->2 OK +trigger.model.wait_after_last_change_seconds 1->absent OK +trigger.model.wait_after_last_change_seconds 2->1 OK +trigger.model.wait_after_last_change_seconds 2->absent OK +trigger.model.wait_after_last_change_seconds absent->1 OK +trigger.model.wait_after_last_change_seconds absent->2 OK +trigger.pause_status absent->x OK +trigger.pause_status absent->y OK +trigger.pause_status x->absent OK +trigger.pause_status x->y OK +trigger.pause_status y->absent OK +trigger.pause_status y->x OK +trigger.periodic.interval 1->2 OK +trigger.periodic.interval 2->1 OK +trigger.periodic.unit x->y OK +trigger.periodic.unit y->x OK +trigger.sql_condition.sql_query_id x->y OK +trigger.sql_condition.sql_query_id y->x OK +trigger.sql_condition.trigger_mode absent->x OK +trigger.sql_condition.trigger_mode absent->y OK +trigger.sql_condition.trigger_mode x->absent OK +trigger.sql_condition.trigger_mode x->y OK +trigger.sql_condition.trigger_mode y->absent OK +trigger.sql_condition.trigger_mode y->x OK +trigger.sql_condition.warehouse_id x->y OK +trigger.sql_condition.warehouse_id y->x OK +trigger.table_update.condition absent->x OK +trigger.table_update.condition absent->y OK +trigger.table_update.condition x->absent OK +trigger.table_update.condition x->y OK +trigger.table_update.condition y->absent OK +trigger.table_update.condition y->x OK +trigger.table_update.min_time_between_triggers_seconds 1->2 OK +trigger.table_update.min_time_between_triggers_seconds 1->absent OK +trigger.table_update.min_time_between_triggers_seconds 2->1 OK +trigger.table_update.min_time_between_triggers_seconds 2->absent OK +trigger.table_update.min_time_between_triggers_seconds absent->1 OK +trigger.table_update.min_time_between_triggers_seconds absent->2 OK +trigger.table_update.wait_after_last_change_seconds 1->2 OK +trigger.table_update.wait_after_last_change_seconds 1->absent OK +trigger.table_update.wait_after_last_change_seconds 2->1 OK +trigger.table_update.wait_after_last_change_seconds 2->absent OK +trigger.table_update.wait_after_last_change_seconds absent->1 OK +trigger.table_update.wait_after_last_change_seconds absent->2 OK +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent OK +usage_policy_id x->y OK +usage_policy_id y->absent OK +usage_policy_id y->x OK + +=== job_apply_policy_default_values_job_cluster.yml.tmpl +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +continuous.pause_status absent->x OK +continuous.pause_status absent->y OK +continuous.pause_status x->absent OK +continuous.pause_status x->y OK +continuous.pause_status y->absent OK +continuous.pause_status y->x OK +continuous.task_retry_mode absent->x OK +continuous.task_retry_mode absent->y OK +continuous.task_retry_mode x->absent OK +continuous.task_retry_mode x->y OK +continuous.task_retry_mode y->absent OK +continuous.task_retry_mode y->x OK +deployment.deployment_id absent->x OK +deployment.deployment_id absent->y OK +deployment.deployment_id x->absent OK +deployment.deployment_id x->y OK +deployment.deployment_id y->absent OK +deployment.deployment_id y->x OK +deployment.kind x->y OK +deployment.kind y->x OK +deployment.metadata_file_path absent->x OK +deployment.metadata_file_path absent->y OK +deployment.metadata_file_path x->absent OK +deployment.metadata_file_path x->y OK +deployment.metadata_file_path y->absent OK +deployment.metadata_file_path y->x OK +deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service +deployment.version_id x->absent NOT_OBSERVABLE +deployment.version_id x->y SUPPRESSED managed by the deployment metadata service +deployment.version_id y->absent NOT_OBSERVABLE +deployment.version_id y->x SUPPRESSED managed by the deployment metadata service +description absent->x OK +description absent->y OK +description x->absent OK +description x->y OK +description y->absent OK +description y->x OK +edit_mode absent->x OK +edit_mode absent->y OK +edit_mode x->absent OK +edit_mode x->y OK +edit_mode y->absent OK +edit_mode y->x OK +email_notifications.no_alert_for_skipped_runs absent->false OK +email_notifications.no_alert_for_skipped_runs absent->true OK +email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false->true OK +email_notifications.no_alert_for_skipped_runs true->absent OK +email_notifications.no_alert_for_skipped_runs true->false OK +format MULTI_TASK->SINGLE_TASK OK +format MULTI_TASK->absent OK +format SINGLE_TASK->MULTI_TASK OK +format SINGLE_TASK->absent OK +format absent->MULTI_TASK OK +format absent->SINGLE_TASK OK +git_source.git_branch - SKIPPED needs a coherent git_source block +git_source.git_commit - SKIPPED needs a coherent git_source block +git_source.git_provider - SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block +git_source.git_tag - SKIPPED needs a coherent git_source block +git_source.git_url - SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +max_concurrent_runs 1->5 OK +max_concurrent_runs 1->absent OK +max_concurrent_runs 5->1 OK +max_concurrent_runs 5->absent OK +max_concurrent_runs absent->1 OK +max_concurrent_runs absent->5 OK +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +notification_settings.no_alert_for_canceled_runs absent->false OK +notification_settings.no_alert_for_canceled_runs absent->true OK +notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false->true OK +notification_settings.no_alert_for_canceled_runs true->absent OK +notification_settings.no_alert_for_canceled_runs true->false OK +notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent->true OK +notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false->true OK +notification_settings.no_alert_for_skipped_runs true->absent OK +notification_settings.no_alert_for_skipped_runs true->false OK +parent_path absent->x OK +parent_path absent->y OK +parent_path x->absent OK +parent_path x->y OK +parent_path y->absent OK +parent_path y->x OK +performance_target PERFORMANCE_OPTIMIZED->STANDARD OK +performance_target PERFORMANCE_OPTIMIZED->absent OK +performance_target STANDARD->PERFORMANCE_OPTIMIZED OK +performance_target STANDARD->absent OK +performance_target absent->PERFORMANCE_OPTIMIZED OK +performance_target absent->STANDARD OK +queue.enabled false->true OK +queue.enabled true->false OK +run_as.group_name absent->x OK +run_as.group_name absent->y OK +run_as.group_name x->absent OK +run_as.group_name x->y OK +run_as.group_name y->absent OK +run_as.group_name y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +schedule.pause_status absent->x OK +schedule.pause_status absent->y OK +schedule.pause_status x->absent OK +schedule.pause_status x->y OK +schedule.pause_status y->absent OK +schedule.pause_status y->x OK +schedule.quartz_cron_expression x->y OK +schedule.quartz_cron_expression y->x OK +schedule.sql_condition.sql_query_id x->y OK +schedule.sql_condition.sql_query_id y->x OK +schedule.sql_condition.trigger_mode absent->x OK +schedule.sql_condition.trigger_mode absent->y OK +schedule.sql_condition.trigger_mode x->absent OK +schedule.sql_condition.trigger_mode x->y OK +schedule.sql_condition.trigger_mode y->absent OK +schedule.sql_condition.trigger_mode y->x OK +schedule.sql_condition.warehouse_id x->y OK +schedule.sql_condition.warehouse_id y->x OK +schedule.timezone_id x->y OK +schedule.timezone_id y->x OK +timeout_seconds 1200->600 OK +timeout_seconds 1200->absent OK +timeout_seconds 600->1200 OK +timeout_seconds 600->absent OK +timeout_seconds absent->1200 OK +timeout_seconds absent->600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK +trigger.file_arrival.url x->y OK +trigger.file_arrival.url y->x OK +trigger.file_arrival.wait_after_last_change_seconds 1->2 OK +trigger.file_arrival.wait_after_last_change_seconds 1->absent OK +trigger.file_arrival.wait_after_last_change_seconds 2->1 OK +trigger.file_arrival.wait_after_last_change_seconds 2->absent OK +trigger.file_arrival.wait_after_last_change_seconds absent->1 OK +trigger.file_arrival.wait_after_last_change_seconds absent->2 OK +trigger.model.condition x->y OK +trigger.model.condition y->x OK +trigger.model.min_time_between_triggers_seconds 1->2 OK +trigger.model.min_time_between_triggers_seconds 1->absent OK +trigger.model.min_time_between_triggers_seconds 2->1 OK +trigger.model.min_time_between_triggers_seconds 2->absent OK +trigger.model.min_time_between_triggers_seconds absent->1 OK +trigger.model.min_time_between_triggers_seconds absent->2 OK +trigger.model.securable_name absent->x OK +trigger.model.securable_name absent->y OK +trigger.model.securable_name x->absent OK +trigger.model.securable_name x->y OK +trigger.model.securable_name y->absent OK +trigger.model.securable_name y->x OK +trigger.model.wait_after_last_change_seconds 1->2 OK +trigger.model.wait_after_last_change_seconds 1->absent OK +trigger.model.wait_after_last_change_seconds 2->1 OK +trigger.model.wait_after_last_change_seconds 2->absent OK +trigger.model.wait_after_last_change_seconds absent->1 OK +trigger.model.wait_after_last_change_seconds absent->2 OK +trigger.pause_status absent->x OK +trigger.pause_status absent->y OK +trigger.pause_status x->absent OK +trigger.pause_status x->y OK +trigger.pause_status y->absent OK +trigger.pause_status y->x OK +trigger.periodic.interval 1->2 OK +trigger.periodic.interval 2->1 OK +trigger.periodic.unit x->y OK +trigger.periodic.unit y->x OK +trigger.sql_condition.sql_query_id x->y OK +trigger.sql_condition.sql_query_id y->x OK +trigger.sql_condition.trigger_mode absent->x OK +trigger.sql_condition.trigger_mode absent->y OK +trigger.sql_condition.trigger_mode x->absent OK +trigger.sql_condition.trigger_mode x->y OK +trigger.sql_condition.trigger_mode y->absent OK +trigger.sql_condition.trigger_mode y->x OK +trigger.sql_condition.warehouse_id x->y OK +trigger.sql_condition.warehouse_id y->x OK +trigger.table_update.condition absent->x OK +trigger.table_update.condition absent->y OK +trigger.table_update.condition x->absent OK +trigger.table_update.condition x->y OK +trigger.table_update.condition y->absent OK +trigger.table_update.condition y->x OK +trigger.table_update.min_time_between_triggers_seconds 1->2 OK +trigger.table_update.min_time_between_triggers_seconds 1->absent OK +trigger.table_update.min_time_between_triggers_seconds 2->1 OK +trigger.table_update.min_time_between_triggers_seconds 2->absent OK +trigger.table_update.min_time_between_triggers_seconds absent->1 OK +trigger.table_update.min_time_between_triggers_seconds absent->2 OK +trigger.table_update.wait_after_last_change_seconds 1->2 OK +trigger.table_update.wait_after_last_change_seconds 1->absent OK +trigger.table_update.wait_after_last_change_seconds 2->1 OK +trigger.table_update.wait_after_last_change_seconds 2->absent OK +trigger.table_update.wait_after_last_change_seconds absent->1 OK +trigger.table_update.wait_after_last_change_seconds absent->2 OK +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent OK +usage_policy_id x->y OK +usage_policy_id y->absent OK +usage_policy_id y->x OK + +=== job_apply_policy_default_values_task_cluster.yml.tmpl +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +continuous.pause_status absent->x OK +continuous.pause_status absent->y OK +continuous.pause_status x->absent OK +continuous.pause_status x->y OK +continuous.pause_status y->absent OK +continuous.pause_status y->x OK +continuous.task_retry_mode absent->x OK +continuous.task_retry_mode absent->y OK +continuous.task_retry_mode x->absent OK +continuous.task_retry_mode x->y OK +continuous.task_retry_mode y->absent OK +continuous.task_retry_mode y->x OK +deployment.deployment_id absent->x OK +deployment.deployment_id absent->y OK +deployment.deployment_id x->absent OK +deployment.deployment_id x->y OK +deployment.deployment_id y->absent OK +deployment.deployment_id y->x OK +deployment.kind x->y OK +deployment.kind y->x OK +deployment.metadata_file_path absent->x OK +deployment.metadata_file_path absent->y OK +deployment.metadata_file_path x->absent OK +deployment.metadata_file_path x->y OK +deployment.metadata_file_path y->absent OK +deployment.metadata_file_path y->x OK +deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service +deployment.version_id x->absent NOT_OBSERVABLE +deployment.version_id x->y SUPPRESSED managed by the deployment metadata service +deployment.version_id y->absent NOT_OBSERVABLE +deployment.version_id y->x SUPPRESSED managed by the deployment metadata service +description absent->x OK +description absent->y OK +description x->absent OK +description x->y OK +description y->absent OK +description y->x OK +edit_mode absent->x OK +edit_mode absent->y OK +edit_mode x->absent OK +edit_mode x->y OK +edit_mode y->absent OK +edit_mode y->x OK +email_notifications.no_alert_for_skipped_runs absent->false OK +email_notifications.no_alert_for_skipped_runs absent->true OK +email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false->true OK +email_notifications.no_alert_for_skipped_runs true->absent OK +email_notifications.no_alert_for_skipped_runs true->false OK +format MULTI_TASK->SINGLE_TASK OK +format MULTI_TASK->absent OK +format SINGLE_TASK->MULTI_TASK OK +format SINGLE_TASK->absent OK +format absent->MULTI_TASK OK +format absent->SINGLE_TASK OK +git_source.git_branch - SKIPPED needs a coherent git_source block +git_source.git_commit - SKIPPED needs a coherent git_source block +git_source.git_provider - SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block +git_source.git_tag - SKIPPED needs a coherent git_source block +git_source.git_url - SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +max_concurrent_runs 1->5 OK +max_concurrent_runs 1->absent OK +max_concurrent_runs 5->1 OK +max_concurrent_runs 5->absent OK +max_concurrent_runs absent->1 OK +max_concurrent_runs absent->5 OK +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +notification_settings.no_alert_for_canceled_runs absent->false OK +notification_settings.no_alert_for_canceled_runs absent->true OK +notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false->true OK +notification_settings.no_alert_for_canceled_runs true->absent OK +notification_settings.no_alert_for_canceled_runs true->false OK +notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent->true OK +notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false->true OK +notification_settings.no_alert_for_skipped_runs true->absent OK +notification_settings.no_alert_for_skipped_runs true->false OK +parent_path absent->x OK +parent_path absent->y OK +parent_path x->absent OK +parent_path x->y OK +parent_path y->absent OK +parent_path y->x OK +performance_target PERFORMANCE_OPTIMIZED->STANDARD OK +performance_target PERFORMANCE_OPTIMIZED->absent OK +performance_target STANDARD->PERFORMANCE_OPTIMIZED OK +performance_target STANDARD->absent OK +performance_target absent->PERFORMANCE_OPTIMIZED OK +performance_target absent->STANDARD OK +queue.enabled false->true OK +queue.enabled true->false OK +run_as.group_name absent->x OK +run_as.group_name absent->y OK +run_as.group_name x->absent OK +run_as.group_name x->y OK +run_as.group_name y->absent OK +run_as.group_name y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +schedule.pause_status absent->x OK +schedule.pause_status absent->y OK +schedule.pause_status x->absent OK +schedule.pause_status x->y OK +schedule.pause_status y->absent OK +schedule.pause_status y->x OK +schedule.quartz_cron_expression x->y OK +schedule.quartz_cron_expression y->x OK +schedule.sql_condition.sql_query_id x->y OK +schedule.sql_condition.sql_query_id y->x OK +schedule.sql_condition.trigger_mode absent->x OK +schedule.sql_condition.trigger_mode absent->y OK +schedule.sql_condition.trigger_mode x->absent OK +schedule.sql_condition.trigger_mode x->y OK +schedule.sql_condition.trigger_mode y->absent OK +schedule.sql_condition.trigger_mode y->x OK +schedule.sql_condition.warehouse_id x->y OK +schedule.sql_condition.warehouse_id y->x OK +schedule.timezone_id x->y OK +schedule.timezone_id y->x OK +timeout_seconds 1200->600 OK +timeout_seconds 1200->absent OK +timeout_seconds 600->1200 OK +timeout_seconds 600->absent OK +timeout_seconds absent->1200 OK +timeout_seconds absent->600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK +trigger.file_arrival.url x->y OK +trigger.file_arrival.url y->x OK +trigger.file_arrival.wait_after_last_change_seconds 1->2 OK +trigger.file_arrival.wait_after_last_change_seconds 1->absent OK +trigger.file_arrival.wait_after_last_change_seconds 2->1 OK +trigger.file_arrival.wait_after_last_change_seconds 2->absent OK +trigger.file_arrival.wait_after_last_change_seconds absent->1 OK +trigger.file_arrival.wait_after_last_change_seconds absent->2 OK +trigger.model.condition x->y OK +trigger.model.condition y->x OK +trigger.model.min_time_between_triggers_seconds 1->2 OK +trigger.model.min_time_between_triggers_seconds 1->absent OK +trigger.model.min_time_between_triggers_seconds 2->1 OK +trigger.model.min_time_between_triggers_seconds 2->absent OK +trigger.model.min_time_between_triggers_seconds absent->1 OK +trigger.model.min_time_between_triggers_seconds absent->2 OK +trigger.model.securable_name absent->x OK +trigger.model.securable_name absent->y OK +trigger.model.securable_name x->absent OK +trigger.model.securable_name x->y OK +trigger.model.securable_name y->absent OK +trigger.model.securable_name y->x OK +trigger.model.wait_after_last_change_seconds 1->2 OK +trigger.model.wait_after_last_change_seconds 1->absent OK +trigger.model.wait_after_last_change_seconds 2->1 OK +trigger.model.wait_after_last_change_seconds 2->absent OK +trigger.model.wait_after_last_change_seconds absent->1 OK +trigger.model.wait_after_last_change_seconds absent->2 OK +trigger.pause_status absent->x OK +trigger.pause_status absent->y OK +trigger.pause_status x->absent OK +trigger.pause_status x->y OK +trigger.pause_status y->absent OK +trigger.pause_status y->x OK +trigger.periodic.interval 1->2 OK +trigger.periodic.interval 2->1 OK +trigger.periodic.unit x->y OK +trigger.periodic.unit y->x OK +trigger.sql_condition.sql_query_id x->y OK +trigger.sql_condition.sql_query_id y->x OK +trigger.sql_condition.trigger_mode absent->x OK +trigger.sql_condition.trigger_mode absent->y OK +trigger.sql_condition.trigger_mode x->absent OK +trigger.sql_condition.trigger_mode x->y OK +trigger.sql_condition.trigger_mode y->absent OK +trigger.sql_condition.trigger_mode y->x OK +trigger.sql_condition.warehouse_id x->y OK +trigger.sql_condition.warehouse_id y->x OK +trigger.table_update.condition absent->x OK +trigger.table_update.condition absent->y OK +trigger.table_update.condition x->absent OK +trigger.table_update.condition x->y OK +trigger.table_update.condition y->absent OK +trigger.table_update.condition y->x OK +trigger.table_update.min_time_between_triggers_seconds 1->2 OK +trigger.table_update.min_time_between_triggers_seconds 1->absent OK +trigger.table_update.min_time_between_triggers_seconds 2->1 OK +trigger.table_update.min_time_between_triggers_seconds 2->absent OK +trigger.table_update.min_time_between_triggers_seconds absent->1 OK +trigger.table_update.min_time_between_triggers_seconds absent->2 OK +trigger.table_update.wait_after_last_change_seconds 1->2 OK +trigger.table_update.wait_after_last_change_seconds 1->absent OK +trigger.table_update.wait_after_last_change_seconds 2->1 OK +trigger.table_update.wait_after_last_change_seconds 2->absent OK +trigger.table_update.wait_after_last_change_seconds absent->1 OK +trigger.table_update.wait_after_last_change_seconds absent->2 OK +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent OK +usage_policy_id x->y OK +usage_policy_id y->absent OK +usage_policy_id y->x OK + +=== job_with_depends_on.yml.tmpl +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +continuous.pause_status absent->x OK +continuous.pause_status absent->y OK +continuous.pause_status x->absent OK +continuous.pause_status x->y OK +continuous.pause_status y->absent OK +continuous.pause_status y->x OK +continuous.task_retry_mode absent->x OK +continuous.task_retry_mode absent->y OK +continuous.task_retry_mode x->absent OK +continuous.task_retry_mode x->y OK +continuous.task_retry_mode y->absent OK +continuous.task_retry_mode y->x OK +deployment.deployment_id absent->x OK +deployment.deployment_id absent->y OK +deployment.deployment_id x->absent OK +deployment.deployment_id x->y OK +deployment.deployment_id y->absent OK +deployment.deployment_id y->x OK +deployment.kind x->y OK +deployment.kind y->x OK +deployment.metadata_file_path absent->x OK +deployment.metadata_file_path absent->y OK +deployment.metadata_file_path x->absent OK +deployment.metadata_file_path x->y OK +deployment.metadata_file_path y->absent OK +deployment.metadata_file_path y->x OK +deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service +deployment.version_id x->absent NOT_OBSERVABLE +deployment.version_id x->y SUPPRESSED managed by the deployment metadata service +deployment.version_id y->absent NOT_OBSERVABLE +deployment.version_id y->x SUPPRESSED managed by the deployment metadata service +description absent->x OK +description absent->y OK +description x->absent OK +description x->y OK +description y->absent OK +description y->x OK +edit_mode absent->x OK +edit_mode absent->y OK +edit_mode x->absent OK +edit_mode x->y OK +edit_mode y->absent OK +edit_mode y->x OK +email_notifications.no_alert_for_skipped_runs absent->false OK +email_notifications.no_alert_for_skipped_runs absent->true OK +email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false->true OK +email_notifications.no_alert_for_skipped_runs true->absent OK +email_notifications.no_alert_for_skipped_runs true->false OK +format MULTI_TASK->SINGLE_TASK OK +format MULTI_TASK->absent OK +format SINGLE_TASK->MULTI_TASK OK +format SINGLE_TASK->absent OK +format absent->MULTI_TASK OK +format absent->SINGLE_TASK OK +git_source.git_branch - SKIPPED needs a coherent git_source block +git_source.git_commit - SKIPPED needs a coherent git_source block +git_source.git_provider - SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block +git_source.git_tag - SKIPPED needs a coherent git_source block +git_source.git_url - SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +max_concurrent_runs 1->5 OK +max_concurrent_runs 1->absent OK +max_concurrent_runs 5->1 OK +max_concurrent_runs 5->absent OK +max_concurrent_runs absent->1 OK +max_concurrent_runs absent->5 OK +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +notification_settings.no_alert_for_canceled_runs absent->false OK +notification_settings.no_alert_for_canceled_runs absent->true OK +notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false->true OK +notification_settings.no_alert_for_canceled_runs true->absent OK +notification_settings.no_alert_for_canceled_runs true->false OK +notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent->true OK +notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false->true OK +notification_settings.no_alert_for_skipped_runs true->absent OK +notification_settings.no_alert_for_skipped_runs true->false OK +parent_path absent->x OK +parent_path absent->y OK +parent_path x->absent OK +parent_path x->y OK +parent_path y->absent OK +parent_path y->x OK +performance_target PERFORMANCE_OPTIMIZED->STANDARD OK +performance_target PERFORMANCE_OPTIMIZED->absent OK +performance_target STANDARD->PERFORMANCE_OPTIMIZED OK +performance_target STANDARD->absent OK +performance_target absent->PERFORMANCE_OPTIMIZED OK +performance_target absent->STANDARD OK +queue.enabled false->true OK +queue.enabled true->false OK +run_as.group_name absent->x OK +run_as.group_name absent->y OK +run_as.group_name x->absent OK +run_as.group_name x->y OK +run_as.group_name y->absent OK +run_as.group_name y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +schedule.pause_status absent->x OK +schedule.pause_status absent->y OK +schedule.pause_status x->absent OK +schedule.pause_status x->y OK +schedule.pause_status y->absent OK +schedule.pause_status y->x OK +schedule.quartz_cron_expression x->y OK +schedule.quartz_cron_expression y->x OK +schedule.sql_condition.sql_query_id x->y OK +schedule.sql_condition.sql_query_id y->x OK +schedule.sql_condition.trigger_mode absent->x OK +schedule.sql_condition.trigger_mode absent->y OK +schedule.sql_condition.trigger_mode x->absent OK +schedule.sql_condition.trigger_mode x->y OK +schedule.sql_condition.trigger_mode y->absent OK +schedule.sql_condition.trigger_mode y->x OK +schedule.sql_condition.warehouse_id x->y OK +schedule.sql_condition.warehouse_id y->x OK +schedule.timezone_id x->y OK +schedule.timezone_id y->x OK +timeout_seconds 1200->600 OK +timeout_seconds 1200->absent OK +timeout_seconds 600->1200 OK +timeout_seconds 600->absent OK +timeout_seconds absent->1200 OK +timeout_seconds absent->600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK +trigger.file_arrival.url x->y OK +trigger.file_arrival.url y->x OK +trigger.file_arrival.wait_after_last_change_seconds 1->2 OK +trigger.file_arrival.wait_after_last_change_seconds 1->absent OK +trigger.file_arrival.wait_after_last_change_seconds 2->1 OK +trigger.file_arrival.wait_after_last_change_seconds 2->absent OK +trigger.file_arrival.wait_after_last_change_seconds absent->1 OK +trigger.file_arrival.wait_after_last_change_seconds absent->2 OK +trigger.model.condition x->y OK +trigger.model.condition y->x OK +trigger.model.min_time_between_triggers_seconds 1->2 OK +trigger.model.min_time_between_triggers_seconds 1->absent OK +trigger.model.min_time_between_triggers_seconds 2->1 OK +trigger.model.min_time_between_triggers_seconds 2->absent OK +trigger.model.min_time_between_triggers_seconds absent->1 OK +trigger.model.min_time_between_triggers_seconds absent->2 OK +trigger.model.securable_name absent->x OK +trigger.model.securable_name absent->y OK +trigger.model.securable_name x->absent OK +trigger.model.securable_name x->y OK +trigger.model.securable_name y->absent OK +trigger.model.securable_name y->x OK +trigger.model.wait_after_last_change_seconds 1->2 OK +trigger.model.wait_after_last_change_seconds 1->absent OK +trigger.model.wait_after_last_change_seconds 2->1 OK +trigger.model.wait_after_last_change_seconds 2->absent OK +trigger.model.wait_after_last_change_seconds absent->1 OK +trigger.model.wait_after_last_change_seconds absent->2 OK +trigger.pause_status absent->x OK +trigger.pause_status absent->y OK +trigger.pause_status x->absent OK +trigger.pause_status x->y OK +trigger.pause_status y->absent OK +trigger.pause_status y->x OK +trigger.periodic.interval 1->2 OK +trigger.periodic.interval 2->1 OK +trigger.periodic.unit x->y OK +trigger.periodic.unit y->x OK +trigger.sql_condition.sql_query_id x->y OK +trigger.sql_condition.sql_query_id y->x OK +trigger.sql_condition.trigger_mode absent->x OK +trigger.sql_condition.trigger_mode absent->y OK +trigger.sql_condition.trigger_mode x->absent OK +trigger.sql_condition.trigger_mode x->y OK +trigger.sql_condition.trigger_mode y->absent OK +trigger.sql_condition.trigger_mode y->x OK +trigger.sql_condition.warehouse_id x->y OK +trigger.sql_condition.warehouse_id y->x OK +trigger.table_update.condition absent->x OK +trigger.table_update.condition absent->y OK +trigger.table_update.condition x->absent OK +trigger.table_update.condition x->y OK +trigger.table_update.condition y->absent OK +trigger.table_update.condition y->x OK +trigger.table_update.min_time_between_triggers_seconds 1->2 OK +trigger.table_update.min_time_between_triggers_seconds 1->absent OK +trigger.table_update.min_time_between_triggers_seconds 2->1 OK +trigger.table_update.min_time_between_triggers_seconds 2->absent OK +trigger.table_update.min_time_between_triggers_seconds absent->1 OK +trigger.table_update.min_time_between_triggers_seconds absent->2 OK +trigger.table_update.wait_after_last_change_seconds 1->2 OK +trigger.table_update.wait_after_last_change_seconds 1->absent OK +trigger.table_update.wait_after_last_change_seconds 2->1 OK +trigger.table_update.wait_after_last_change_seconds 2->absent OK +trigger.table_update.wait_after_last_change_seconds absent->1 OK +trigger.table_update.wait_after_last_change_seconds absent->2 OK +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent OK +usage_policy_id x->y OK +usage_policy_id y->absent OK +usage_policy_id y->x OK + +=== job_with_task.yml.tmpl +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +continuous.pause_status absent->x OK +continuous.pause_status absent->y OK +continuous.pause_status x->absent OK +continuous.pause_status x->y OK +continuous.pause_status y->absent OK +continuous.pause_status y->x OK +continuous.task_retry_mode absent->x OK +continuous.task_retry_mode absent->y OK +continuous.task_retry_mode x->absent OK +continuous.task_retry_mode x->y OK +continuous.task_retry_mode y->absent OK +continuous.task_retry_mode y->x OK +deployment.deployment_id absent->x OK +deployment.deployment_id absent->y OK +deployment.deployment_id x->absent OK +deployment.deployment_id x->y OK +deployment.deployment_id y->absent OK +deployment.deployment_id y->x OK +deployment.kind x->y OK +deployment.kind y->x OK +deployment.metadata_file_path absent->x OK +deployment.metadata_file_path absent->y OK +deployment.metadata_file_path x->absent OK +deployment.metadata_file_path x->y OK +deployment.metadata_file_path y->absent OK +deployment.metadata_file_path y->x OK +deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service +deployment.version_id x->absent NOT_OBSERVABLE +deployment.version_id x->y SUPPRESSED managed by the deployment metadata service +deployment.version_id y->absent NOT_OBSERVABLE +deployment.version_id y->x SUPPRESSED managed by the deployment metadata service +description absent->x OK +description absent->y OK +description x->absent OK +description x->y OK +description y->absent OK +description y->x OK +edit_mode absent->x OK +edit_mode absent->y OK +edit_mode x->absent OK +edit_mode x->y OK +edit_mode y->absent OK +edit_mode y->x OK +email_notifications.no_alert_for_skipped_runs absent->false OK +email_notifications.no_alert_for_skipped_runs absent->true OK +email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false->true OK +email_notifications.no_alert_for_skipped_runs true->absent OK +email_notifications.no_alert_for_skipped_runs true->false OK +format MULTI_TASK->SINGLE_TASK OK +format MULTI_TASK->absent OK +format SINGLE_TASK->MULTI_TASK OK +format SINGLE_TASK->absent OK +format absent->MULTI_TASK OK +format absent->SINGLE_TASK OK +git_source.git_branch - SKIPPED needs a coherent git_source block +git_source.git_commit - SKIPPED needs a coherent git_source block +git_source.git_provider - SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block +git_source.git_tag - SKIPPED needs a coherent git_source block +git_source.git_url - SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +max_concurrent_runs 1->5 OK +max_concurrent_runs 1->absent OK +max_concurrent_runs 5->1 OK +max_concurrent_runs 5->absent OK +max_concurrent_runs absent->1 OK +max_concurrent_runs absent->5 OK +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +notification_settings.no_alert_for_canceled_runs absent->false OK +notification_settings.no_alert_for_canceled_runs absent->true OK +notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false->true OK +notification_settings.no_alert_for_canceled_runs true->absent OK +notification_settings.no_alert_for_canceled_runs true->false OK +notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent->true OK +notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false->true OK +notification_settings.no_alert_for_skipped_runs true->absent OK +notification_settings.no_alert_for_skipped_runs true->false OK +parent_path absent->x OK +parent_path absent->y OK +parent_path x->absent OK +parent_path x->y OK +parent_path y->absent OK +parent_path y->x OK +performance_target PERFORMANCE_OPTIMIZED->STANDARD OK +performance_target PERFORMANCE_OPTIMIZED->absent OK +performance_target STANDARD->PERFORMANCE_OPTIMIZED OK +performance_target STANDARD->absent OK +performance_target absent->PERFORMANCE_OPTIMIZED OK +performance_target absent->STANDARD OK +queue.enabled false->true OK +queue.enabled true->false OK +run_as.group_name absent->x OK +run_as.group_name absent->y OK +run_as.group_name x->absent OK +run_as.group_name x->y OK +run_as.group_name y->absent OK +run_as.group_name y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +schedule.pause_status absent->x OK +schedule.pause_status absent->y OK +schedule.pause_status x->absent OK +schedule.pause_status x->y OK +schedule.pause_status y->absent OK +schedule.pause_status y->x OK +schedule.quartz_cron_expression x->y OK +schedule.quartz_cron_expression y->x OK +schedule.sql_condition.sql_query_id x->y OK +schedule.sql_condition.sql_query_id y->x OK +schedule.sql_condition.trigger_mode absent->x OK +schedule.sql_condition.trigger_mode absent->y OK +schedule.sql_condition.trigger_mode x->absent OK +schedule.sql_condition.trigger_mode x->y OK +schedule.sql_condition.trigger_mode y->absent OK +schedule.sql_condition.trigger_mode y->x OK +schedule.sql_condition.warehouse_id x->y OK +schedule.sql_condition.warehouse_id y->x OK +schedule.timezone_id x->y OK +schedule.timezone_id y->x OK +timeout_seconds 1200->600 OK +timeout_seconds 1200->absent OK +timeout_seconds 600->1200 OK +timeout_seconds 600->absent OK +timeout_seconds absent->1200 OK +timeout_seconds absent->600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK +trigger.file_arrival.url x->y OK +trigger.file_arrival.url y->x OK +trigger.file_arrival.wait_after_last_change_seconds 1->2 OK +trigger.file_arrival.wait_after_last_change_seconds 1->absent OK +trigger.file_arrival.wait_after_last_change_seconds 2->1 OK +trigger.file_arrival.wait_after_last_change_seconds 2->absent OK +trigger.file_arrival.wait_after_last_change_seconds absent->1 OK +trigger.file_arrival.wait_after_last_change_seconds absent->2 OK +trigger.model.condition x->y OK +trigger.model.condition y->x OK +trigger.model.min_time_between_triggers_seconds 1->2 OK +trigger.model.min_time_between_triggers_seconds 1->absent OK +trigger.model.min_time_between_triggers_seconds 2->1 OK +trigger.model.min_time_between_triggers_seconds 2->absent OK +trigger.model.min_time_between_triggers_seconds absent->1 OK +trigger.model.min_time_between_triggers_seconds absent->2 OK +trigger.model.securable_name absent->x OK +trigger.model.securable_name absent->y OK +trigger.model.securable_name x->absent OK +trigger.model.securable_name x->y OK +trigger.model.securable_name y->absent OK +trigger.model.securable_name y->x OK +trigger.model.wait_after_last_change_seconds 1->2 OK +trigger.model.wait_after_last_change_seconds 1->absent OK +trigger.model.wait_after_last_change_seconds 2->1 OK +trigger.model.wait_after_last_change_seconds 2->absent OK +trigger.model.wait_after_last_change_seconds absent->1 OK +trigger.model.wait_after_last_change_seconds absent->2 OK +trigger.pause_status absent->x OK +trigger.pause_status absent->y OK +trigger.pause_status x->absent OK +trigger.pause_status x->y OK +trigger.pause_status y->absent OK +trigger.pause_status y->x OK +trigger.periodic.interval 1->2 OK +trigger.periodic.interval 2->1 OK +trigger.periodic.unit x->y OK +trigger.periodic.unit y->x OK +trigger.sql_condition.sql_query_id x->y OK +trigger.sql_condition.sql_query_id y->x OK +trigger.sql_condition.trigger_mode absent->x OK +trigger.sql_condition.trigger_mode absent->y OK +trigger.sql_condition.trigger_mode x->absent OK +trigger.sql_condition.trigger_mode x->y OK +trigger.sql_condition.trigger_mode y->absent OK +trigger.sql_condition.trigger_mode y->x OK +trigger.sql_condition.warehouse_id x->y OK +trigger.sql_condition.warehouse_id y->x OK +trigger.table_update.condition absent->x OK +trigger.table_update.condition absent->y OK +trigger.table_update.condition x->absent OK +trigger.table_update.condition x->y OK +trigger.table_update.condition y->absent OK +trigger.table_update.condition y->x OK +trigger.table_update.min_time_between_triggers_seconds 1->2 OK +trigger.table_update.min_time_between_triggers_seconds 1->absent OK +trigger.table_update.min_time_between_triggers_seconds 2->1 OK +trigger.table_update.min_time_between_triggers_seconds 2->absent OK +trigger.table_update.min_time_between_triggers_seconds absent->1 OK +trigger.table_update.min_time_between_triggers_seconds absent->2 OK +trigger.table_update.wait_after_last_change_seconds 1->2 OK +trigger.table_update.wait_after_last_change_seconds 1->absent OK +trigger.table_update.wait_after_last_change_seconds 2->1 OK +trigger.table_update.wait_after_last_change_seconds 2->absent OK +trigger.table_update.wait_after_last_change_seconds absent->1 OK +trigger.table_update.wait_after_last_change_seconds absent->2 OK +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent OK +usage_policy_id x->y OK +usage_policy_id y->absent OK +usage_policy_id y->x OK + +=== summary +OK 1272 +SUPPRESSED 48 +NOT_OBSERVABLE 48 +SKIPPED 54 + +=== not covered: 614 fields under a slice or map +email_notifications.on_duration_warning_threshold_exceeded[*] +email_notifications.on_failure[*] +email_notifications.on_start[*] +email_notifications.on_streaming_backlog_exceeded[*] +email_notifications.on_success[*] +environments[*].environment_key +environments[*].spec.base_environment +environments[*].spec.client +environments[*].spec.dependencies[*] +environments[*].spec.environment_version +environments[*].spec.java_dependencies[*] +git_source.sparse_checkout.patterns[*] +health.rules[*].metric +health.rules[*].op +health.rules[*].value +job_clusters[*].job_cluster_key +job_clusters[*].new_cluster.apply_policy_default_values +job_clusters[*].new_cluster.autoscale.max_workers +job_clusters[*].new_cluster.autoscale.min_workers +job_clusters[*].new_cluster.autotermination_minutes +job_clusters[*].new_cluster.aws_attributes.availability +job_clusters[*].new_cluster.aws_attributes.ebs_volume_count +job_clusters[*].new_cluster.aws_attributes.ebs_volume_iops +job_clusters[*].new_cluster.aws_attributes.ebs_volume_size +job_clusters[*].new_cluster.aws_attributes.ebs_volume_throughput +job_clusters[*].new_cluster.aws_attributes.ebs_volume_type +job_clusters[*].new_cluster.aws_attributes.first_on_demand +job_clusters[*].new_cluster.aws_attributes.instance_profile_arn +job_clusters[*].new_cluster.aws_attributes.spot_bid_price_percent +job_clusters[*].new_cluster.aws_attributes.zone_id +job_clusters[*].new_cluster.azure_attributes.availability +job_clusters[*].new_cluster.azure_attributes.capacity_reservation_group +job_clusters[*].new_cluster.azure_attributes.first_on_demand +job_clusters[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key +job_clusters[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id +job_clusters[*].new_cluster.azure_attributes.spot_bid_max_price +job_clusters[*].new_cluster.cluster_log_conf.dbfs.destination +job_clusters[*].new_cluster.cluster_log_conf.s3.canned_acl +job_clusters[*].new_cluster.cluster_log_conf.s3.destination +job_clusters[*].new_cluster.cluster_log_conf.s3.enable_encryption +job_clusters[*].new_cluster.cluster_log_conf.s3.encryption_type +job_clusters[*].new_cluster.cluster_log_conf.s3.endpoint +job_clusters[*].new_cluster.cluster_log_conf.s3.kms_key +job_clusters[*].new_cluster.cluster_log_conf.s3.region +job_clusters[*].new_cluster.cluster_log_conf.volumes.destination +job_clusters[*].new_cluster.cluster_name +job_clusters[*].new_cluster.custom_tags.* +job_clusters[*].new_cluster.data_security_mode +job_clusters[*].new_cluster.dependency_mode +job_clusters[*].new_cluster.docker_image.basic_auth.password +job_clusters[*].new_cluster.docker_image.basic_auth.username +job_clusters[*].new_cluster.docker_image.url +job_clusters[*].new_cluster.driver_instance_pool_id +job_clusters[*].new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] +job_clusters[*].new_cluster.driver_node_type_id +job_clusters[*].new_cluster.enable_elastic_disk +job_clusters[*].new_cluster.enable_local_disk_encryption +job_clusters[*].new_cluster.gcp_attributes.availability +job_clusters[*].new_cluster.gcp_attributes.boot_disk_size +job_clusters[*].new_cluster.gcp_attributes.confidential_compute_type +job_clusters[*].new_cluster.gcp_attributes.first_on_demand +job_clusters[*].new_cluster.gcp_attributes.google_service_account +job_clusters[*].new_cluster.gcp_attributes.local_ssd_count +job_clusters[*].new_cluster.gcp_attributes.use_preemptible_executors +job_clusters[*].new_cluster.gcp_attributes.zone_id +job_clusters[*].new_cluster.init_scripts[*].abfss.destination +job_clusters[*].new_cluster.init_scripts[*].dbfs.destination +job_clusters[*].new_cluster.init_scripts[*].file.destination +job_clusters[*].new_cluster.init_scripts[*].gcs.destination +job_clusters[*].new_cluster.init_scripts[*].s3.canned_acl +job_clusters[*].new_cluster.init_scripts[*].s3.destination +job_clusters[*].new_cluster.init_scripts[*].s3.enable_encryption +job_clusters[*].new_cluster.init_scripts[*].s3.encryption_type +job_clusters[*].new_cluster.init_scripts[*].s3.endpoint +job_clusters[*].new_cluster.init_scripts[*].s3.kms_key +job_clusters[*].new_cluster.init_scripts[*].s3.region +job_clusters[*].new_cluster.init_scripts[*].volumes.destination +job_clusters[*].new_cluster.init_scripts[*].workspace.destination +job_clusters[*].new_cluster.instance_pool_id +job_clusters[*].new_cluster.is_single_node +job_clusters[*].new_cluster.kind +job_clusters[*].new_cluster.node_type_id +job_clusters[*].new_cluster.num_workers +job_clusters[*].new_cluster.policy_id +job_clusters[*].new_cluster.remote_disk_throughput +job_clusters[*].new_cluster.runtime_engine +job_clusters[*].new_cluster.single_user_name +job_clusters[*].new_cluster.spark_conf.* +job_clusters[*].new_cluster.spark_env_vars.* +job_clusters[*].new_cluster.spark_version +job_clusters[*].new_cluster.ssh_public_keys[*] +job_clusters[*].new_cluster.total_initial_remote_disk_size +job_clusters[*].new_cluster.use_ml_runtime +job_clusters[*].new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] +job_clusters[*].new_cluster.workload_type.clients.jobs +job_clusters[*].new_cluster.workload_type.clients.notebooks +job_clusters[*].serverless_compute_id +parameters[*].default +parameters[*].name +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +tags.* +tasks[*].ai_runtime_task.code_source_path +tasks[*].ai_runtime_task.deployments[*].command_path +tasks[*].ai_runtime_task.deployments[*].compute.accelerator_count +tasks[*].ai_runtime_task.deployments[*].compute.accelerator_type +tasks[*].ai_runtime_task.deployments[*].name +tasks[*].ai_runtime_task.docker_image_url +tasks[*].ai_runtime_task.experiment +tasks[*].ai_runtime_task.mlflow_artifact_location +tasks[*].ai_runtime_task.mlflow_experiment_directory +tasks[*].ai_runtime_task.mlflow_run +tasks[*].alert_task.alert_id +tasks[*].alert_task.subscribers[*].destination_id +tasks[*].alert_task.subscribers[*].user_name +tasks[*].alert_task.warehouse_id +tasks[*].alert_task.workspace_path +tasks[*].clean_rooms_notebook_task.clean_room_name +tasks[*].clean_rooms_notebook_task.etag +tasks[*].clean_rooms_notebook_task.notebook_base_parameters.* +tasks[*].clean_rooms_notebook_task.notebook_name +tasks[*].compute.hardware_accelerator +tasks[*].condition_task.left +tasks[*].condition_task.op +tasks[*].condition_task.right +tasks[*].dashboard_task.dashboard_id +tasks[*].dashboard_task.filters.* +tasks[*].dashboard_task.subscription.custom_subject +tasks[*].dashboard_task.subscription.paused +tasks[*].dashboard_task.subscription.subscribers[*].destination_id +tasks[*].dashboard_task.subscription.subscribers[*].user_name +tasks[*].dashboard_task.warehouse_id +tasks[*].dbt_cloud_task.connection_resource_name +tasks[*].dbt_cloud_task.dbt_cloud_job_id +tasks[*].dbt_platform_task.connection_resource_name +tasks[*].dbt_platform_task.dbt_platform_job_id +tasks[*].dbt_task.catalog +tasks[*].dbt_task.commands[*] +tasks[*].dbt_task.profiles_directory +tasks[*].dbt_task.project_directory +tasks[*].dbt_task.schema +tasks[*].dbt_task.source +tasks[*].dbt_task.warehouse_id +tasks[*].depends_on[*].outcome +tasks[*].depends_on[*].task_key +tasks[*].description +tasks[*].disable_auto_optimization +tasks[*].disabled +tasks[*].email_notifications.no_alert_for_skipped_runs +tasks[*].email_notifications.on_duration_warning_threshold_exceeded[*] +tasks[*].email_notifications.on_failure[*] +tasks[*].email_notifications.on_start[*] +tasks[*].email_notifications.on_streaming_backlog_exceeded[*] +tasks[*].email_notifications.on_success[*] +tasks[*].environment_key +tasks[*].existing_cluster_id +tasks[*].for_each_task.concurrency +tasks[*].for_each_task.inputs +tasks[*].for_each_task.task.ai_runtime_task.code_source_path +tasks[*].for_each_task.task.ai_runtime_task.deployments[*].command_path +tasks[*].for_each_task.task.ai_runtime_task.deployments[*].compute.accelerator_count +tasks[*].for_each_task.task.ai_runtime_task.deployments[*].compute.accelerator_type +tasks[*].for_each_task.task.ai_runtime_task.deployments[*].name +tasks[*].for_each_task.task.ai_runtime_task.docker_image_url +tasks[*].for_each_task.task.ai_runtime_task.experiment +tasks[*].for_each_task.task.ai_runtime_task.mlflow_artifact_location +tasks[*].for_each_task.task.ai_runtime_task.mlflow_experiment_directory +tasks[*].for_each_task.task.ai_runtime_task.mlflow_run +tasks[*].for_each_task.task.alert_task.alert_id +tasks[*].for_each_task.task.alert_task.subscribers[*].destination_id +tasks[*].for_each_task.task.alert_task.subscribers[*].user_name +tasks[*].for_each_task.task.alert_task.warehouse_id +tasks[*].for_each_task.task.alert_task.workspace_path +tasks[*].for_each_task.task.clean_rooms_notebook_task.clean_room_name +tasks[*].for_each_task.task.clean_rooms_notebook_task.etag +tasks[*].for_each_task.task.clean_rooms_notebook_task.notebook_base_parameters.* +tasks[*].for_each_task.task.clean_rooms_notebook_task.notebook_name +tasks[*].for_each_task.task.compute.hardware_accelerator +tasks[*].for_each_task.task.condition_task.left +tasks[*].for_each_task.task.condition_task.op +tasks[*].for_each_task.task.condition_task.right +tasks[*].for_each_task.task.dashboard_task.dashboard_id +tasks[*].for_each_task.task.dashboard_task.filters.* +tasks[*].for_each_task.task.dashboard_task.subscription.custom_subject +tasks[*].for_each_task.task.dashboard_task.subscription.paused +tasks[*].for_each_task.task.dashboard_task.subscription.subscribers[*].destination_id +tasks[*].for_each_task.task.dashboard_task.subscription.subscribers[*].user_name +tasks[*].for_each_task.task.dashboard_task.warehouse_id +tasks[*].for_each_task.task.dbt_cloud_task.connection_resource_name +tasks[*].for_each_task.task.dbt_cloud_task.dbt_cloud_job_id +tasks[*].for_each_task.task.dbt_platform_task.connection_resource_name +tasks[*].for_each_task.task.dbt_platform_task.dbt_platform_job_id +tasks[*].for_each_task.task.dbt_task.catalog +tasks[*].for_each_task.task.dbt_task.commands[*] +tasks[*].for_each_task.task.dbt_task.profiles_directory +tasks[*].for_each_task.task.dbt_task.project_directory +tasks[*].for_each_task.task.dbt_task.schema +tasks[*].for_each_task.task.dbt_task.source +tasks[*].for_each_task.task.dbt_task.warehouse_id +tasks[*].for_each_task.task.depends_on[*].outcome +tasks[*].for_each_task.task.depends_on[*].task_key +tasks[*].for_each_task.task.description +tasks[*].for_each_task.task.disable_auto_optimization +tasks[*].for_each_task.task.disabled +tasks[*].for_each_task.task.email_notifications.no_alert_for_skipped_runs +tasks[*].for_each_task.task.email_notifications.on_duration_warning_threshold_exceeded[*] +tasks[*].for_each_task.task.email_notifications.on_failure[*] +tasks[*].for_each_task.task.email_notifications.on_start[*] +tasks[*].for_each_task.task.email_notifications.on_streaming_backlog_exceeded[*] +tasks[*].for_each_task.task.email_notifications.on_success[*] +tasks[*].for_each_task.task.environment_key +tasks[*].for_each_task.task.existing_cluster_id +tasks[*].for_each_task.task.for_each_task.concurrency +tasks[*].for_each_task.task.for_each_task.inputs +tasks[*].for_each_task.task.gen_ai_compute_task.command +tasks[*].for_each_task.task.gen_ai_compute_task.compute.gpu_node_pool_id +tasks[*].for_each_task.task.gen_ai_compute_task.compute.gpu_type +tasks[*].for_each_task.task.gen_ai_compute_task.compute.num_gpus +tasks[*].for_each_task.task.gen_ai_compute_task.dl_runtime_image +tasks[*].for_each_task.task.gen_ai_compute_task.mlflow_experiment_name +tasks[*].for_each_task.task.gen_ai_compute_task.source +tasks[*].for_each_task.task.gen_ai_compute_task.training_script_path +tasks[*].for_each_task.task.gen_ai_compute_task.yaml_parameters +tasks[*].for_each_task.task.gen_ai_compute_task.yaml_parameters_file_path +tasks[*].for_each_task.task.health.rules[*].metric +tasks[*].for_each_task.task.health.rules[*].op +tasks[*].for_each_task.task.health.rules[*].value +tasks[*].for_each_task.task.job_cluster_key +tasks[*].for_each_task.task.libraries[*].cran.package +tasks[*].for_each_task.task.libraries[*].cran.repo +tasks[*].for_each_task.task.libraries[*].egg +tasks[*].for_each_task.task.libraries[*].jar +tasks[*].for_each_task.task.libraries[*].maven.coordinates +tasks[*].for_each_task.task.libraries[*].maven.exclusions[*] +tasks[*].for_each_task.task.libraries[*].maven.repo +tasks[*].for_each_task.task.libraries[*].pypi.package +tasks[*].for_each_task.task.libraries[*].pypi.repo +tasks[*].for_each_task.task.libraries[*].requirements +tasks[*].for_each_task.task.libraries[*].whl +tasks[*].for_each_task.task.max_retries +tasks[*].for_each_task.task.min_retry_interval_millis +tasks[*].for_each_task.task.new_cluster.apply_policy_default_values +tasks[*].for_each_task.task.new_cluster.autoscale.max_workers +tasks[*].for_each_task.task.new_cluster.autoscale.min_workers +tasks[*].for_each_task.task.new_cluster.autotermination_minutes +tasks[*].for_each_task.task.new_cluster.aws_attributes.availability +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_count +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_iops +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_size +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_throughput +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_type +tasks[*].for_each_task.task.new_cluster.aws_attributes.first_on_demand +tasks[*].for_each_task.task.new_cluster.aws_attributes.instance_profile_arn +tasks[*].for_each_task.task.new_cluster.aws_attributes.spot_bid_price_percent +tasks[*].for_each_task.task.new_cluster.aws_attributes.zone_id +tasks[*].for_each_task.task.new_cluster.azure_attributes.availability +tasks[*].for_each_task.task.new_cluster.azure_attributes.capacity_reservation_group +tasks[*].for_each_task.task.new_cluster.azure_attributes.first_on_demand +tasks[*].for_each_task.task.new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key +tasks[*].for_each_task.task.new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id +tasks[*].for_each_task.task.new_cluster.azure_attributes.spot_bid_max_price +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.dbfs.destination +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.canned_acl +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.destination +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.enable_encryption +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.encryption_type +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.endpoint +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.kms_key +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.region +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.volumes.destination +tasks[*].for_each_task.task.new_cluster.cluster_name +tasks[*].for_each_task.task.new_cluster.custom_tags.* +tasks[*].for_each_task.task.new_cluster.data_security_mode +tasks[*].for_each_task.task.new_cluster.dependency_mode +tasks[*].for_each_task.task.new_cluster.docker_image.basic_auth.password +tasks[*].for_each_task.task.new_cluster.docker_image.basic_auth.username +tasks[*].for_each_task.task.new_cluster.docker_image.url +tasks[*].for_each_task.task.new_cluster.driver_instance_pool_id +tasks[*].for_each_task.task.new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] +tasks[*].for_each_task.task.new_cluster.driver_node_type_id +tasks[*].for_each_task.task.new_cluster.enable_elastic_disk +tasks[*].for_each_task.task.new_cluster.enable_local_disk_encryption +tasks[*].for_each_task.task.new_cluster.gcp_attributes.availability +tasks[*].for_each_task.task.new_cluster.gcp_attributes.boot_disk_size +tasks[*].for_each_task.task.new_cluster.gcp_attributes.confidential_compute_type +tasks[*].for_each_task.task.new_cluster.gcp_attributes.first_on_demand +tasks[*].for_each_task.task.new_cluster.gcp_attributes.google_service_account +tasks[*].for_each_task.task.new_cluster.gcp_attributes.local_ssd_count +tasks[*].for_each_task.task.new_cluster.gcp_attributes.use_preemptible_executors +tasks[*].for_each_task.task.new_cluster.gcp_attributes.zone_id +tasks[*].for_each_task.task.new_cluster.init_scripts[*].abfss.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].dbfs.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].file.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].gcs.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.canned_acl +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.enable_encryption +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.encryption_type +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.endpoint +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.kms_key +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.region +tasks[*].for_each_task.task.new_cluster.init_scripts[*].volumes.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].workspace.destination +tasks[*].for_each_task.task.new_cluster.instance_pool_id +tasks[*].for_each_task.task.new_cluster.is_single_node +tasks[*].for_each_task.task.new_cluster.kind +tasks[*].for_each_task.task.new_cluster.node_type_id +tasks[*].for_each_task.task.new_cluster.num_workers +tasks[*].for_each_task.task.new_cluster.policy_id +tasks[*].for_each_task.task.new_cluster.remote_disk_throughput +tasks[*].for_each_task.task.new_cluster.runtime_engine +tasks[*].for_each_task.task.new_cluster.single_user_name +tasks[*].for_each_task.task.new_cluster.spark_conf.* +tasks[*].for_each_task.task.new_cluster.spark_env_vars.* +tasks[*].for_each_task.task.new_cluster.spark_version +tasks[*].for_each_task.task.new_cluster.ssh_public_keys[*] +tasks[*].for_each_task.task.new_cluster.total_initial_remote_disk_size +tasks[*].for_each_task.task.new_cluster.use_ml_runtime +tasks[*].for_each_task.task.new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] +tasks[*].for_each_task.task.new_cluster.workload_type.clients.jobs +tasks[*].for_each_task.task.new_cluster.workload_type.clients.notebooks +tasks[*].for_each_task.task.notebook_task.base_parameters.* +tasks[*].for_each_task.task.notebook_task.notebook_path +tasks[*].for_each_task.task.notebook_task.source +tasks[*].for_each_task.task.notebook_task.warehouse_id +tasks[*].for_each_task.task.notification_settings.alert_on_last_attempt +tasks[*].for_each_task.task.notification_settings.no_alert_for_canceled_runs +tasks[*].for_each_task.task.notification_settings.no_alert_for_skipped_runs +tasks[*].for_each_task.task.pipeline_task.full_refresh +tasks[*].for_each_task.task.pipeline_task.full_refresh_selection[*] +tasks[*].for_each_task.task.pipeline_task.parameters.* +tasks[*].for_each_task.task.pipeline_task.pipeline_id +tasks[*].for_each_task.task.pipeline_task.refresh_flow_selection[*] +tasks[*].for_each_task.task.pipeline_task.refresh_selection[*] +tasks[*].for_each_task.task.pipeline_task.reset_checkpoint_selection[*] +tasks[*].for_each_task.task.power_bi_task.connection_resource_name +tasks[*].for_each_task.task.power_bi_task.power_bi_model.authentication_method +tasks[*].for_each_task.task.power_bi_task.power_bi_model.model_name +tasks[*].for_each_task.task.power_bi_task.power_bi_model.overwrite_existing +tasks[*].for_each_task.task.power_bi_task.power_bi_model.storage_mode +tasks[*].for_each_task.task.power_bi_task.power_bi_model.workspace_name +tasks[*].for_each_task.task.power_bi_task.refresh_after_update +tasks[*].for_each_task.task.power_bi_task.tables[*].catalog +tasks[*].for_each_task.task.power_bi_task.tables[*].name +tasks[*].for_each_task.task.power_bi_task.tables[*].schema +tasks[*].for_each_task.task.power_bi_task.tables[*].storage_mode +tasks[*].for_each_task.task.power_bi_task.warehouse_id +tasks[*].for_each_task.task.python_operator_task.main +tasks[*].for_each_task.task.python_operator_task.parameters[*].name +tasks[*].for_each_task.task.python_operator_task.parameters[*].value +tasks[*].for_each_task.task.python_wheel_task.entry_point +tasks[*].for_each_task.task.python_wheel_task.named_parameters.* +tasks[*].for_each_task.task.python_wheel_task.package_name +tasks[*].for_each_task.task.python_wheel_task.parameters[*] +tasks[*].for_each_task.task.retry_on_timeout +tasks[*].for_each_task.task.run_if +tasks[*].for_each_task.task.run_job_task.dbt_commands[*] +tasks[*].for_each_task.task.run_job_task.jar_params[*] +tasks[*].for_each_task.task.run_job_task.job_id +tasks[*].for_each_task.task.run_job_task.job_parameters.* +tasks[*].for_each_task.task.run_job_task.notebook_params.* +tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh +tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh_selection[*] +tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_flow_selection[*] +tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_selection[*] +tasks[*].for_each_task.task.run_job_task.pipeline_params.reset_checkpoint_selection[*] +tasks[*].for_each_task.task.run_job_task.python_named_params.* +tasks[*].for_each_task.task.run_job_task.python_params[*] +tasks[*].for_each_task.task.run_job_task.spark_submit_params[*] +tasks[*].for_each_task.task.run_job_task.sql_params.* +tasks[*].for_each_task.task.spark_jar_task.jar_uri +tasks[*].for_each_task.task.spark_jar_task.main_class_name +tasks[*].for_each_task.task.spark_jar_task.parameters[*] +tasks[*].for_each_task.task.spark_jar_task.run_as_repl +tasks[*].for_each_task.task.spark_python_task.parameters[*] +tasks[*].for_each_task.task.spark_python_task.python_file +tasks[*].for_each_task.task.spark_python_task.source +tasks[*].for_each_task.task.spark_submit_task.parameters[*] +tasks[*].for_each_task.task.sql_task.alert.alert_id +tasks[*].for_each_task.task.sql_task.alert.pause_subscriptions +tasks[*].for_each_task.task.sql_task.alert.subscriptions[*].destination_id +tasks[*].for_each_task.task.sql_task.alert.subscriptions[*].user_name +tasks[*].for_each_task.task.sql_task.dashboard.custom_subject +tasks[*].for_each_task.task.sql_task.dashboard.dashboard_id +tasks[*].for_each_task.task.sql_task.dashboard.pause_subscriptions +tasks[*].for_each_task.task.sql_task.dashboard.subscriptions[*].destination_id +tasks[*].for_each_task.task.sql_task.dashboard.subscriptions[*].user_name +tasks[*].for_each_task.task.sql_task.file.path +tasks[*].for_each_task.task.sql_task.file.source +tasks[*].for_each_task.task.sql_task.parameters.* +tasks[*].for_each_task.task.sql_task.query.query_id +tasks[*].for_each_task.task.sql_task.warehouse_id +tasks[*].for_each_task.task.task_key +tasks[*].for_each_task.task.timeout_seconds +tasks[*].for_each_task.task.webhook_notifications.on_duration_warning_threshold_exceeded[*].id +tasks[*].for_each_task.task.webhook_notifications.on_failure[*].id +tasks[*].for_each_task.task.webhook_notifications.on_start[*].id +tasks[*].for_each_task.task.webhook_notifications.on_streaming_backlog_exceeded[*].id +tasks[*].for_each_task.task.webhook_notifications.on_success[*].id +tasks[*].gen_ai_compute_task.command +tasks[*].gen_ai_compute_task.compute.gpu_node_pool_id +tasks[*].gen_ai_compute_task.compute.gpu_type +tasks[*].gen_ai_compute_task.compute.num_gpus +tasks[*].gen_ai_compute_task.dl_runtime_image +tasks[*].gen_ai_compute_task.mlflow_experiment_name +tasks[*].gen_ai_compute_task.source +tasks[*].gen_ai_compute_task.training_script_path +tasks[*].gen_ai_compute_task.yaml_parameters +tasks[*].gen_ai_compute_task.yaml_parameters_file_path +tasks[*].health.rules[*].metric +tasks[*].health.rules[*].op +tasks[*].health.rules[*].value +tasks[*].job_cluster_key +tasks[*].libraries[*].cran.package +tasks[*].libraries[*].cran.repo +tasks[*].libraries[*].egg +tasks[*].libraries[*].jar +tasks[*].libraries[*].maven.coordinates +tasks[*].libraries[*].maven.exclusions[*] +tasks[*].libraries[*].maven.repo +tasks[*].libraries[*].pypi.package +tasks[*].libraries[*].pypi.repo +tasks[*].libraries[*].requirements +tasks[*].libraries[*].whl +tasks[*].max_retries +tasks[*].min_retry_interval_millis +tasks[*].new_cluster.apply_policy_default_values +tasks[*].new_cluster.autoscale.max_workers +tasks[*].new_cluster.autoscale.min_workers +tasks[*].new_cluster.autotermination_minutes +tasks[*].new_cluster.aws_attributes.availability +tasks[*].new_cluster.aws_attributes.ebs_volume_count +tasks[*].new_cluster.aws_attributes.ebs_volume_iops +tasks[*].new_cluster.aws_attributes.ebs_volume_size +tasks[*].new_cluster.aws_attributes.ebs_volume_throughput +tasks[*].new_cluster.aws_attributes.ebs_volume_type +tasks[*].new_cluster.aws_attributes.first_on_demand +tasks[*].new_cluster.aws_attributes.instance_profile_arn +tasks[*].new_cluster.aws_attributes.spot_bid_price_percent +tasks[*].new_cluster.aws_attributes.zone_id +tasks[*].new_cluster.azure_attributes.availability +tasks[*].new_cluster.azure_attributes.capacity_reservation_group +tasks[*].new_cluster.azure_attributes.first_on_demand +tasks[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key +tasks[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id +tasks[*].new_cluster.azure_attributes.spot_bid_max_price +tasks[*].new_cluster.cluster_log_conf.dbfs.destination +tasks[*].new_cluster.cluster_log_conf.s3.canned_acl +tasks[*].new_cluster.cluster_log_conf.s3.destination +tasks[*].new_cluster.cluster_log_conf.s3.enable_encryption +tasks[*].new_cluster.cluster_log_conf.s3.encryption_type +tasks[*].new_cluster.cluster_log_conf.s3.endpoint +tasks[*].new_cluster.cluster_log_conf.s3.kms_key +tasks[*].new_cluster.cluster_log_conf.s3.region +tasks[*].new_cluster.cluster_log_conf.volumes.destination +tasks[*].new_cluster.cluster_name +tasks[*].new_cluster.custom_tags.* +tasks[*].new_cluster.data_security_mode +tasks[*].new_cluster.dependency_mode +tasks[*].new_cluster.docker_image.basic_auth.password +tasks[*].new_cluster.docker_image.basic_auth.username +tasks[*].new_cluster.docker_image.url +tasks[*].new_cluster.driver_instance_pool_id +tasks[*].new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] +tasks[*].new_cluster.driver_node_type_id +tasks[*].new_cluster.enable_elastic_disk +tasks[*].new_cluster.enable_local_disk_encryption +tasks[*].new_cluster.gcp_attributes.availability +tasks[*].new_cluster.gcp_attributes.boot_disk_size +tasks[*].new_cluster.gcp_attributes.confidential_compute_type +tasks[*].new_cluster.gcp_attributes.first_on_demand +tasks[*].new_cluster.gcp_attributes.google_service_account +tasks[*].new_cluster.gcp_attributes.local_ssd_count +tasks[*].new_cluster.gcp_attributes.use_preemptible_executors +tasks[*].new_cluster.gcp_attributes.zone_id +tasks[*].new_cluster.init_scripts[*].abfss.destination +tasks[*].new_cluster.init_scripts[*].dbfs.destination +tasks[*].new_cluster.init_scripts[*].file.destination +tasks[*].new_cluster.init_scripts[*].gcs.destination +tasks[*].new_cluster.init_scripts[*].s3.canned_acl +tasks[*].new_cluster.init_scripts[*].s3.destination +tasks[*].new_cluster.init_scripts[*].s3.enable_encryption +tasks[*].new_cluster.init_scripts[*].s3.encryption_type +tasks[*].new_cluster.init_scripts[*].s3.endpoint +tasks[*].new_cluster.init_scripts[*].s3.kms_key +tasks[*].new_cluster.init_scripts[*].s3.region +tasks[*].new_cluster.init_scripts[*].volumes.destination +tasks[*].new_cluster.init_scripts[*].workspace.destination +tasks[*].new_cluster.instance_pool_id +tasks[*].new_cluster.is_single_node +tasks[*].new_cluster.kind +tasks[*].new_cluster.node_type_id +tasks[*].new_cluster.num_workers +tasks[*].new_cluster.policy_id +tasks[*].new_cluster.remote_disk_throughput +tasks[*].new_cluster.runtime_engine +tasks[*].new_cluster.single_user_name +tasks[*].new_cluster.spark_conf.* +tasks[*].new_cluster.spark_env_vars.* +tasks[*].new_cluster.spark_version +tasks[*].new_cluster.ssh_public_keys[*] +tasks[*].new_cluster.total_initial_remote_disk_size +tasks[*].new_cluster.use_ml_runtime +tasks[*].new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] +tasks[*].new_cluster.workload_type.clients.jobs +tasks[*].new_cluster.workload_type.clients.notebooks +tasks[*].notebook_task.base_parameters.* +tasks[*].notebook_task.notebook_path +tasks[*].notebook_task.source +tasks[*].notebook_task.warehouse_id +tasks[*].notification_settings.alert_on_last_attempt +tasks[*].notification_settings.no_alert_for_canceled_runs +tasks[*].notification_settings.no_alert_for_skipped_runs +tasks[*].pipeline_task.full_refresh +tasks[*].pipeline_task.full_refresh_selection[*] +tasks[*].pipeline_task.parameters.* +tasks[*].pipeline_task.pipeline_id +tasks[*].pipeline_task.refresh_flow_selection[*] +tasks[*].pipeline_task.refresh_selection[*] +tasks[*].pipeline_task.reset_checkpoint_selection[*] +tasks[*].power_bi_task.connection_resource_name +tasks[*].power_bi_task.power_bi_model.authentication_method +tasks[*].power_bi_task.power_bi_model.model_name +tasks[*].power_bi_task.power_bi_model.overwrite_existing +tasks[*].power_bi_task.power_bi_model.storage_mode +tasks[*].power_bi_task.power_bi_model.workspace_name +tasks[*].power_bi_task.refresh_after_update +tasks[*].power_bi_task.tables[*].catalog +tasks[*].power_bi_task.tables[*].name +tasks[*].power_bi_task.tables[*].schema +tasks[*].power_bi_task.tables[*].storage_mode +tasks[*].power_bi_task.warehouse_id +tasks[*].python_operator_task.main +tasks[*].python_operator_task.parameters[*].name +tasks[*].python_operator_task.parameters[*].value +tasks[*].python_wheel_task.entry_point +tasks[*].python_wheel_task.named_parameters.* +tasks[*].python_wheel_task.package_name +tasks[*].python_wheel_task.parameters[*] +tasks[*].retry_on_timeout +tasks[*].run_if +tasks[*].run_job_task.dbt_commands[*] +tasks[*].run_job_task.jar_params[*] +tasks[*].run_job_task.job_id +tasks[*].run_job_task.job_parameters.* +tasks[*].run_job_task.notebook_params.* +tasks[*].run_job_task.pipeline_params.full_refresh +tasks[*].run_job_task.pipeline_params.full_refresh_selection[*] +tasks[*].run_job_task.pipeline_params.refresh_flow_selection[*] +tasks[*].run_job_task.pipeline_params.refresh_selection[*] +tasks[*].run_job_task.pipeline_params.reset_checkpoint_selection[*] +tasks[*].run_job_task.python_named_params.* +tasks[*].run_job_task.python_params[*] +tasks[*].run_job_task.spark_submit_params[*] +tasks[*].run_job_task.sql_params.* +tasks[*].spark_jar_task.jar_uri +tasks[*].spark_jar_task.main_class_name +tasks[*].spark_jar_task.parameters[*] +tasks[*].spark_jar_task.run_as_repl +tasks[*].spark_python_task.parameters[*] +tasks[*].spark_python_task.python_file +tasks[*].spark_python_task.source +tasks[*].spark_submit_task.parameters[*] +tasks[*].sql_task.alert.alert_id +tasks[*].sql_task.alert.pause_subscriptions +tasks[*].sql_task.alert.subscriptions[*].destination_id +tasks[*].sql_task.alert.subscriptions[*].user_name +tasks[*].sql_task.dashboard.custom_subject +tasks[*].sql_task.dashboard.dashboard_id +tasks[*].sql_task.dashboard.pause_subscriptions +tasks[*].sql_task.dashboard.subscriptions[*].destination_id +tasks[*].sql_task.dashboard.subscriptions[*].user_name +tasks[*].sql_task.file.path +tasks[*].sql_task.file.source +tasks[*].sql_task.parameters.* +tasks[*].sql_task.query.query_id +tasks[*].sql_task.warehouse_id +tasks[*].task_key +tasks[*].timeout_seconds +tasks[*].webhook_notifications.on_duration_warning_threshold_exceeded[*].id +tasks[*].webhook_notifications.on_failure[*].id +tasks[*].webhook_notifications.on_start[*].id +tasks[*].webhook_notifications.on_streaming_backlog_exceeded[*].id +tasks[*].webhook_notifications.on_success[*].id +trigger.model.aliases[*] +trigger.table_update.table_names[*] +triggers[*].continuous.task_retry_mode +triggers[*].file_arrival.min_time_between_triggers_seconds +triggers[*].file_arrival.url +triggers[*].file_arrival.wait_after_last_change_seconds +triggers[*].model.aliases[*] +triggers[*].model.condition +triggers[*].model.min_time_between_triggers_seconds +triggers[*].model.securable_name +triggers[*].model.wait_after_last_change_seconds +triggers[*].pause_status +triggers[*].periodic.interval +triggers[*].periodic.unit +triggers[*].schedule.quartz_cron_expression +triggers[*].schedule.timezone_id +triggers[*].sql_condition.sql_query_id +triggers[*].sql_condition.trigger_mode +triggers[*].sql_condition.warehouse_id +triggers[*].table_update.condition +triggers[*].table_update.min_time_between_triggers_seconds +triggers[*].table_update.table_names[*] +triggers[*].table_update.wait_after_last_change_seconds +webhook_notifications.on_duration_warning_threshold_exceeded[*].id +webhook_notifications.on_failure[*].id +webhook_notifications.on_start[*].id +webhook_notifications.on_streaming_backlog_exceeded[*].id +webhook_notifications.on_success[*].id diff --git a/bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt b/bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt new file mode 100644 index 00000000000..42456e6d3e5 --- /dev/null +++ b/bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt @@ -0,0 +1,396 @@ +=== model_serving_endpoint.yml.tmpl +ai_gateway.fallback_config.enabled false->true OK +ai_gateway.fallback_config.enabled true->false OK +ai_gateway.guardrails.input.pii.behavior absent->x OK +ai_gateway.guardrails.input.pii.behavior absent->y OK +ai_gateway.guardrails.input.pii.behavior x->absent OK +ai_gateway.guardrails.input.pii.behavior x->y OK +ai_gateway.guardrails.input.pii.behavior y->absent OK +ai_gateway.guardrails.input.pii.behavior y->x OK +ai_gateway.guardrails.input.safety absent->false SUPPRESSED empty +ai_gateway.guardrails.input.safety absent->true OK +ai_gateway.guardrails.input.safety false->absent SUPPRESSED empty +ai_gateway.guardrails.input.safety false->true OK +ai_gateway.guardrails.input.safety true->absent OK +ai_gateway.guardrails.input.safety true->false OK +ai_gateway.guardrails.output.pii.behavior absent->x OK +ai_gateway.guardrails.output.pii.behavior absent->y OK +ai_gateway.guardrails.output.pii.behavior x->absent OK +ai_gateway.guardrails.output.pii.behavior x->y OK +ai_gateway.guardrails.output.pii.behavior y->absent OK +ai_gateway.guardrails.output.pii.behavior y->x OK +ai_gateway.guardrails.output.safety absent->false SUPPRESSED empty +ai_gateway.guardrails.output.safety absent->true OK +ai_gateway.guardrails.output.safety false->absent SUPPRESSED empty +ai_gateway.guardrails.output.safety false->true OK +ai_gateway.guardrails.output.safety true->absent OK +ai_gateway.guardrails.output.safety true->false OK +ai_gateway.inference_table_config.catalog_name absent->x OK +ai_gateway.inference_table_config.catalog_name absent->y OK +ai_gateway.inference_table_config.catalog_name x->absent OK +ai_gateway.inference_table_config.catalog_name x->y OK +ai_gateway.inference_table_config.catalog_name y->absent OK +ai_gateway.inference_table_config.catalog_name y->x OK +ai_gateway.inference_table_config.enabled absent->false SUPPRESSED empty +ai_gateway.inference_table_config.enabled absent->true OK +ai_gateway.inference_table_config.enabled false->absent SUPPRESSED empty +ai_gateway.inference_table_config.enabled false->true OK +ai_gateway.inference_table_config.enabled true->absent OK +ai_gateway.inference_table_config.enabled true->false OK +ai_gateway.inference_table_config.schema_name absent->x OK +ai_gateway.inference_table_config.schema_name absent->y OK +ai_gateway.inference_table_config.schema_name x->absent OK +ai_gateway.inference_table_config.schema_name x->y OK +ai_gateway.inference_table_config.schema_name y->absent OK +ai_gateway.inference_table_config.schema_name y->x OK +ai_gateway.inference_table_config.table_name_prefix absent->x OK +ai_gateway.inference_table_config.table_name_prefix absent->y OK +ai_gateway.inference_table_config.table_name_prefix x->absent OK +ai_gateway.inference_table_config.table_name_prefix x->y OK +ai_gateway.inference_table_config.table_name_prefix y->absent OK +ai_gateway.inference_table_config.table_name_prefix y->x OK +ai_gateway.usage_tracking_config.enabled absent->false OK +ai_gateway.usage_tracking_config.enabled absent->true OK +ai_gateway.usage_tracking_config.enabled false->absent SUPPRESSED empty +ai_gateway.usage_tracking_config.enabled false->true OK +ai_gateway.usage_tracking_config.enabled true->absent OK +ai_gateway.usage_tracking_config.enabled true->false OK +budget_policy_id absent->x SUPPRESSED no_update_api +budget_policy_id absent->y SUPPRESSED no_update_api +budget_policy_id x->absent NOT_OBSERVABLE +budget_policy_id x->y SUPPRESSED no_update_api +budget_policy_id y->absent NOT_OBSERVABLE +budget_policy_id y->x SUPPRESSED no_update_api +config.auto_capture_config.catalog_name absent->x DRIFT config +config.auto_capture_config.catalog_name absent->y OK_RECREATE +config.auto_capture_config.catalog_name x->absent OK_RECREATE +config.auto_capture_config.catalog_name x->y OK_RECREATE +config.auto_capture_config.catalog_name y->absent OK_RECREATE +config.auto_capture_config.catalog_name y->x OK_RECREATE +config.auto_capture_config.enabled absent->false SUPPRESSED empty +config.auto_capture_config.enabled absent->true DRIFT config +config.auto_capture_config.enabled false->absent DRIFT config +config.auto_capture_config.enabled false->true DRIFT config +config.auto_capture_config.enabled true->absent DRIFT config +config.auto_capture_config.enabled true->false DRIFT config +config.auto_capture_config.schema_name absent->x DRIFT config +config.auto_capture_config.schema_name absent->y OK_RECREATE +config.auto_capture_config.schema_name x->absent OK_RECREATE +config.auto_capture_config.schema_name x->y OK_RECREATE +config.auto_capture_config.schema_name y->absent OK_RECREATE +config.auto_capture_config.schema_name y->x OK_RECREATE +config.auto_capture_config.table_name_prefix absent->x OK_RECREATE +config.auto_capture_config.table_name_prefix absent->y OK_RECREATE +config.auto_capture_config.table_name_prefix x->absent OK_RECREATE +config.auto_capture_config.table_name_prefix x->y OK_RECREATE +config.auto_capture_config.table_name_prefix y->absent OK_RECREATE +config.auto_capture_config.table_name_prefix y->x OK_RECREATE +description absent->x OK_RECREATE +description absent->y OK_RECREATE +description x->absent OK_RECREATE +description x->y OK_RECREATE +description y->absent OK_RECREATE +description y->x OK_RECREATE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK_RECREATE +name y->x OK_RECREATE +route_optimized absent->false SUPPRESSED remote_already_set +route_optimized absent->true OK_RECREATE +route_optimized false->absent SUPPRESSED empty +route_optimized false->true OK_RECREATE +route_optimized true->absent OK_RECREATE +route_optimized true->false OK_RECREATE +telemetry_config.inference_table_config.name - SKIPPED needs a writable catalog and schema +telemetry_config.inference_table_config.sampling_fraction 1->2 BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.inference_table_config.sampling_fraction 1->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.inference_table_config.sampling_fraction 2->1 BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.inference_table_config.sampling_fraction 2->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.inference_table_config.sampling_fraction absent->1 BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... +telemetry_config.inference_table_config.sampling_fraction absent->2 BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.annotations_table absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... +telemetry_config.table_names.annotations_table absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.annotations_table x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.annotations_table x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.annotations_table y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.annotations_table y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.logs_table absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... +telemetry_config.table_names.logs_table absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.logs_table x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.logs_table x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.logs_table y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.logs_table y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.metrics_table absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... +telemetry_config.table_names.metrics_table absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.metrics_table x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.metrics_table x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.metrics_table y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.metrics_table y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.traces_table absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... +telemetry_config.table_names.traces_table absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.traces_table x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.traces_table x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.traces_table y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.table_names.traces_table y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.telemetry_profile_id absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... +telemetry_config.telemetry_profile_id absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.telemetry_profile_id x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.telemetry_profile_id x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.telemetry_profile_id y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.telemetry_profile_id y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled + +=== model_serving_endpoint_telemetry.yml.tmpl +ai_gateway.fallback_config.enabled false->true OK +ai_gateway.fallback_config.enabled true->false OK +ai_gateway.guardrails.input.pii.behavior absent->x OK +ai_gateway.guardrails.input.pii.behavior absent->y OK +ai_gateway.guardrails.input.pii.behavior x->absent OK +ai_gateway.guardrails.input.pii.behavior x->y OK +ai_gateway.guardrails.input.pii.behavior y->absent OK +ai_gateway.guardrails.input.pii.behavior y->x OK +ai_gateway.guardrails.input.safety absent->false SUPPRESSED empty +ai_gateway.guardrails.input.safety absent->true OK +ai_gateway.guardrails.input.safety false->absent SUPPRESSED empty +ai_gateway.guardrails.input.safety false->true OK +ai_gateway.guardrails.input.safety true->absent OK +ai_gateway.guardrails.input.safety true->false OK +ai_gateway.guardrails.output.pii.behavior absent->x OK +ai_gateway.guardrails.output.pii.behavior absent->y OK +ai_gateway.guardrails.output.pii.behavior x->absent OK +ai_gateway.guardrails.output.pii.behavior x->y OK +ai_gateway.guardrails.output.pii.behavior y->absent OK +ai_gateway.guardrails.output.pii.behavior y->x OK +ai_gateway.guardrails.output.safety absent->false SUPPRESSED empty +ai_gateway.guardrails.output.safety absent->true OK +ai_gateway.guardrails.output.safety false->absent SUPPRESSED empty +ai_gateway.guardrails.output.safety false->true OK +ai_gateway.guardrails.output.safety true->absent OK +ai_gateway.guardrails.output.safety true->false OK +ai_gateway.inference_table_config.catalog_name absent->x OK +ai_gateway.inference_table_config.catalog_name absent->y OK +ai_gateway.inference_table_config.catalog_name x->absent OK +ai_gateway.inference_table_config.catalog_name x->y OK +ai_gateway.inference_table_config.catalog_name y->absent OK +ai_gateway.inference_table_config.catalog_name y->x OK +ai_gateway.inference_table_config.enabled absent->false SUPPRESSED empty +ai_gateway.inference_table_config.enabled absent->true OK +ai_gateway.inference_table_config.enabled false->absent SUPPRESSED empty +ai_gateway.inference_table_config.enabled false->true OK +ai_gateway.inference_table_config.enabled true->absent OK +ai_gateway.inference_table_config.enabled true->false OK +ai_gateway.inference_table_config.schema_name absent->x OK +ai_gateway.inference_table_config.schema_name absent->y OK +ai_gateway.inference_table_config.schema_name x->absent OK +ai_gateway.inference_table_config.schema_name x->y OK +ai_gateway.inference_table_config.schema_name y->absent OK +ai_gateway.inference_table_config.schema_name y->x OK +ai_gateway.inference_table_config.table_name_prefix absent->x OK +ai_gateway.inference_table_config.table_name_prefix absent->y OK +ai_gateway.inference_table_config.table_name_prefix x->absent OK +ai_gateway.inference_table_config.table_name_prefix x->y OK +ai_gateway.inference_table_config.table_name_prefix y->absent OK +ai_gateway.inference_table_config.table_name_prefix y->x OK +ai_gateway.usage_tracking_config.enabled absent->false OK +ai_gateway.usage_tracking_config.enabled absent->true OK +ai_gateway.usage_tracking_config.enabled false->absent SUPPRESSED empty +ai_gateway.usage_tracking_config.enabled false->true OK +ai_gateway.usage_tracking_config.enabled true->absent OK +ai_gateway.usage_tracking_config.enabled true->false OK +budget_policy_id absent->x SUPPRESSED no_update_api +budget_policy_id absent->y SUPPRESSED no_update_api +budget_policy_id x->absent NOT_OBSERVABLE +budget_policy_id x->y SUPPRESSED no_update_api +budget_policy_id y->absent NOT_OBSERVABLE +budget_policy_id y->x SUPPRESSED no_update_api +config.auto_capture_config.catalog_name absent->x OK +config.auto_capture_config.catalog_name absent->y OK_RECREATE +config.auto_capture_config.catalog_name x->absent OK_RECREATE +config.auto_capture_config.catalog_name x->y OK_RECREATE +config.auto_capture_config.catalog_name y->absent OK_RECREATE +config.auto_capture_config.catalog_name y->x OK_RECREATE +config.auto_capture_config.enabled absent->false SUPPRESSED empty +config.auto_capture_config.enabled absent->true OK +config.auto_capture_config.enabled false->absent SUPPRESSED empty +config.auto_capture_config.enabled false->true OK +config.auto_capture_config.enabled true->absent OK +config.auto_capture_config.enabled true->false OK +config.auto_capture_config.schema_name absent->x OK_RECREATE +config.auto_capture_config.schema_name absent->y OK_RECREATE +config.auto_capture_config.schema_name x->absent OK_RECREATE +config.auto_capture_config.schema_name x->y OK_RECREATE +config.auto_capture_config.schema_name y->absent OK_RECREATE +config.auto_capture_config.schema_name y->x OK_RECREATE +config.auto_capture_config.table_name_prefix absent->x OK_RECREATE +config.auto_capture_config.table_name_prefix absent->y OK_RECREATE +config.auto_capture_config.table_name_prefix x->absent OK_RECREATE +config.auto_capture_config.table_name_prefix x->y OK_RECREATE +config.auto_capture_config.table_name_prefix y->absent OK_RECREATE +config.auto_capture_config.table_name_prefix y->x OK_RECREATE +description absent->x OK_RECREATE +description absent->y OK_RECREATE +description x->absent OK_RECREATE +description x->y OK_RECREATE +description y->absent OK_RECREATE +description y->x OK_RECREATE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK_RECREATE +name y->x OK_RECREATE +route_optimized absent->false SUPPRESSED remote_already_set +route_optimized absent->true OK_RECREATE +route_optimized false->absent SUPPRESSED empty +route_optimized false->true OK_RECREATE +route_optimized true->absent OK_RECREATE +route_optimized true->false OK_RECREATE +telemetry_config.inference_table_config.name - SKIPPED needs a writable catalog and schema +telemetry_config.inference_table_config.sampling_fraction 1->2 OK +telemetry_config.inference_table_config.sampling_fraction 1->absent OK +telemetry_config.inference_table_config.sampling_fraction 2->1 OK +telemetry_config.inference_table_config.sampling_fraction 2->absent OK +telemetry_config.inference_table_config.sampling_fraction absent->1 OK +telemetry_config.inference_table_config.sampling_fraction absent->2 OK +telemetry_config.table_names.annotations_table absent->x OK +telemetry_config.table_names.annotations_table absent->y OK +telemetry_config.table_names.annotations_table x->absent OK +telemetry_config.table_names.annotations_table x->y OK +telemetry_config.table_names.annotations_table y->absent OK +telemetry_config.table_names.annotations_table y->x OK +telemetry_config.table_names.logs_table absent->x OK +telemetry_config.table_names.logs_table absent->y OK +telemetry_config.table_names.logs_table x->absent OK +telemetry_config.table_names.logs_table x->y OK +telemetry_config.table_names.logs_table y->absent OK +telemetry_config.table_names.logs_table y->x OK +telemetry_config.table_names.metrics_table absent->x OK +telemetry_config.table_names.metrics_table absent->y OK +telemetry_config.table_names.metrics_table x->absent OK +telemetry_config.table_names.metrics_table x->y OK +telemetry_config.table_names.metrics_table y->absent OK +telemetry_config.table_names.metrics_table y->x OK +telemetry_config.table_names.traces_table absent->x OK +telemetry_config.table_names.traces_table absent->y OK +telemetry_config.table_names.traces_table x->absent OK +telemetry_config.table_names.traces_table x->y OK +telemetry_config.table_names.traces_table y->absent OK +telemetry_config.table_names.traces_table y->x OK +telemetry_config.telemetry_profile_id absent->x OK +telemetry_config.telemetry_profile_id absent->y OK +telemetry_config.telemetry_profile_id x->absent OK +telemetry_config.telemetry_profile_id x->y OK +telemetry_config.telemetry_profile_id y->absent OK +telemetry_config.telemetry_profile_id y->x OK + +=== summary +OK 139 +OK_RECREATE 57 +SUPPRESSED 29 +NOT_OBSERVABLE 16 +DRIFT 7 +BACKEND_ERROR 6 +SKIPPED 2 +BASE_ERROR 30 + +=== not covered: 95 fields under a slice or map +ai_gateway.guardrails.input.invalid_keywords[*] +ai_gateway.guardrails.input.valid_topics[*] +ai_gateway.guardrails.output.invalid_keywords[*] +ai_gateway.guardrails.output.valid_topics[*] +ai_gateway.rate_limits[*].calls +ai_gateway.rate_limits[*].key +ai_gateway.rate_limits[*].principal +ai_gateway.rate_limits[*].renewal_period +ai_gateway.rate_limits[*].tokens +config.served_entities[*].burst_scaling_enabled +config.served_entities[*].entity_name +config.served_entities[*].entity_version +config.served_entities[*].environment_vars.* +config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key +config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext +config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id +config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id_plaintext +config.served_entities[*].external_model.amazon_bedrock_config.aws_region +config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key +config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key_plaintext +config.served_entities[*].external_model.amazon_bedrock_config.bedrock_provider +config.served_entities[*].external_model.amazon_bedrock_config.instance_profile_arn +config.served_entities[*].external_model.anthropic_config.anthropic_api_key +config.served_entities[*].external_model.anthropic_config.anthropic_api_key_plaintext +config.served_entities[*].external_model.cohere_config.cohere_api_base +config.served_entities[*].external_model.cohere_config.cohere_api_key +config.served_entities[*].external_model.cohere_config.cohere_api_key_plaintext +config.served_entities[*].external_model.custom_provider_config.api_key_auth.key +config.served_entities[*].external_model.custom_provider_config.api_key_auth.value +config.served_entities[*].external_model.custom_provider_config.api_key_auth.value_plaintext +config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token +config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token_plaintext +config.served_entities[*].external_model.custom_provider_config.custom_provider_url +config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token +config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token_plaintext +config.served_entities[*].external_model.databricks_model_serving_config.databricks_workspace_url +config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key +config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key_plaintext +config.served_entities[*].external_model.google_cloud_vertex_ai_config.project_id +config.served_entities[*].external_model.google_cloud_vertex_ai_config.region +config.served_entities[*].external_model.name +config.served_entities[*].external_model.openai_config.microsoft_entra_client_id +config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret +config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret_plaintext +config.served_entities[*].external_model.openai_config.microsoft_entra_tenant_id +config.served_entities[*].external_model.openai_config.openai_api_base +config.served_entities[*].external_model.openai_config.openai_api_key +config.served_entities[*].external_model.openai_config.openai_api_key_plaintext +config.served_entities[*].external_model.openai_config.openai_api_type +config.served_entities[*].external_model.openai_config.openai_api_version +config.served_entities[*].external_model.openai_config.openai_deployment_name +config.served_entities[*].external_model.openai_config.openai_organization +config.served_entities[*].external_model.palm_config.palm_api_key +config.served_entities[*].external_model.palm_config.palm_api_key_plaintext +config.served_entities[*].external_model.provider +config.served_entities[*].external_model.task +config.served_entities[*].instance_profile_arn +config.served_entities[*].max_provisioned_concurrency +config.served_entities[*].max_provisioned_throughput +config.served_entities[*].min_provisioned_concurrency +config.served_entities[*].min_provisioned_throughput +config.served_entities[*].name +config.served_entities[*].provisioned_model_units +config.served_entities[*].scale_to_zero_enabled +config.served_entities[*].workload_size +config.served_entities[*].workload_type +config.served_models[*].burst_scaling_enabled +config.served_models[*].environment_vars.* +config.served_models[*].instance_profile_arn +config.served_models[*].max_provisioned_concurrency +config.served_models[*].max_provisioned_throughput +config.served_models[*].min_provisioned_concurrency +config.served_models[*].min_provisioned_throughput +config.served_models[*].model_name +config.served_models[*].model_version +config.served_models[*].name +config.served_models[*].provisioned_model_units +config.served_models[*].scale_to_zero_enabled +config.served_models[*].workload_size +config.served_models[*].workload_type +config.traffic_config.routes[*].served_entity_name +config.traffic_config.routes[*].served_model_name +config.traffic_config.routes[*].traffic_percentage +email_notifications.on_update_failure[*] +email_notifications.on_update_success[*] +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +rate_limits[*].calls +rate_limits[*].key +rate_limits[*].renewal_period +tags[*].key +tags[*].value +telemetry_config.enabled_telemetry_features[*] diff --git a/bundle/direct/dresources_test/out.models.fields.txt b/bundle/direct/dresources_test/out.models.fields.txt new file mode 100644 index 00000000000..c7453cf2882 --- /dev/null +++ b/bundle/direct/dresources_test/out.models.fields.txt @@ -0,0 +1,28 @@ +=== model.yml.tmpl +description absent->x OK +description absent->y OK +description x->absent OK +description x->y OK +description y->absent OK +description y->x OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK_RECREATE +name y->x OK_RECREATE + +=== summary +OK 6 +OK_RECREATE 2 +NOT_OBSERVABLE 6 + +=== not covered: 6 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +tags[*].key +tags[*].value diff --git a/bundle/direct/dresources_test/out.pipelines.fields.txt b/bundle/direct/dresources_test/out.pipelines.fields.txt new file mode 100644 index 00000000000..37ceee5dae9 --- /dev/null +++ b/bundle/direct/dresources_test/out.pipelines.fields.txt @@ -0,0 +1,1406 @@ +=== pipeline.yml.tmpl +allow_duplicate_names absent->false SUPPRESSED empty +allow_duplicate_names absent->true OK +allow_duplicate_names false->absent SUPPRESSED empty +allow_duplicate_names false->true OK +allow_duplicate_names true->absent OK +allow_duplicate_names true->false OK +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +cascade_on_destroy absent->false OK +cascade_on_destroy absent->true OK +cascade_on_destroy false->absent OK +cascade_on_destroy false->true OK +cascade_on_destroy true->absent OK +cascade_on_destroy true->false OK +catalog absent->x OK +catalog absent->y OK +catalog x->absent OK +catalog x->y OK +catalog y->absent OK +catalog y->x OK +channel absent->x OK +channel absent->y OK +channel x->absent OK +channel x->y OK +channel y->absent OK +channel y->x OK +continuous absent->false SUPPRESSED empty +continuous absent->true OK +continuous false->absent SUPPRESSED empty +continuous false->true OK +continuous true->absent OK +continuous true->false OK +deployment.deployment_id absent->x OK +deployment.deployment_id absent->y OK +deployment.deployment_id x->absent OK +deployment.deployment_id x->y OK +deployment.deployment_id y->absent OK +deployment.deployment_id y->x OK +deployment.kind x->y OK +deployment.kind y->x OK +deployment.metadata_file_path absent->x OK +deployment.metadata_file_path absent->y OK +deployment.metadata_file_path x->absent OK +deployment.metadata_file_path x->y OK +deployment.metadata_file_path y->absent OK +deployment.metadata_file_path y->x OK +deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service +deployment.version_id x->absent NOT_OBSERVABLE +deployment.version_id x->y SUPPRESSED managed by the deployment metadata service +deployment.version_id y->absent NOT_OBSERVABLE +deployment.version_id y->x SUPPRESSED managed by the deployment metadata service +development absent->false SUPPRESSED empty +development absent->true OK +development false->absent SUPPRESSED empty +development false->true OK +development true->absent OK +development true->false OK +dry_run absent->false SUPPRESSED empty +dry_run absent->true DRIFT dry_run +dry_run false->absent SUPPRESSED remote_already_set +dry_run false->true DRIFT dry_run +dry_run true->absent SUPPRESSED remote_already_set +dry_run true->false OK +edition absent->x OK +edition absent->y OK +edition x->absent OK +edition x->y OK +edition y->absent OK +edition y->x OK +environment.environment_version absent->x OK +environment.environment_version absent->y OK +environment.environment_version x->absent OK +environment.environment_version x->y OK +environment.environment_version y->absent OK +environment.environment_version y->x OK +event_log.catalog absent->x OK +event_log.catalog absent->y OK +event_log.catalog x->absent OK +event_log.catalog x->y OK +event_log.catalog y->absent OK +event_log.catalog y->x OK +event_log.name absent->x OK +event_log.name absent->y OK +event_log.name x->absent OK +event_log.name x->y OK +event_log.name y->absent OK +event_log.name y->x OK +event_log.schema absent->x OK +event_log.schema absent->y OK +event_log.schema x->absent OK +event_log.schema x->y OK +event_log.schema y->absent OK +event_log.schema y->x OK +gateway_definition.connection_id absent->x OK +gateway_definition.connection_id absent->y OK_RECREATE +gateway_definition.connection_id x->absent OK_RECREATE +gateway_definition.connection_id x->y OK_RECREATE +gateway_definition.connection_id y->absent OK_RECREATE +gateway_definition.connection_id y->x OK_RECREATE +gateway_definition.connection_name x->y OK_RECREATE +gateway_definition.connection_name y->x OK_RECREATE +gateway_definition.connection_parameters.source_catalog absent->x OK +gateway_definition.connection_parameters.source_catalog absent->y OK +gateway_definition.connection_parameters.source_catalog x->absent OK +gateway_definition.connection_parameters.source_catalog x->y OK +gateway_definition.connection_parameters.source_catalog y->absent OK +gateway_definition.connection_parameters.source_catalog y->x OK +gateway_definition.gateway_storage_catalog x->y OK_RECREATE +gateway_definition.gateway_storage_catalog y->x OK_RECREATE +gateway_definition.gateway_storage_name absent->x OK +gateway_definition.gateway_storage_name absent->y OK +gateway_definition.gateway_storage_name x->absent OK +gateway_definition.gateway_storage_name x->y OK +gateway_definition.gateway_storage_name y->absent OK +gateway_definition.gateway_storage_name y->x OK +gateway_definition.gateway_storage_schema x->y OK_RECREATE +gateway_definition.gateway_storage_schema y->x OK_RECREATE +id absent->x NOT_OBSERVABLE +id absent->y NOT_OBSERVABLE +id x->absent NOT_OBSERVABLE +id x->y NOT_OBSERVABLE +id y->absent NOT_OBSERVABLE +id y->x NOT_OBSERVABLE +ingestion_definition.connection_name absent->x OK +ingestion_definition.connection_name absent->y OK_RECREATE +ingestion_definition.connection_name x->absent OK_RECREATE +ingestion_definition.connection_name x->y OK_RECREATE +ingestion_definition.connection_name y->absent OK_RECREATE +ingestion_definition.connection_name y->x OK_RECREATE +ingestion_definition.connector_type absent->x OK +ingestion_definition.connector_type absent->y OK +ingestion_definition.connector_type x->absent OK +ingestion_definition.connector_type x->y OK +ingestion_definition.connector_type y->absent OK +ingestion_definition.connector_type y->x OK +ingestion_definition.data_staging_options.catalog_name x->y OK +ingestion_definition.data_staging_options.catalog_name y->x OK +ingestion_definition.data_staging_options.schema_name x->y OK +ingestion_definition.data_staging_options.schema_name y->x OK +ingestion_definition.data_staging_options.volume_name absent->x OK +ingestion_definition.data_staging_options.volume_name absent->y OK +ingestion_definition.data_staging_options.volume_name x->absent OK +ingestion_definition.data_staging_options.volume_name x->y OK +ingestion_definition.data_staging_options.volume_name y->absent OK +ingestion_definition.data_staging_options.volume_name y->x OK +ingestion_definition.full_refresh_window.start_hour 1->2 OK +ingestion_definition.full_refresh_window.start_hour 2->1 OK +ingestion_definition.full_refresh_window.time_zone_id absent->x OK +ingestion_definition.full_refresh_window.time_zone_id absent->y OK +ingestion_definition.full_refresh_window.time_zone_id x->absent OK +ingestion_definition.full_refresh_window.time_zone_id x->y OK +ingestion_definition.full_refresh_window.time_zone_id y->absent OK +ingestion_definition.full_refresh_window.time_zone_id y->x OK +ingestion_definition.ingest_from_uc_foreign_catalog absent->false SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog absent->true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog false->absent SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog false->true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true->absent OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true->false OK_RECREATE +ingestion_definition.ingestion_gateway_id absent->x OK_RECREATE +ingestion_definition.ingestion_gateway_id absent->y OK_RECREATE +ingestion_definition.ingestion_gateway_id x->absent OK_RECREATE +ingestion_definition.ingestion_gateway_id x->y OK_RECREATE +ingestion_definition.ingestion_gateway_id y->absent OK_RECREATE +ingestion_definition.ingestion_gateway_id y->x OK_RECREATE +ingestion_definition.netsuite_jar_path absent->x OK +ingestion_definition.netsuite_jar_path absent->y OK +ingestion_definition.netsuite_jar_path x->absent OK +ingestion_definition.netsuite_jar_path x->y OK +ingestion_definition.netsuite_jar_path y->absent OK +ingestion_definition.netsuite_jar_path y->x OK +ingestion_definition.source_type absent->x OK +ingestion_definition.source_type absent->y OK +ingestion_definition.source_type x->absent OK +ingestion_definition.source_type x->y OK +ingestion_definition.source_type y->absent OK +ingestion_definition.source_type y->x OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false->true OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true->false OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->2 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->2 OK +ingestion_definition.table_configuration.enable_auto_clustering absent->false SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering absent->true OK +ingestion_definition.table_configuration.enable_auto_clustering false->absent SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering false->true OK +ingestion_definition.table_configuration.enable_auto_clustering true->absent OK +ingestion_definition.table_configuration.enable_auto_clustering true->false OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->x OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->x OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->2 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->2 OK +ingestion_definition.table_configuration.row_filter absent->x OK +ingestion_definition.table_configuration.row_filter absent->y OK +ingestion_definition.table_configuration.row_filter x->absent OK +ingestion_definition.table_configuration.row_filter x->y OK +ingestion_definition.table_configuration.row_filter y->absent OK +ingestion_definition.table_configuration.row_filter y->x OK +ingestion_definition.table_configuration.salesforce_include_formula_fields absent->false SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields absent->true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields false->absent SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields false->true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true->absent OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true->false OK +ingestion_definition.table_configuration.scd_type absent->x OK +ingestion_definition.table_configuration.scd_type absent->y OK +ingestion_definition.table_configuration.scd_type x->absent OK +ingestion_definition.table_configuration.scd_type x->y OK +ingestion_definition.table_configuration.scd_type y->absent OK +ingestion_definition.table_configuration.scd_type y->x OK +ingestion_definition.table_configuration.source_metadata_column absent->x OK +ingestion_definition.table_configuration.source_metadata_column absent->y OK +ingestion_definition.table_configuration.source_metadata_column x->absent OK +ingestion_definition.table_configuration.source_metadata_column x->y OK +ingestion_definition.table_configuration.source_metadata_column y->absent OK +ingestion_definition.table_configuration.source_metadata_column y->x OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent->false OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent->true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental false->absent SUPPRESSED empty +ingestion_definition.table_configuration.workday_report_parameters.incremental false->true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true->absent OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true->false OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +photon absent->false SUPPRESSED empty +photon absent->true OK +photon false->absent SUPPRESSED empty +photon false->true OK +photon true->absent OK +photon true->false OK +restart_window.start_hour 1->2 OK +restart_window.start_hour 2->1 OK +restart_window.time_zone_id absent->x OK +restart_window.time_zone_id absent->y OK +restart_window.time_zone_id x->absent OK +restart_window.time_zone_id x->y OK +restart_window.time_zone_id y->absent OK +restart_window.time_zone_id y->x OK +root_path absent->x OK +root_path absent->y OK +root_path x->absent OK +root_path x->y OK +root_path y->absent OK +root_path y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +schema absent->x OK +schema absent->y OK +schema x->absent OK +schema x->y OK +schema y->absent OK +schema y->x OK +serverless absent->false SUPPRESSED empty +serverless absent->true OK +serverless false->absent SUPPRESSED empty +serverless false->true OK +serverless true->absent OK +serverless true->false OK +serverless_compute_id absent->x OK +serverless_compute_id absent->y OK +serverless_compute_id x->absent OK +serverless_compute_id x->y OK +serverless_compute_id y->absent OK +serverless_compute_id y->x OK +storage absent->x OK_RECREATE +storage absent->y OK_RECREATE +storage x->absent OK_RECREATE +storage x->y OK_RECREATE +storage y->absent OK_RECREATE +storage y->x OK_RECREATE +target absent->x OK +target absent->y OK +target x->absent OK +target x->y OK +target y->absent OK +target y->x OK +trigger.cron.quartz_cron_schedule absent->x OK +trigger.cron.quartz_cron_schedule absent->y OK +trigger.cron.quartz_cron_schedule x->absent OK +trigger.cron.quartz_cron_schedule x->y OK +trigger.cron.quartz_cron_schedule y->absent OK +trigger.cron.quartz_cron_schedule y->x OK +trigger.cron.timezone_id absent->x OK +trigger.cron.timezone_id absent->y OK +trigger.cron.timezone_id x->absent OK +trigger.cron.timezone_id x->y OK +trigger.cron.timezone_id y->absent OK +trigger.cron.timezone_id y->x OK +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent OK +usage_policy_id x->y OK +usage_policy_id y->absent OK +usage_policy_id y->x OK + +=== pipeline_allow_duplicate_names.yml.tmpl +allow_duplicate_names absent->false SUPPRESSED empty +allow_duplicate_names absent->true OK +allow_duplicate_names false->absent SUPPRESSED empty +allow_duplicate_names false->true OK +allow_duplicate_names true->absent OK +allow_duplicate_names true->false OK +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +cascade_on_destroy absent->false OK +cascade_on_destroy absent->true OK +cascade_on_destroy false->absent OK +cascade_on_destroy false->true OK +cascade_on_destroy true->absent OK +cascade_on_destroy true->false OK +catalog absent->x OK +catalog absent->y OK +catalog x->absent OK +catalog x->y OK +catalog y->absent OK +catalog y->x OK +channel absent->x OK +channel absent->y OK +channel x->absent OK +channel x->y OK +channel y->absent OK +channel y->x OK +continuous absent->false SUPPRESSED empty +continuous absent->true OK +continuous false->absent SUPPRESSED empty +continuous false->true OK +continuous true->absent OK +continuous true->false OK +deployment.deployment_id absent->x OK +deployment.deployment_id absent->y OK +deployment.deployment_id x->absent OK +deployment.deployment_id x->y OK +deployment.deployment_id y->absent OK +deployment.deployment_id y->x OK +deployment.kind x->y OK +deployment.kind y->x OK +deployment.metadata_file_path absent->x OK +deployment.metadata_file_path absent->y OK +deployment.metadata_file_path x->absent OK +deployment.metadata_file_path x->y OK +deployment.metadata_file_path y->absent OK +deployment.metadata_file_path y->x OK +deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service +deployment.version_id x->absent NOT_OBSERVABLE +deployment.version_id x->y SUPPRESSED managed by the deployment metadata service +deployment.version_id y->absent NOT_OBSERVABLE +deployment.version_id y->x SUPPRESSED managed by the deployment metadata service +development absent->false SUPPRESSED empty +development absent->true OK +development false->absent SUPPRESSED empty +development false->true OK +development true->absent OK +development true->false OK +dry_run absent->false SUPPRESSED empty +dry_run absent->true DRIFT dry_run +dry_run false->absent SUPPRESSED remote_already_set +dry_run false->true DRIFT dry_run +dry_run true->absent SUPPRESSED remote_already_set +dry_run true->false OK +edition absent->x OK +edition absent->y OK +edition x->absent OK +edition x->y OK +edition y->absent OK +edition y->x OK +environment.environment_version absent->x OK +environment.environment_version absent->y OK +environment.environment_version x->absent OK +environment.environment_version x->y OK +environment.environment_version y->absent OK +environment.environment_version y->x OK +event_log.catalog absent->x OK +event_log.catalog absent->y OK +event_log.catalog x->absent OK +event_log.catalog x->y OK +event_log.catalog y->absent OK +event_log.catalog y->x OK +event_log.name absent->x OK +event_log.name absent->y OK +event_log.name x->absent OK +event_log.name x->y OK +event_log.name y->absent OK +event_log.name y->x OK +event_log.schema absent->x OK +event_log.schema absent->y OK +event_log.schema x->absent OK +event_log.schema x->y OK +event_log.schema y->absent OK +event_log.schema y->x OK +gateway_definition.connection_id absent->x OK +gateway_definition.connection_id absent->y OK_RECREATE +gateway_definition.connection_id x->absent OK_RECREATE +gateway_definition.connection_id x->y OK_RECREATE +gateway_definition.connection_id y->absent OK_RECREATE +gateway_definition.connection_id y->x OK_RECREATE +gateway_definition.connection_name x->y OK_RECREATE +gateway_definition.connection_name y->x OK_RECREATE +gateway_definition.connection_parameters.source_catalog absent->x OK +gateway_definition.connection_parameters.source_catalog absent->y OK +gateway_definition.connection_parameters.source_catalog x->absent OK +gateway_definition.connection_parameters.source_catalog x->y OK +gateway_definition.connection_parameters.source_catalog y->absent OK +gateway_definition.connection_parameters.source_catalog y->x OK +gateway_definition.gateway_storage_catalog x->y OK_RECREATE +gateway_definition.gateway_storage_catalog y->x OK_RECREATE +gateway_definition.gateway_storage_name absent->x OK +gateway_definition.gateway_storage_name absent->y OK +gateway_definition.gateway_storage_name x->absent OK +gateway_definition.gateway_storage_name x->y OK +gateway_definition.gateway_storage_name y->absent OK +gateway_definition.gateway_storage_name y->x OK +gateway_definition.gateway_storage_schema x->y OK_RECREATE +gateway_definition.gateway_storage_schema y->x OK_RECREATE +id absent->x NOT_OBSERVABLE +id absent->y NOT_OBSERVABLE +id x->absent NOT_OBSERVABLE +id x->y NOT_OBSERVABLE +id y->absent NOT_OBSERVABLE +id y->x NOT_OBSERVABLE +ingestion_definition.connection_name absent->x OK +ingestion_definition.connection_name absent->y OK_RECREATE +ingestion_definition.connection_name x->absent OK_RECREATE +ingestion_definition.connection_name x->y OK_RECREATE +ingestion_definition.connection_name y->absent OK_RECREATE +ingestion_definition.connection_name y->x OK_RECREATE +ingestion_definition.connector_type absent->x OK +ingestion_definition.connector_type absent->y OK +ingestion_definition.connector_type x->absent OK +ingestion_definition.connector_type x->y OK +ingestion_definition.connector_type y->absent OK +ingestion_definition.connector_type y->x OK +ingestion_definition.data_staging_options.catalog_name x->y OK +ingestion_definition.data_staging_options.catalog_name y->x OK +ingestion_definition.data_staging_options.schema_name x->y OK +ingestion_definition.data_staging_options.schema_name y->x OK +ingestion_definition.data_staging_options.volume_name absent->x OK +ingestion_definition.data_staging_options.volume_name absent->y OK +ingestion_definition.data_staging_options.volume_name x->absent OK +ingestion_definition.data_staging_options.volume_name x->y OK +ingestion_definition.data_staging_options.volume_name y->absent OK +ingestion_definition.data_staging_options.volume_name y->x OK +ingestion_definition.full_refresh_window.start_hour 1->2 OK +ingestion_definition.full_refresh_window.start_hour 2->1 OK +ingestion_definition.full_refresh_window.time_zone_id absent->x OK +ingestion_definition.full_refresh_window.time_zone_id absent->y OK +ingestion_definition.full_refresh_window.time_zone_id x->absent OK +ingestion_definition.full_refresh_window.time_zone_id x->y OK +ingestion_definition.full_refresh_window.time_zone_id y->absent OK +ingestion_definition.full_refresh_window.time_zone_id y->x OK +ingestion_definition.ingest_from_uc_foreign_catalog absent->false SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog absent->true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog false->absent SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog false->true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true->absent OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true->false OK_RECREATE +ingestion_definition.ingestion_gateway_id absent->x OK_RECREATE +ingestion_definition.ingestion_gateway_id absent->y OK_RECREATE +ingestion_definition.ingestion_gateway_id x->absent OK_RECREATE +ingestion_definition.ingestion_gateway_id x->y OK_RECREATE +ingestion_definition.ingestion_gateway_id y->absent OK_RECREATE +ingestion_definition.ingestion_gateway_id y->x OK_RECREATE +ingestion_definition.netsuite_jar_path absent->x OK +ingestion_definition.netsuite_jar_path absent->y OK +ingestion_definition.netsuite_jar_path x->absent OK +ingestion_definition.netsuite_jar_path x->y OK +ingestion_definition.netsuite_jar_path y->absent OK +ingestion_definition.netsuite_jar_path y->x OK +ingestion_definition.source_type absent->x OK +ingestion_definition.source_type absent->y OK +ingestion_definition.source_type x->absent OK +ingestion_definition.source_type x->y OK +ingestion_definition.source_type y->absent OK +ingestion_definition.source_type y->x OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false->true OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true->false OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->2 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->2 OK +ingestion_definition.table_configuration.enable_auto_clustering absent->false SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering absent->true OK +ingestion_definition.table_configuration.enable_auto_clustering false->absent SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering false->true OK +ingestion_definition.table_configuration.enable_auto_clustering true->absent OK +ingestion_definition.table_configuration.enable_auto_clustering true->false OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->x OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->x OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->2 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->2 OK +ingestion_definition.table_configuration.row_filter absent->x OK +ingestion_definition.table_configuration.row_filter absent->y OK +ingestion_definition.table_configuration.row_filter x->absent OK +ingestion_definition.table_configuration.row_filter x->y OK +ingestion_definition.table_configuration.row_filter y->absent OK +ingestion_definition.table_configuration.row_filter y->x OK +ingestion_definition.table_configuration.salesforce_include_formula_fields absent->false SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields absent->true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields false->absent SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields false->true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true->absent OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true->false OK +ingestion_definition.table_configuration.scd_type absent->x OK +ingestion_definition.table_configuration.scd_type absent->y OK +ingestion_definition.table_configuration.scd_type x->absent OK +ingestion_definition.table_configuration.scd_type x->y OK +ingestion_definition.table_configuration.scd_type y->absent OK +ingestion_definition.table_configuration.scd_type y->x OK +ingestion_definition.table_configuration.source_metadata_column absent->x OK +ingestion_definition.table_configuration.source_metadata_column absent->y OK +ingestion_definition.table_configuration.source_metadata_column x->absent OK +ingestion_definition.table_configuration.source_metadata_column x->y OK +ingestion_definition.table_configuration.source_metadata_column y->absent OK +ingestion_definition.table_configuration.source_metadata_column y->x OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent->false OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent->true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental false->absent SUPPRESSED empty +ingestion_definition.table_configuration.workday_report_parameters.incremental false->true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true->absent OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true->false OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +photon absent->false SUPPRESSED empty +photon absent->true OK +photon false->absent SUPPRESSED empty +photon false->true OK +photon true->absent OK +photon true->false OK +restart_window.start_hour 1->2 OK +restart_window.start_hour 2->1 OK +restart_window.time_zone_id absent->x OK +restart_window.time_zone_id absent->y OK +restart_window.time_zone_id x->absent OK +restart_window.time_zone_id x->y OK +restart_window.time_zone_id y->absent OK +restart_window.time_zone_id y->x OK +root_path absent->x OK +root_path absent->y OK +root_path x->absent OK +root_path x->y OK +root_path y->absent OK +root_path y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +schema absent->x OK +schema absent->y OK +schema x->absent OK +schema x->y OK +schema y->absent OK +schema y->x OK +serverless absent->false SUPPRESSED empty +serverless absent->true OK +serverless false->absent SUPPRESSED empty +serverless false->true OK +serverless true->absent OK +serverless true->false OK +serverless_compute_id absent->x OK +serverless_compute_id absent->y OK +serverless_compute_id x->absent OK +serverless_compute_id x->y OK +serverless_compute_id y->absent OK +serverless_compute_id y->x OK +storage absent->x OK_RECREATE +storage absent->y OK_RECREATE +storage x->absent OK_RECREATE +storage x->y OK_RECREATE +storage y->absent OK_RECREATE +storage y->x OK_RECREATE +target absent->x OK +target absent->y OK +target x->absent OK +target x->y OK +target y->absent OK +target y->x OK +trigger.cron.quartz_cron_schedule absent->x OK +trigger.cron.quartz_cron_schedule absent->y OK +trigger.cron.quartz_cron_schedule x->absent OK +trigger.cron.quartz_cron_schedule x->y OK +trigger.cron.quartz_cron_schedule y->absent OK +trigger.cron.quartz_cron_schedule y->x OK +trigger.cron.timezone_id absent->x OK +trigger.cron.timezone_id absent->y OK +trigger.cron.timezone_id x->absent OK +trigger.cron.timezone_id x->y OK +trigger.cron.timezone_id y->absent OK +trigger.cron.timezone_id y->x OK +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent OK +usage_policy_id x->y OK +usage_policy_id y->absent OK +usage_policy_id y->x OK + +=== pipeline_apply_policy_default_values.yml.tmpl +allow_duplicate_names absent->false SUPPRESSED empty +allow_duplicate_names absent->true OK +allow_duplicate_names false->absent SUPPRESSED empty +allow_duplicate_names false->true OK +allow_duplicate_names true->absent OK +allow_duplicate_names true->false OK +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +cascade_on_destroy absent->false OK +cascade_on_destroy absent->true OK +cascade_on_destroy false->absent OK +cascade_on_destroy false->true OK +cascade_on_destroy true->absent OK +cascade_on_destroy true->false OK +catalog absent->x OK +catalog absent->y OK +catalog x->absent OK +catalog x->y OK +catalog y->absent OK +catalog y->x OK +channel absent->x OK +channel absent->y OK +channel x->absent OK +channel x->y OK +channel y->absent OK +channel y->x OK +continuous absent->false SUPPRESSED empty +continuous absent->true OK +continuous false->absent SUPPRESSED empty +continuous false->true OK +continuous true->absent OK +continuous true->false OK +deployment.deployment_id absent->x OK +deployment.deployment_id absent->y OK +deployment.deployment_id x->absent OK +deployment.deployment_id x->y OK +deployment.deployment_id y->absent OK +deployment.deployment_id y->x OK +deployment.kind x->y OK +deployment.kind y->x OK +deployment.metadata_file_path absent->x OK +deployment.metadata_file_path absent->y OK +deployment.metadata_file_path x->absent OK +deployment.metadata_file_path x->y OK +deployment.metadata_file_path y->absent OK +deployment.metadata_file_path y->x OK +deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service +deployment.version_id x->absent NOT_OBSERVABLE +deployment.version_id x->y SUPPRESSED managed by the deployment metadata service +deployment.version_id y->absent NOT_OBSERVABLE +deployment.version_id y->x SUPPRESSED managed by the deployment metadata service +development absent->false SUPPRESSED empty +development absent->true OK +development false->absent SUPPRESSED empty +development false->true OK +development true->absent OK +development true->false OK +dry_run absent->false SUPPRESSED empty +dry_run absent->true DRIFT dry_run +dry_run false->absent SUPPRESSED remote_already_set +dry_run false->true DRIFT dry_run +dry_run true->absent SUPPRESSED remote_already_set +dry_run true->false OK +edition absent->x OK +edition absent->y OK +edition x->absent OK +edition x->y OK +edition y->absent OK +edition y->x OK +environment.environment_version absent->x OK +environment.environment_version absent->y OK +environment.environment_version x->absent OK +environment.environment_version x->y OK +environment.environment_version y->absent OK +environment.environment_version y->x OK +event_log.catalog absent->x OK +event_log.catalog absent->y OK +event_log.catalog x->absent OK +event_log.catalog x->y OK +event_log.catalog y->absent OK +event_log.catalog y->x OK +event_log.name absent->x OK +event_log.name absent->y OK +event_log.name x->absent OK +event_log.name x->y OK +event_log.name y->absent OK +event_log.name y->x OK +event_log.schema absent->x OK +event_log.schema absent->y OK +event_log.schema x->absent OK +event_log.schema x->y OK +event_log.schema y->absent OK +event_log.schema y->x OK +gateway_definition.connection_id absent->x OK +gateway_definition.connection_id absent->y OK_RECREATE +gateway_definition.connection_id x->absent OK_RECREATE +gateway_definition.connection_id x->y OK_RECREATE +gateway_definition.connection_id y->absent OK_RECREATE +gateway_definition.connection_id y->x OK_RECREATE +gateway_definition.connection_name x->y OK_RECREATE +gateway_definition.connection_name y->x OK_RECREATE +gateway_definition.connection_parameters.source_catalog absent->x OK +gateway_definition.connection_parameters.source_catalog absent->y OK +gateway_definition.connection_parameters.source_catalog x->absent OK +gateway_definition.connection_parameters.source_catalog x->y OK +gateway_definition.connection_parameters.source_catalog y->absent OK +gateway_definition.connection_parameters.source_catalog y->x OK +gateway_definition.gateway_storage_catalog x->y OK_RECREATE +gateway_definition.gateway_storage_catalog y->x OK_RECREATE +gateway_definition.gateway_storage_name absent->x OK +gateway_definition.gateway_storage_name absent->y OK +gateway_definition.gateway_storage_name x->absent OK +gateway_definition.gateway_storage_name x->y OK +gateway_definition.gateway_storage_name y->absent OK +gateway_definition.gateway_storage_name y->x OK +gateway_definition.gateway_storage_schema x->y OK_RECREATE +gateway_definition.gateway_storage_schema y->x OK_RECREATE +id absent->x NOT_OBSERVABLE +id absent->y NOT_OBSERVABLE +id x->absent NOT_OBSERVABLE +id x->y NOT_OBSERVABLE +id y->absent NOT_OBSERVABLE +id y->x NOT_OBSERVABLE +ingestion_definition.connection_name absent->x OK +ingestion_definition.connection_name absent->y OK_RECREATE +ingestion_definition.connection_name x->absent OK_RECREATE +ingestion_definition.connection_name x->y OK_RECREATE +ingestion_definition.connection_name y->absent OK_RECREATE +ingestion_definition.connection_name y->x OK_RECREATE +ingestion_definition.connector_type absent->x OK +ingestion_definition.connector_type absent->y OK +ingestion_definition.connector_type x->absent OK +ingestion_definition.connector_type x->y OK +ingestion_definition.connector_type y->absent OK +ingestion_definition.connector_type y->x OK +ingestion_definition.data_staging_options.catalog_name x->y OK +ingestion_definition.data_staging_options.catalog_name y->x OK +ingestion_definition.data_staging_options.schema_name x->y OK +ingestion_definition.data_staging_options.schema_name y->x OK +ingestion_definition.data_staging_options.volume_name absent->x OK +ingestion_definition.data_staging_options.volume_name absent->y OK +ingestion_definition.data_staging_options.volume_name x->absent OK +ingestion_definition.data_staging_options.volume_name x->y OK +ingestion_definition.data_staging_options.volume_name y->absent OK +ingestion_definition.data_staging_options.volume_name y->x OK +ingestion_definition.full_refresh_window.start_hour 1->2 OK +ingestion_definition.full_refresh_window.start_hour 2->1 OK +ingestion_definition.full_refresh_window.time_zone_id absent->x OK +ingestion_definition.full_refresh_window.time_zone_id absent->y OK +ingestion_definition.full_refresh_window.time_zone_id x->absent OK +ingestion_definition.full_refresh_window.time_zone_id x->y OK +ingestion_definition.full_refresh_window.time_zone_id y->absent OK +ingestion_definition.full_refresh_window.time_zone_id y->x OK +ingestion_definition.ingest_from_uc_foreign_catalog absent->false SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog absent->true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog false->absent SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog false->true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true->absent OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true->false OK_RECREATE +ingestion_definition.ingestion_gateway_id absent->x OK_RECREATE +ingestion_definition.ingestion_gateway_id absent->y OK_RECREATE +ingestion_definition.ingestion_gateway_id x->absent OK_RECREATE +ingestion_definition.ingestion_gateway_id x->y OK_RECREATE +ingestion_definition.ingestion_gateway_id y->absent OK_RECREATE +ingestion_definition.ingestion_gateway_id y->x OK_RECREATE +ingestion_definition.netsuite_jar_path absent->x OK +ingestion_definition.netsuite_jar_path absent->y OK +ingestion_definition.netsuite_jar_path x->absent OK +ingestion_definition.netsuite_jar_path x->y OK +ingestion_definition.netsuite_jar_path y->absent OK +ingestion_definition.netsuite_jar_path y->x OK +ingestion_definition.source_type absent->x OK +ingestion_definition.source_type absent->y OK +ingestion_definition.source_type x->absent OK +ingestion_definition.source_type x->y OK +ingestion_definition.source_type y->absent OK +ingestion_definition.source_type y->x OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false->true OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true->false OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->2 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->2 OK +ingestion_definition.table_configuration.enable_auto_clustering absent->false SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering absent->true OK +ingestion_definition.table_configuration.enable_auto_clustering false->absent SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering false->true OK +ingestion_definition.table_configuration.enable_auto_clustering true->absent OK +ingestion_definition.table_configuration.enable_auto_clustering true->false OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->x OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->x OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->2 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->2 OK +ingestion_definition.table_configuration.row_filter absent->x OK +ingestion_definition.table_configuration.row_filter absent->y OK +ingestion_definition.table_configuration.row_filter x->absent OK +ingestion_definition.table_configuration.row_filter x->y OK +ingestion_definition.table_configuration.row_filter y->absent OK +ingestion_definition.table_configuration.row_filter y->x OK +ingestion_definition.table_configuration.salesforce_include_formula_fields absent->false SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields absent->true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields false->absent SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields false->true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true->absent OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true->false OK +ingestion_definition.table_configuration.scd_type absent->x OK +ingestion_definition.table_configuration.scd_type absent->y OK +ingestion_definition.table_configuration.scd_type x->absent OK +ingestion_definition.table_configuration.scd_type x->y OK +ingestion_definition.table_configuration.scd_type y->absent OK +ingestion_definition.table_configuration.scd_type y->x OK +ingestion_definition.table_configuration.source_metadata_column absent->x OK +ingestion_definition.table_configuration.source_metadata_column absent->y OK +ingestion_definition.table_configuration.source_metadata_column x->absent OK +ingestion_definition.table_configuration.source_metadata_column x->y OK +ingestion_definition.table_configuration.source_metadata_column y->absent OK +ingestion_definition.table_configuration.source_metadata_column y->x OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent->false OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent->true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental false->absent SUPPRESSED empty +ingestion_definition.table_configuration.workday_report_parameters.incremental false->true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true->absent OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true->false OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name absent->x OK +name absent->y OK +name x->absent OK +name x->y OK +name y->absent OK +name y->x OK +photon absent->false SUPPRESSED empty +photon absent->true OK +photon false->absent SUPPRESSED empty +photon false->true OK +photon true->absent OK +photon true->false OK +restart_window.start_hour 1->2 OK +restart_window.start_hour 2->1 OK +restart_window.time_zone_id absent->x OK +restart_window.time_zone_id absent->y OK +restart_window.time_zone_id x->absent OK +restart_window.time_zone_id x->y OK +restart_window.time_zone_id y->absent OK +restart_window.time_zone_id y->x OK +root_path absent->x OK +root_path absent->y OK +root_path x->absent OK +root_path x->y OK +root_path y->absent OK +root_path y->x OK +run_as.service_principal_name absent->x OK +run_as.service_principal_name absent->y OK +run_as.service_principal_name x->absent OK +run_as.service_principal_name x->y OK +run_as.service_principal_name y->absent OK +run_as.service_principal_name y->x OK +run_as.user_name absent->x OK +run_as.user_name absent->y OK +run_as.user_name x->absent OK +run_as.user_name x->y OK +run_as.user_name y->absent OK +run_as.user_name y->x OK +schema absent->x OK +schema absent->y OK +schema x->absent OK +schema x->y OK +schema y->absent OK +schema y->x OK +serverless absent->false SUPPRESSED empty +serverless absent->true OK +serverless false->absent SUPPRESSED empty +serverless false->true OK +serverless true->absent OK +serverless true->false OK +serverless_compute_id absent->x OK +serverless_compute_id absent->y OK +serverless_compute_id x->absent OK +serverless_compute_id x->y OK +serverless_compute_id y->absent OK +serverless_compute_id y->x OK +storage absent->x OK_RECREATE +storage absent->y OK_RECREATE +storage x->absent OK_RECREATE +storage x->y OK_RECREATE +storage y->absent OK_RECREATE +storage y->x OK_RECREATE +target absent->x OK +target absent->y OK +target x->absent OK +target x->y OK +target y->absent OK +target y->x OK +trigger.cron.quartz_cron_schedule absent->x OK +trigger.cron.quartz_cron_schedule absent->y OK +trigger.cron.quartz_cron_schedule x->absent OK +trigger.cron.quartz_cron_schedule x->y OK +trigger.cron.quartz_cron_schedule y->absent OK +trigger.cron.quartz_cron_schedule y->x OK +trigger.cron.timezone_id absent->x OK +trigger.cron.timezone_id absent->y OK +trigger.cron.timezone_id x->absent OK +trigger.cron.timezone_id x->y OK +trigger.cron.timezone_id y->absent OK +trigger.cron.timezone_id y->x OK +usage_policy_id absent->x OK +usage_policy_id absent->y OK +usage_policy_id x->absent OK +usage_policy_id x->y OK +usage_policy_id y->absent OK +usage_policy_id y->x OK + +=== summary +OK 774 +OK_RECREATE 96 +SUPPRESSED 72 +NOT_OBSERVABLE 42 +DRIFT 6 + +=== not covered: 402 fields under a slice or map +clusters[*].apply_policy_default_values +clusters[*].autoscale.max_workers +clusters[*].autoscale.min_workers +clusters[*].autoscale.mode +clusters[*].aws_attributes.availability +clusters[*].aws_attributes.ebs_volume_count +clusters[*].aws_attributes.ebs_volume_iops +clusters[*].aws_attributes.ebs_volume_size +clusters[*].aws_attributes.ebs_volume_throughput +clusters[*].aws_attributes.ebs_volume_type +clusters[*].aws_attributes.first_on_demand +clusters[*].aws_attributes.instance_profile_arn +clusters[*].aws_attributes.spot_bid_price_percent +clusters[*].aws_attributes.zone_id +clusters[*].azure_attributes.availability +clusters[*].azure_attributes.capacity_reservation_group +clusters[*].azure_attributes.first_on_demand +clusters[*].azure_attributes.log_analytics_info.log_analytics_primary_key +clusters[*].azure_attributes.log_analytics_info.log_analytics_workspace_id +clusters[*].azure_attributes.spot_bid_max_price +clusters[*].cluster_log_conf.dbfs.destination +clusters[*].cluster_log_conf.s3.canned_acl +clusters[*].cluster_log_conf.s3.destination +clusters[*].cluster_log_conf.s3.enable_encryption +clusters[*].cluster_log_conf.s3.encryption_type +clusters[*].cluster_log_conf.s3.endpoint +clusters[*].cluster_log_conf.s3.kms_key +clusters[*].cluster_log_conf.s3.region +clusters[*].cluster_log_conf.volumes.destination +clusters[*].custom_tags.* +clusters[*].driver_instance_pool_id +clusters[*].driver_node_type_id +clusters[*].enable_local_disk_encryption +clusters[*].gcp_attributes.availability +clusters[*].gcp_attributes.boot_disk_size +clusters[*].gcp_attributes.confidential_compute_type +clusters[*].gcp_attributes.first_on_demand +clusters[*].gcp_attributes.google_service_account +clusters[*].gcp_attributes.local_ssd_count +clusters[*].gcp_attributes.use_preemptible_executors +clusters[*].gcp_attributes.zone_id +clusters[*].init_scripts[*].abfss.destination +clusters[*].init_scripts[*].dbfs.destination +clusters[*].init_scripts[*].file.destination +clusters[*].init_scripts[*].gcs.destination +clusters[*].init_scripts[*].s3.canned_acl +clusters[*].init_scripts[*].s3.destination +clusters[*].init_scripts[*].s3.enable_encryption +clusters[*].init_scripts[*].s3.encryption_type +clusters[*].init_scripts[*].s3.endpoint +clusters[*].init_scripts[*].s3.kms_key +clusters[*].init_scripts[*].s3.region +clusters[*].init_scripts[*].volumes.destination +clusters[*].init_scripts[*].workspace.destination +clusters[*].instance_pool_id +clusters[*].label +clusters[*].node_type_id +clusters[*].num_workers +clusters[*].policy_id +clusters[*].spark_conf.* +clusters[*].spark_env_vars.* +clusters[*].ssh_public_keys[*] +configuration.* +environment.dependencies[*] +filters.exclude[*] +filters.include[*] +ingestion_definition.full_refresh_window.days_of_week[*] +ingestion_definition.objects[*].report.destination_catalog +ingestion_definition.objects[*].report.destination_schema +ingestion_definition.objects[*].report.destination_table +ingestion_definition.objects[*].report.source_url +ingestion_definition.objects[*].report.table_configuration.auto_full_refresh_policy.enabled +ingestion_definition.objects[*].report.table_configuration.auto_full_refresh_policy.min_interval_hours +ingestion_definition.objects[*].report.table_configuration.clustering_columns[*] +ingestion_definition.objects[*].report.table_configuration.enable_auto_clustering +ingestion_definition.objects[*].report.table_configuration.exclude_columns[*] +ingestion_definition.objects[*].report.table_configuration.include_columns[*] +ingestion_definition.objects[*].report.table_configuration.primary_keys[*] +ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.cursor_columns[*] +ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.deletion_condition +ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds +ingestion_definition.objects[*].report.table_configuration.row_filter +ingestion_definition.objects[*].report.table_configuration.salesforce_include_formula_fields +ingestion_definition.objects[*].report.table_configuration.scd_type +ingestion_definition.objects[*].report.table_configuration.sequence_by[*] +ingestion_definition.objects[*].report.table_configuration.source_metadata_column +ingestion_definition.objects[*].report.table_configuration.table_properties.* +ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.incremental +ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.parameters.* +ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].key +ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].value +ingestion_definition.objects[*].schema.connector_options.api_source_connector_options.options.* +ingestion_definition.objects[*].schema.connector_options.confluence_options.include_confluence_spaces[*] +ingestion_definition.objects[*].schema.connector_options.gdrive_options.entity_type +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format_options.* +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.infer_column_types +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.rescued_data_column +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_hints +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.single_variant_column +ingestion_definition.objects[*].schema.connector_options.gdrive_options.url +ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.resource +ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.resource_fields[*] +ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.segments[*] +ingestion_definition.objects[*].schema.connector_options.google_ads_options.lookback_window_days +ingestion_definition.objects[*].schema.connector_options.google_ads_options.manager_account_id +ingestion_definition.objects[*].schema.connector_options.google_ads_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.jira_options.include_jira_spaces[*] +ingestion_definition.objects[*].schema.connector_options.kafka_options.client_config.* +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.format +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.input_column +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.as_variant +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_file_path +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_hints +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.output_column +ingestion_definition.objects[*].schema.connector_options.kafka_options.max_offsets_per_trigger +ingestion_definition.objects[*].schema.connector_options.kafka_options.starting_offset +ingestion_definition.objects[*].schema.connector_options.kafka_options.topic_pattern +ingestion_definition.objects[*].schema.connector_options.kafka_options.topics[*] +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.format +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.input_column +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.as_variant +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_file_path +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_hints +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.output_column +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.entity_granularity[*] +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.finder +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.time_granularity +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.lookback_window_days +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.marketo_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_attribution_windows[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_report_time +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_insights_lookback_window +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_attribution_windows[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_report_time +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.level +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.time_increment +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.level +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.start_date +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.time_increment +ingestion_definition.objects[*].schema.connector_options.outlook_options.attachment_mode +ingestion_definition.objects[*].schema.connector_options.outlook_options.body_format +ingestion_definition.objects[*].schema.connector_options.outlook_options.folder_filter[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.include_folders[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.include_mailboxes[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.include_senders[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.include_subjects[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.sender_filter[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.start_date +ingestion_definition.objects[*].schema.connector_options.outlook_options.subject_filter[*] +ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.custom_report_options.breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.custom_report_options.fields[*] +ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.lookback_window_days +ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.entity_type +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format_options.* +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.infer_column_types +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_hints +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.single_variant_column +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.url +ingestion_definition.objects[*].schema.connector_options.smartsheet_options.enforce_schema +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.data_level +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.dimensions[*] +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.query_lifetime +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.report_type +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.data_level +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.dimensions[*] +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.lookback_window_days +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.metrics[*] +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.query_lifetime +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.report_type +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.zendesk_support_options.start_date +ingestion_definition.objects[*].schema.destination_catalog +ingestion_definition.objects[*].schema.destination_schema +ingestion_definition.objects[*].schema.fanout_options.fanout_by +ingestion_definition.objects[*].schema.fanout_options.transforms[*].format +ingestion_definition.objects[*].schema.fanout_options.transforms[*].input_column +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.as_variant +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_evolution_mode +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_file_path +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_hints +ingestion_definition.objects[*].schema.fanout_options.transforms[*].output_column +ingestion_definition.objects[*].schema.source_catalog +ingestion_definition.objects[*].schema.source_schema +ingestion_definition.objects[*].schema.table_configuration.auto_full_refresh_policy.enabled +ingestion_definition.objects[*].schema.table_configuration.auto_full_refresh_policy.min_interval_hours +ingestion_definition.objects[*].schema.table_configuration.clustering_columns[*] +ingestion_definition.objects[*].schema.table_configuration.enable_auto_clustering +ingestion_definition.objects[*].schema.table_configuration.exclude_columns[*] +ingestion_definition.objects[*].schema.table_configuration.include_columns[*] +ingestion_definition.objects[*].schema.table_configuration.primary_keys[*] +ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.cursor_columns[*] +ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.deletion_condition +ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds +ingestion_definition.objects[*].schema.table_configuration.row_filter +ingestion_definition.objects[*].schema.table_configuration.salesforce_include_formula_fields +ingestion_definition.objects[*].schema.table_configuration.scd_type +ingestion_definition.objects[*].schema.table_configuration.sequence_by[*] +ingestion_definition.objects[*].schema.table_configuration.source_metadata_column +ingestion_definition.objects[*].schema.table_configuration.table_properties.* +ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.incremental +ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.parameters.* +ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].key +ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].value +ingestion_definition.objects[*].table.connector_options.api_source_connector_options.options.* +ingestion_definition.objects[*].table.connector_options.confluence_options.include_confluence_spaces[*] +ingestion_definition.objects[*].table.connector_options.gdrive_options.entity_type +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format_options.* +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.infer_column_types +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.rescued_data_column +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_hints +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.single_variant_column +ingestion_definition.objects[*].table.connector_options.gdrive_options.url +ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.resource +ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.resource_fields[*] +ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.segments[*] +ingestion_definition.objects[*].table.connector_options.google_ads_options.lookback_window_days +ingestion_definition.objects[*].table.connector_options.google_ads_options.manager_account_id +ingestion_definition.objects[*].table.connector_options.google_ads_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.jira_options.include_jira_spaces[*] +ingestion_definition.objects[*].table.connector_options.kafka_options.client_config.* +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.format +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.input_column +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.as_variant +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_file_path +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_hints +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.output_column +ingestion_definition.objects[*].table.connector_options.kafka_options.max_offsets_per_trigger +ingestion_definition.objects[*].table.connector_options.kafka_options.starting_offset +ingestion_definition.objects[*].table.connector_options.kafka_options.topic_pattern +ingestion_definition.objects[*].table.connector_options.kafka_options.topics[*] +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.format +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.input_column +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.as_variant +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_file_path +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_hints +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.output_column +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.entity_granularity[*] +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.finder +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.time_granularity +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.lookback_window_days +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.marketo_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_attribution_windows[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_breakdowns[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_report_time +ingestion_definition.objects[*].table.connector_options.meta_ads_options.breakdowns[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_insights_lookback_window +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_attribution_windows[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_breakdowns[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_report_time +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.breakdowns[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.level +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.time_increment +ingestion_definition.objects[*].table.connector_options.meta_ads_options.level +ingestion_definition.objects[*].table.connector_options.meta_ads_options.start_date +ingestion_definition.objects[*].table.connector_options.meta_ads_options.time_increment +ingestion_definition.objects[*].table.connector_options.outlook_options.attachment_mode +ingestion_definition.objects[*].table.connector_options.outlook_options.body_format +ingestion_definition.objects[*].table.connector_options.outlook_options.folder_filter[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.include_folders[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.include_mailboxes[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.include_senders[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.include_subjects[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.sender_filter[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.start_date +ingestion_definition.objects[*].table.connector_options.outlook_options.subject_filter[*] +ingestion_definition.objects[*].table.connector_options.reddit_ads_options.custom_report_options.breakdowns[*] +ingestion_definition.objects[*].table.connector_options.reddit_ads_options.custom_report_options.fields[*] +ingestion_definition.objects[*].table.connector_options.reddit_ads_options.lookback_window_days +ingestion_definition.objects[*].table.connector_options.reddit_ads_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.sharepoint_options.entity_type +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format_options.* +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.infer_column_types +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_hints +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.single_variant_column +ingestion_definition.objects[*].table.connector_options.sharepoint_options.url +ingestion_definition.objects[*].table.connector_options.smartsheet_options.enforce_schema +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.data_level +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.dimensions[*] +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.query_lifetime +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.report_type +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.data_level +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.dimensions[*] +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.lookback_window_days +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.metrics[*] +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.query_lifetime +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.report_type +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.zendesk_support_options.start_date +ingestion_definition.objects[*].table.destination_catalog +ingestion_definition.objects[*].table.destination_schema +ingestion_definition.objects[*].table.destination_table +ingestion_definition.objects[*].table.source_catalog +ingestion_definition.objects[*].table.source_schema +ingestion_definition.objects[*].table.source_table +ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy.enabled +ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy.min_interval_hours +ingestion_definition.objects[*].table.table_configuration.clustering_columns[*] +ingestion_definition.objects[*].table.table_configuration.enable_auto_clustering +ingestion_definition.objects[*].table.table_configuration.exclude_columns[*] +ingestion_definition.objects[*].table.table_configuration.include_columns[*] +ingestion_definition.objects[*].table.table_configuration.primary_keys[*] +ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.cursor_columns[*] +ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.deletion_condition +ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds +ingestion_definition.objects[*].table.table_configuration.row_filter +ingestion_definition.objects[*].table.table_configuration.salesforce_include_formula_fields +ingestion_definition.objects[*].table.table_configuration.scd_type +ingestion_definition.objects[*].table.table_configuration.sequence_by[*] +ingestion_definition.objects[*].table.table_configuration.source_metadata_column +ingestion_definition.objects[*].table.table_configuration.table_properties.* +ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.incremental +ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.parameters.* +ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.report_parameters[*].key +ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.report_parameters[*].value +ingestion_definition.source_configurations[*].api_source_connector_config.configs.* +ingestion_definition.source_configurations[*].catalog.postgres.slot_config.publication_name +ingestion_definition.source_configurations[*].catalog.postgres.slot_config.slot_name +ingestion_definition.source_configurations[*].catalog.source_catalog +ingestion_definition.source_configurations[*].google_ads_config.manager_account_id +ingestion_definition.table_configuration.clustering_columns[*] +ingestion_definition.table_configuration.exclude_columns[*] +ingestion_definition.table_configuration.include_columns[*] +ingestion_definition.table_configuration.primary_keys[*] +ingestion_definition.table_configuration.query_based_connector_config.cursor_columns[*] +ingestion_definition.table_configuration.sequence_by[*] +ingestion_definition.table_configuration.table_properties.* +ingestion_definition.table_configuration.workday_report_parameters.parameters.* +ingestion_definition.table_configuration.workday_report_parameters.report_parameters[*].key +ingestion_definition.table_configuration.workday_report_parameters.report_parameters[*].value +libraries[*].file.path +libraries[*].glob.include +libraries[*].jar +libraries[*].maven.coordinates +libraries[*].maven.exclusions[*] +libraries[*].maven.repo +libraries[*].notebook.path +libraries[*].whl +notifications[*].alerts[*] +notifications[*].email_recipients[*] +parameters.* +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +restart_window.days_of_week[*] +tags.* diff --git a/bundle/direct/dresources_test/out.postgres_projects.fields.txt b/bundle/direct/dresources_test/out.postgres_projects.fields.txt new file mode 100644 index 00000000000..5dabe17e388 --- /dev/null +++ b/bundle/direct/dresources_test/out.postgres_projects.fields.txt @@ -0,0 +1,78 @@ +=== postgres_project.yml.tmpl +budget_policy_id absent->x OK +budget_policy_id absent->y OK +budget_policy_id x->absent OK +budget_policy_id x->y OK +budget_policy_id y->absent OK +budget_policy_id y->x OK +default_branch absent->x OK +default_branch absent->y OK +default_branch x->absent OK +default_branch x->y OK +default_branch y->absent OK +default_branch y->x OK +default_endpoint_settings.autoscaling_limit_max_cu 1->2 OK +default_endpoint_settings.autoscaling_limit_max_cu 1->absent OK +default_endpoint_settings.autoscaling_limit_max_cu 2->1 OK +default_endpoint_settings.autoscaling_limit_max_cu 2->absent OK +default_endpoint_settings.autoscaling_limit_max_cu absent->1 OK +default_endpoint_settings.autoscaling_limit_max_cu absent->2 OK +default_endpoint_settings.autoscaling_limit_min_cu 1->2 OK +default_endpoint_settings.autoscaling_limit_min_cu 1->absent OK +default_endpoint_settings.autoscaling_limit_min_cu 2->1 OK +default_endpoint_settings.autoscaling_limit_min_cu 2->absent OK +default_endpoint_settings.autoscaling_limit_min_cu absent->1 OK +default_endpoint_settings.autoscaling_limit_min_cu absent->2 OK +default_endpoint_settings.no_suspension absent->false OK +default_endpoint_settings.no_suspension absent->true OK +default_endpoint_settings.no_suspension false->absent OK +default_endpoint_settings.no_suspension false->true OK +default_endpoint_settings.no_suspension true->absent OK +default_endpoint_settings.no_suspension true->false OK +display_name absent->x OK +display_name absent->y OK +display_name x->absent OK +display_name x->y OK +display_name y->absent OK +display_name y->x OK +enable_pg_native_login absent->false SUPPRESSED empty +enable_pg_native_login absent->true OK +enable_pg_native_login false->absent SUPPRESSED empty +enable_pg_native_login false->true OK +enable_pg_native_login true->absent OK +enable_pg_native_login true->false OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +pg_version 16->17 OK_RECREATE +pg_version 16->absent OK_RECREATE +pg_version 17->16 OK_RECREATE +pg_version 17->absent OK_RECREATE +pg_version absent->16 OK_RECREATE +pg_version absent->17 OK_RECREATE +project_id pgproj1->pgproj2 OK_RECREATE +project_id pgproj2->pgproj1 OK_RECREATE +purge_on_delete absent->false SUPPRESSED empty +purge_on_delete absent->true OK +purge_on_delete false->absent SUPPRESSED empty +purge_on_delete false->true OK +purge_on_delete true->absent OK +purge_on_delete true->false OK + +=== summary +OK 44 +OK_RECREATE 8 +SUPPRESSED 4 +NOT_OBSERVABLE 6 + +=== not covered: 7 fields under a slice or map +custom_tags[*].key +custom_tags[*].value +default_endpoint_settings.pg_settings.* +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/dresources_test/out.postgres_synced_tables.fields.txt b/bundle/direct/dresources_test/out.postgres_synced_tables.fields.txt new file mode 100644 index 00000000000..d58581a9a53 --- /dev/null +++ b/bundle/direct/dresources_test/out.postgres_synced_tables.fields.txt @@ -0,0 +1,96 @@ +=== postgres_synced_table.yml.tmpl +accelerated_sync absent->false SUPPRESSED empty +accelerated_sync absent->true OK_RECREATE +accelerated_sync false->absent SUPPRESSED empty +accelerated_sync false->true OK_RECREATE +accelerated_sync true->absent OK_RECREATE +accelerated_sync true->false OK_RECREATE +branch absent->x OK_RECREATE +branch absent->y OK_RECREATE +branch x->absent OK_RECREATE +branch x->y OK_RECREATE +branch y->absent OK_RECREATE +branch y->x OK_RECREATE +create_database_objects_if_missing absent->false SUPPRESSED empty +create_database_objects_if_missing absent->true OK_RECREATE +create_database_objects_if_missing false->absent SUPPRESSED empty +create_database_objects_if_missing false->true OK_RECREATE +create_database_objects_if_missing true->absent OK_RECREATE +create_database_objects_if_missing true->false OK_RECREATE +existing_pipeline_id absent->x OK_RECREATE +existing_pipeline_id absent->y OK_RECREATE +existing_pipeline_id x->absent OK_RECREATE +existing_pipeline_id x->y OK_RECREATE +existing_pipeline_id y->absent OK_RECREATE +existing_pipeline_id y->x OK_RECREATE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +new_pipeline_spec.budget_policy_id absent->x OK_RECREATE +new_pipeline_spec.budget_policy_id absent->y OK_RECREATE +new_pipeline_spec.budget_policy_id x->absent OK_RECREATE +new_pipeline_spec.budget_policy_id x->y OK_RECREATE +new_pipeline_spec.budget_policy_id y->absent OK_RECREATE +new_pipeline_spec.budget_policy_id y->x OK_RECREATE +new_pipeline_spec.pipeline_channel absent->x OK_RECREATE +new_pipeline_spec.pipeline_channel absent->y OK_RECREATE +new_pipeline_spec.pipeline_channel x->absent OK_RECREATE +new_pipeline_spec.pipeline_channel x->y OK_RECREATE +new_pipeline_spec.pipeline_channel y->absent OK_RECREATE +new_pipeline_spec.pipeline_channel y->x OK_RECREATE +new_pipeline_spec.storage_catalog absent->x OK_RECREATE +new_pipeline_spec.storage_catalog absent->y OK_RECREATE +new_pipeline_spec.storage_catalog x->absent OK_RECREATE +new_pipeline_spec.storage_catalog x->y OK_RECREATE +new_pipeline_spec.storage_catalog y->absent OK_RECREATE +new_pipeline_spec.storage_catalog y->x OK_RECREATE +new_pipeline_spec.storage_schema absent->x OK_RECREATE +new_pipeline_spec.storage_schema absent->y OK_RECREATE +new_pipeline_spec.storage_schema x->absent OK_RECREATE +new_pipeline_spec.storage_schema x->y OK_RECREATE +new_pipeline_spec.storage_schema y->absent OK_RECREATE +new_pipeline_spec.storage_schema y->x OK_RECREATE +postgres_database absent->x OK_RECREATE +postgres_database absent->y OK_RECREATE +postgres_database x->absent OK_RECREATE +postgres_database x->y OK_RECREATE +postgres_database y->absent OK_RECREATE +postgres_database y->x OK_RECREATE +scheduling_policy absent->x OK_RECREATE +scheduling_policy absent->y OK_RECREATE +scheduling_policy x->absent OK_RECREATE +scheduling_policy x->y OK_RECREATE +scheduling_policy y->absent OK_RECREATE +scheduling_policy y->x OK_RECREATE +source_table_full_name absent->x OK_RECREATE +source_table_full_name absent->y OK_RECREATE +source_table_full_name x->absent OK_RECREATE +source_table_full_name x->y OK_RECREATE +source_table_full_name y->absent OK_RECREATE +source_table_full_name y->x OK_RECREATE +synced_table_id x->y OK_RECREATE +synced_table_id y->x OK_RECREATE +timeseries_key absent->x OK_RECREATE +timeseries_key absent->y OK_RECREATE +timeseries_key x->absent OK_RECREATE +timeseries_key x->y OK_RECREATE +timeseries_key y->absent OK_RECREATE +timeseries_key y->x OK_RECREATE + +=== summary +OK_RECREATE 70 +SUPPRESSED 4 +NOT_OBSERVABLE 6 + +=== not covered: 8 fields under a slice or map +extra_columns[*].column_name +extra_columns[*].column_type +extra_columns[*].compute +extra_columns[*].maintenance +primary_key_columns[*] +type_overrides[*].column_name +type_overrides[*].pg_type +type_overrides[*].size diff --git a/bundle/direct/dresources_test/out.registered_models.fields.txt b/bundle/direct/dresources_test/out.registered_models.fields.txt new file mode 100644 index 00000000000..c1cf187fc2b --- /dev/null +++ b/bundle/direct/dresources_test/out.registered_models.fields.txt @@ -0,0 +1,102 @@ +=== registered_model.yml.tmpl +browse_only absent->false SUPPRESSED empty +browse_only absent->true OK +browse_only false->absent SUPPRESSED empty +browse_only false->true OK +browse_only true->absent OK +browse_only true->false OK +catalog_name absent->x OK_RECREATE +catalog_name absent->y OK_RECREATE +catalog_name x->absent OK_RECREATE +catalog_name x->y OK_RECREATE +catalog_name y->absent OK_RECREATE +catalog_name y->x OK_RECREATE +comment absent->x OK +comment absent->y OK +comment x->absent DRIFT comment +comment x->y OK +comment y->absent DRIFT comment +comment y->x OK +created_at 1->2 OK +created_at 1->absent OK +created_at 2->1 OK +created_at 2->absent OK +created_at absent->1 OK +created_at absent->2 OK +created_by absent->x OK +created_by absent->y OK +created_by x->absent OK +created_by x->y OK +created_by y->absent OK +created_by y->x OK +full_name absent->x DRIFT full_name +full_name absent->y DRIFT full_name +full_name x->absent OK +full_name x->y DRIFT full_name +full_name y->absent OK +full_name y->x DRIFT full_name +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +metastore_id absent->x DRIFT metastore_id +metastore_id absent->y DRIFT metastore_id +metastore_id x->absent OK +metastore_id x->y DRIFT metastore_id +metastore_id y->absent OK +metastore_id y->x DRIFT metastore_id +name absent->x OK_RECREATE +name absent->y OK_RECREATE +name x->absent OK_RECREATE +name x->y OK_RECREATE +name y->absent OK_RECREATE +name y->x OK_RECREATE +owner absent->x DRIFT owner +owner absent->y DRIFT owner +owner x->absent OK +owner x->y DRIFT owner +owner y->absent OK +owner y->x DRIFT owner +schema_name absent->x OK_RECREATE +schema_name absent->y OK_RECREATE +schema_name x->absent OK_RECREATE +schema_name x->y OK_RECREATE +schema_name y->absent OK_RECREATE +schema_name y->x OK_RECREATE +storage_location absent->x OK_RECREATE +storage_location absent->y OK_RECREATE +storage_location x->absent OK_RECREATE +storage_location x->y OK_RECREATE +storage_location y->absent OK_RECREATE +storage_location y->x OK_RECREATE +updated_at 1->2 OK +updated_at 1->absent OK +updated_at 2->1 OK +updated_at 2->absent OK +updated_at absent->1 OK +updated_at absent->2 OK +updated_by absent->x OK +updated_by absent->y OK +updated_by x->absent OK +updated_by x->y OK +updated_by y->absent OK +updated_by y->x OK + +=== summary +OK 38 +OK_RECREATE 24 +SUPPRESSED 2 +NOT_OBSERVABLE 6 +DRIFT 14 + +=== not covered: 8 fields under a slice or map +aliases[*].alias_name +aliases[*].catalog_name +aliases[*].id +aliases[*].model_name +aliases[*].schema_name +aliases[*].version_num +grants[*].principal +grants[*].privileges[*] diff --git a/bundle/direct/dresources_test/out.schemas.fields.txt b/bundle/direct/dresources_test/out.schemas.fields.txt new file mode 100644 index 00000000000..2776ca51f54 --- /dev/null +++ b/bundle/direct/dresources_test/out.schemas.fields.txt @@ -0,0 +1,80 @@ +=== schema.yml.tmpl +comment absent->x OK +comment absent->y OK +comment x->absent DRIFT comment +comment x->y OK +comment y->absent DRIFT comment +comment y->x OK +custom_max_retention_hours 1->2 OK +custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours +custom_max_retention_hours 2->1 OK +custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours +custom_max_retention_hours absent->1 OK +custom_max_retention_hours absent->2 OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK_RECREATE +name y->x OK_RECREATE +storage_root - SKIPPED needs an external location + +=== schema_empty_grants.yml.tmpl +comment absent->x OK +comment absent->y OK +comment x->absent DRIFT comment +comment x->y OK +comment y->absent DRIFT comment +comment y->x OK +custom_max_retention_hours 1->2 OK +custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours +custom_max_retention_hours 2->1 OK +custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours +custom_max_retention_hours absent->1 OK +custom_max_retention_hours absent->2 OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK_RECREATE +name y->x OK_RECREATE +storage_root - SKIPPED needs an external location + +=== schema_uppercase_name.yml.tmpl +comment absent->x OK +comment absent->y OK +comment x->absent DRIFT comment +comment x->y OK +comment y->absent DRIFT comment +comment y->x OK +custom_max_retention_hours 1->2 OK +custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours +custom_max_retention_hours 2->1 OK +custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours +custom_max_retention_hours absent->1 OK +custom_max_retention_hours absent->2 OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK_RECREATE +name y->x OK_RECREATE +storage_root - SKIPPED needs an external location + +=== summary +OK 24 +OK_RECREATE 6 +NOT_OBSERVABLE 18 +DRIFT 12 +SKIPPED 3 + +=== not covered: 3 fields under a slice or map +grants[*].principal +grants[*].privileges[*] +properties.* diff --git a/bundle/direct/dresources_test/out.secret_scopes.fields.txt b/bundle/direct/dresources_test/out.secret_scopes.fields.txt new file mode 100644 index 00000000000..3bf71901e54 --- /dev/null +++ b/bundle/direct/dresources_test/out.secret_scopes.fields.txt @@ -0,0 +1,49 @@ +=== secret_scope.yml.tmpl +backend_type absent->x OK_RECREATE +backend_type absent->y OK_RECREATE +backend_type x->absent OK_RECREATE +backend_type x->y OK_RECREATE +backend_type y->absent OK_RECREATE +backend_type y->x OK_RECREATE +keyvault_metadata.dns_name x->y OK_RECREATE +keyvault_metadata.dns_name y->x OK_RECREATE +keyvault_metadata.resource_id x->y OK_RECREATE +keyvault_metadata.resource_id y->x OK_RECREATE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK_RECREATE +name y->x OK_RECREATE + +=== secret_scope_default_backend_type.yml.tmpl +backend_type absent->x OK_RECREATE +backend_type absent->y OK_RECREATE +backend_type x->absent OK_RECREATE +backend_type x->y OK_RECREATE +backend_type y->absent OK_RECREATE +backend_type y->x OK_RECREATE +keyvault_metadata.dns_name x->y OK_RECREATE +keyvault_metadata.dns_name y->x OK_RECREATE +keyvault_metadata.resource_id x->y OK_RECREATE +keyvault_metadata.resource_id y->x OK_RECREATE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK_RECREATE +name y->x OK_RECREATE + +=== summary +OK_RECREATE 24 +NOT_OBSERVABLE 12 + +=== not covered: 4 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/dresources_test/out.sql_warehouses.fields.txt b/bundle/direct/dresources_test/out.sql_warehouses.fields.txt new file mode 100644 index 00000000000..e63cf93d130 --- /dev/null +++ b/bundle/direct/dresources_test/out.sql_warehouses.fields.txt @@ -0,0 +1,111 @@ +=== sql_warehouse.yml.tmpl +auto_stop_mins 1->2 OK +auto_stop_mins 1->absent OK +auto_stop_mins 2->1 OK +auto_stop_mins 2->absent OK +auto_stop_mins absent->1 OK +auto_stop_mins absent->2 OK +channel.dbsql_version absent->x OK +channel.dbsql_version absent->y OK +channel.dbsql_version x->absent OK +channel.dbsql_version x->y OK +channel.dbsql_version y->absent OK +channel.dbsql_version y->x OK +channel.name absent->x OK +channel.name absent->y OK +channel.name x->absent OK +channel.name x->y OK +channel.name y->absent OK +channel.name y->x OK +cluster_size absent->x OK +cluster_size absent->y OK +cluster_size x->absent OK +cluster_size x->y OK +cluster_size y->absent OK +cluster_size y->x OK +creator_name absent->x OK +creator_name absent->y OK +creator_name x->absent OK +creator_name x->y OK +creator_name y->absent OK +creator_name y->x OK +enable_photon absent->false SUPPRESSED empty +enable_photon absent->true OK +enable_photon false->absent SUPPRESSED empty +enable_photon false->true OK +enable_photon true->absent OK +enable_photon true->false OK +enable_serverless_compute absent->false SUPPRESSED empty +enable_serverless_compute absent->true OK +enable_serverless_compute false->absent SUPPRESSED empty +enable_serverless_compute false->true OK +enable_serverless_compute true->absent OK +enable_serverless_compute true->false OK +instance_profile_arn absent->x OK +instance_profile_arn absent->y OK +instance_profile_arn x->absent OK +instance_profile_arn x->y OK +instance_profile_arn y->absent OK +instance_profile_arn y->x OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +lifecycle.started absent->false OK +lifecycle.started absent->true OK +lifecycle.started false->absent OK +lifecycle.started false->true OK +lifecycle.started true->absent OK +lifecycle.started true->false OK +max_num_clusters 1->2 OK +max_num_clusters 1->absent OK +max_num_clusters 2->1 OK +max_num_clusters 2->absent OK +max_num_clusters absent->1 OK +max_num_clusters absent->2 OK +min_num_clusters 1->2 OK +min_num_clusters 1->absent OK +min_num_clusters 2->1 OK +min_num_clusters 2->absent OK +min_num_clusters absent->1 OK +min_num_clusters absent->2 OK +name absent->x OK +name absent->y OK +name x->absent DRIFT name +name x->y OK +name y->absent DRIFT name +name y->x OK +spot_instance_policy absent->x OK +spot_instance_policy absent->y OK +spot_instance_policy x->absent OK +spot_instance_policy x->y OK +spot_instance_policy y->absent OK +spot_instance_policy y->x OK +warehouse_type absent->x OK +warehouse_type absent->y OK +warehouse_type x->absent OK +warehouse_type x->y OK +warehouse_type y->absent OK +warehouse_type y->x OK + +=== summary +OK 78 +SUPPRESSED 4 +NOT_OBSERVABLE 12 +DRIFT 2 + +=== not covered: 6 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +tags.custom_tags[*].key +tags.custom_tags[*].value diff --git a/bundle/direct/dresources_test/out.volumes.fields.txt b/bundle/direct/dresources_test/out.volumes.fields.txt new file mode 100644 index 00000000000..b0f558666ca --- /dev/null +++ b/bundle/direct/dresources_test/out.volumes.fields.txt @@ -0,0 +1,52 @@ +=== volume.yml.tmpl +catalog_name x->y OK_RECREATE +catalog_name y->x OK_RECREATE +comment absent->x OK +comment absent->y OK +comment x->absent DRIFT comment +comment x->y OK +comment y->absent DRIFT comment +comment y->x OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK +name y->x OK +schema_name x->y OK_RECREATE +schema_name y->x OK_RECREATE +storage_location - SKIPPED needs an external location + +=== volume_external.yml.tmpl +catalog_name x->y OK_RECREATE +catalog_name y->x OK_RECREATE +comment absent->x OK +comment absent->y OK +comment x->absent DRIFT comment +comment x->y OK +comment y->absent DRIFT comment +comment y->x OK +lifecycle.prevent_destroy absent->false NOT_OBSERVABLE +lifecycle.prevent_destroy absent->true NOT_OBSERVABLE +lifecycle.prevent_destroy false->absent NOT_OBSERVABLE +lifecycle.prevent_destroy false->true NOT_OBSERVABLE +lifecycle.prevent_destroy true->absent NOT_OBSERVABLE +lifecycle.prevent_destroy true->false NOT_OBSERVABLE +name x->y OK +name y->x OK +schema_name x->y OK_RECREATE +schema_name y->x OK_RECREATE +storage_location - SKIPPED needs an external location + +=== summary +OK 12 +OK_RECREATE 8 +NOT_OBSERVABLE 12 +DRIFT 4 +SKIPPED 2 + +=== not covered: 2 fields under a slice or map +grants[*].principal +grants[*].privileges[*] diff --git a/bundle/direct/dresources_test/report_test.go b/bundle/direct/dresources_test/report_test.go new file mode 100644 index 00000000000..ec9a1cb0d18 --- /dev/null +++ b/bundle/direct/dresources_test/report_test.go @@ -0,0 +1,228 @@ +package dresources_test + +import ( + "cmp" + "errors" + "fmt" + "io/fs" + "os" + "slices" + "strings" + + "github.com/databricks/cli/internal/testutil" + "github.com/databricks/cli/libs/testdiff" +) + +// verdict classifies what happened to one field transition. The suite never fails a +// subtest on a bad verdict: the point is to catalog which fields the engine handles, +// so every outcome is recorded and the golden file is the report. +type verdict string + +const ( + // The engine planned a change, applied it, and the next plan was clean. + verdictOK verdict = "OK" + // Same, but the engine replaced the resource instead of updating it. + verdictRecreate verdict = "OK_RECREATE" + + // The planner saw the field change and deliberately dropped it. The detail carries + // the engine's own reason, which is the interesting part. + verdictSuppressed verdict = "SUPPRESSED" + // The plan has no entry for the field at all: the two values are indistinguishable + // in the state the engine sends, so there is nothing to observe. An unset bool and + // an explicit false are the common case. + verdictNotObservable verdict = "NOT_OBSERVABLE" + // The config changed, the field diff exists, but the plan produced no action at all. + verdictNoPlan verdict = "NO_PLAN" + // The apply succeeded but a later plan still proposes the same change to this + // field, so deploying never converges. + verdictDrift verdict = "DRIFT" + // The field itself converged, but some other node of the resource is still drifted. + // Sub-resources are stripped before planning, so nothing should reach this: it is a + // guard against silently blaming a field for drift that is not its own. + verdictDriftChild verdict = "DRIFT_CHILD" + + // The backend rejected the value. Usually the value library needs a valid + // value for this field rather than the generic per-kind default. + verdictBackendError verdict = "BACKEND_ERROR" + // Apply failed for a reason that is not an API rejection. + verdictDeployError verdict = "DEPLOY_ERROR" + // Planning itself failed. + verdictPlanError verdict = "PLAN_ERROR" + + // The value could not be written into the config at all: the parent object is + // absent from this base config, or the type does not accept the value. + verdictUnsettable verdict = "UNSETTABLE" + // Left out on purpose by the resource's value library, with a reason. + verdictSkipped verdict = "SKIPPED" + // The transition's starting value could not be deployed, so the transition + // itself was never observed. + verdictBaseError verdict = "BASE_ERROR" +) + +// leavesResourceUsable reports whether the deployed resource is still in a known-good +// state after this outcome. When it is not, the next transition must start from a fresh +// resource, or one real failure cascades into a run of unrelated ones. +func (v verdict) leavesResourceUsable() bool { + switch v { + case verdictOK, verdictRecreate, verdictSuppressed, verdictNotObservable, verdictUnsettable, verdictSkipped: + return true + case verdictDrift, verdictDriftChild: + // The resource exists and still matches what the engine last sent; only the one + // field did not stick. The next transition re-deploys the field anyway. + return true + default: + return false + } +} + +// result is one line of the report. +type result struct { + config string + field string + transition string + verdict verdict + detail string +} + +// report accumulates results for one resource type. +type report struct { + resourceType string + results []result + wildcard []string +} + +func (r *report) add(res result) { r.results = append(r.results, res) } + +// render produces the golden body: one line per transition, grouped by config and +// field, plus a trailing summary of what was not covered. +func (r *report) render() string { + var sb strings.Builder + + slices.SortStableFunc(r.results, func(a, b result) int { + return cmp.Or( + strings.Compare(a.config, b.config), + strings.Compare(a.field, b.field), + strings.Compare(a.transition, b.transition), + ) + }) + + counts := map[verdict]int{} + config := "" + for _, res := range r.results { + if res.config != config { + if config != "" { + sb.WriteString("\n") + } + fmt.Fprintf(&sb, "=== %s\n", res.config) + config = res.config + } + line := fmt.Sprintf("%-44s %-18s %s", res.field, res.transition, res.verdict) + if res.detail != "" { + line += " " + res.detail + } + sb.WriteString(strings.TrimRight(line, " ") + "\n") + counts[res.verdict]++ + } + + sb.WriteString("\n=== summary\n") + for _, v := range []verdict{ + verdictOK, verdictRecreate, verdictSuppressed, verdictNotObservable, verdictNoPlan, + verdictDrift, verdictDriftChild, + verdictBackendError, verdictDeployError, verdictPlanError, + verdictUnsettable, verdictSkipped, verdictBaseError, + } { + if counts[v] > 0 { + fmt.Fprintf(&sb, "%-14s %d\n", v, counts[v]) + } + } + + if len(r.wildcard) > 0 { + slices.Sort(r.wildcard) + fmt.Fprintf(&sb, "\n=== not covered: %d fields under a slice or map\n", len(r.wildcard)) + for _, p := range r.wildcard { + fmt.Fprintf(&sb, "%s\n", p) + } + } + + return sb.String() +} + +// write compares the report against its golden file, or rewrites it under -update. +// Cloud runs go to a separate, uncommitted file: which values a real backend accepts +// depends on the workspace, so those results are for reading, not for diffing. +func (r *report) write(t testutil.TestingT) { + name := "out." + r.resourceType + ".fields.txt" + body := r.render() + + if isCloud() { + name = "out." + r.resourceType + ".fields." + cloudName() + ".txt" + if err := os.WriteFile(name, []byte(body), 0o644); err != nil { + t.Errorf("writing %s: %s", name, err) + } + return + } + + if testdiff.OverwriteMode { + if err := os.WriteFile(name, []byte(body), 0o644); err != nil { + t.Errorf("writing %s: %s", name, err) + } + return + } + + expected, err := os.ReadFile(name) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + t.Errorf("reading %s: %s", name, err) + return + } + testdiff.AssertEqualTexts(t, name, name, testdiff.NormalizeNewlines(string(expected)), body) +} + +// writeCorpusReport records which configs the suite drives and why each of the others +// is left out, so the catalog accounts for the whole corpus rather than just the part +// it happens to cover. +func writeCorpusReport(t testutil.TestingT, usable []testConfig, skipped map[string]string) { + var sb strings.Builder + sb.WriteString("=== covered\n") + for _, c := range usable { + fmt.Fprintf(&sb, "%-48s %s\n", c.name, c.resourceType) + } + + names := make([]string, 0, len(skipped)) + for name := range skipped { + names = append(names, name) + } + slices.Sort(names) + fmt.Fprintf(&sb, "\n=== not covered\n") + for _, name := range names { + fmt.Fprintf(&sb, "%-48s %s\n", name, skipped[name]) + } + + const name = "out.configs.txt" + body := sb.String() + if isCloud() { + return + } + if testdiff.OverwriteMode { + if err := os.WriteFile(name, []byte(body), 0o644); err != nil { + t.Errorf("writing %s: %s", name, err) + } + return + } + expected, err := os.ReadFile(name) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + t.Errorf("reading %s: %s", name, err) + return + } + testdiff.AssertEqualTexts(t, name, name, testdiff.NormalizeNewlines(string(expected)), body) +} + +func cloudName() string { + switch env := os.Getenv("CLOUD_ENV"); env { + case "ucws": + return "aws" + case "gcp-ucws": + return "gcp" + default: + return env + } +} diff --git a/bundle/direct/dresources_test/values_test.go b/bundle/direct/dresources_test/values_test.go new file mode 100644 index 00000000000..89c25b897ea --- /dev/null +++ b/bundle/direct/dresources_test/values_test.go @@ -0,0 +1,233 @@ +package dresources_test + +import ( + "bytes" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "reflect" + "slices" + "strings" + + "github.com/databricks/cli/bundle/internal/validation/generated" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/yamlloader" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structwalk" +) + +// absent is the "field not present in the config" value. Every transition is a move +// between two values drawn from a field's set, and add/remove are just the moves +// with absent on one side. +var absent = dyn.InvalidValue + +// fieldValues is the per-resource-type value library, e.g. fields/schemas.yml. +// +// A field with no entry falls back to defaultValues for its Go kind. Fields the backend +// constrains (enums, ids, cross-referenced names) need an explicit entry, otherwise +// every value is rejected and the field reports only BACKEND_ERROR. +type fieldValues struct { + // cloud enables this resource type in cloud runs. Off by default: a cloud run pays + // real API latency per transition, so a type opts in once someone has checked that + // it completes in reasonable time. + cloud bool + + // skip lists field paths to leave out entirely, with a reason. + skip map[string]string + + // fields maps a field path to the values to try. + fields map[string][]dyn.Value +} + +// loadFieldValues reads fields/.yml. Parsing goes through the repo's own +// yamlloader rather than a yaml package, so the values arrive as dyn.Value -- the same +// representation the bundle config uses, which is what they are written into. +func loadFieldValues(resourceType string) (*fieldValues, error) { + fv := &fieldValues{cloud: false, skip: map[string]string{}, fields: map[string][]dyn.Value{}} + + path := filepath.Join("fields", resourceType+".yml") + data, err := os.ReadFile(path) + if errors.Is(err, fs.ErrNotExist) { + return fv, nil + } + if err != nil { + return nil, err + } + + root, err := yamlloader.LoadYAML(path, bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + + if v, err := dyn.Get(root, "cloud"); err == nil { + fv.cloud, _ = v.AsBool() + } + if v, err := dyn.Get(root, "skip"); err == nil { + m, _ := v.AsMap() + for _, pair := range m.Pairs() { + key, _ := pair.Key.AsString() + reason, _ := pair.Value.AsString() + fv.skip[key] = reason + } + } + if v, err := dyn.Get(root, "fields"); err == nil { + m, _ := v.AsMap() + for _, pair := range m.Pairs() { + key, _ := pair.Key.AsString() + values, ok := pair.Value.AsSequence() + if !ok { + return nil, fmt.Errorf("%s: fields.%s must be a list", path, key) + } + fv.fields[key] = values + } + } + + return fv, nil +} + +// defaultValues per Go kind. Two values per kind is enough to observe a +// value->value transition on top of add and remove; more would multiply the matrix +// without testing a different code path. +// +// isContainer reports whether a kind has fields or elements to descend into. +func isContainer(kind reflect.Kind) bool { + switch kind { + case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map: + return true + default: + return false + } +} + +//nolint:exhaustive // the default branch covers every kind without a generic value +func defaultValues(kind reflect.Kind) []dyn.Value { + switch kind { + case reflect.Bool: + return []dyn.Value{dyn.V(false), dyn.V(true)} + case reflect.String: + return []dyn.Value{dyn.V("x"), dyn.V("y")} + case reflect.Int, reflect.Int32, reflect.Int64: + return []dyn.Value{dyn.V(1), dyn.V(2)} + case reflect.Float32, reflect.Float64: + return []dyn.Value{dyn.V(1.0), dyn.V(2.0)} + default: + // The remaining kinds (interface, unsigned, complex, ...) have no meaningful + // generic value; such a field needs an entry in the value library. + return nil + } +} + +// field is one testable leaf of the resource's input struct. +type field struct { + path string // structpath/dyn path, e.g. "comment" or "email_notifications.on_failure" + kind reflect.Kind + values []dyn.Value + + // required fields do not get an "absent" transition: a config missing one is + // rejected by bundle validate, so removing it is not something a user can deploy. + required bool +} + +// transition is one move of a field from one value to another. +type transition struct { + from, to dyn.Value +} + +func (t transition) label() string { + return valueLabel(t.from) + "->" + valueLabel(t.to) +} + +// transitions returns every ordered pair of the field's values. absent is one of them, +// so adding and removing the field are the pairs with absent on one side. +func (f field) transitions() []transition { + values := f.values + if !f.required { + values = append([]dyn.Value{absent}, values...) + } + var out []transition + for _, from := range values { + for _, to := range values { + if valueLabel(from) != valueLabel(to) { + out = append(out, transition{from: from, to: to}) + } + } + } + return out +} + +// requiredFields returns the field names the bundle schema marks required directly +// under the given path inside a resource, e.g. "" for the resource itself or +// "evaluation" for a nested object. The data is generated from the config structs +// (bundle/internal/validation), not from the engine's resources.yml. +func requiredFields(resourceType, parent string) []string { + key := "resources." + resourceType + ".*" + if parent != "" { + key += "." + parent + } + return generated.RequiredFields[key] +} + +func isRequired(resourceType, path string) bool { + parent, name := "", path + if i := strings.LastIndex(path, "."); i >= 0 { + parent, name = path[:i], path[i+1:] + } + return slices.Contains(requiredFields(resourceType, parent), name) +} + +// enumerateFields walks the resource's input config type the way cmd/bundle/debug +// refschema does, and pairs each scalar leaf with the values to try. +// +// Paths containing a wildcard are left out: they address a slice element or map entry +// that only exists if the base config happens to declare one, and picking an index +// here would silently test nothing. They are reported so the gap stays visible. +func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues) (fields []field, wildcard []string) { + _ = structwalk.WalkType(inputType, func(p *structpath.PatternNode, typ reflect.Type, sf *reflect.StructField) bool { + if p.IsRoot() { + return true + } + for typ.Kind() == reflect.Pointer { + typ = typ.Elem() + } + if isContainer(typ.Kind()) { + // An intermediate node: descend to reach its leaves. + return true + } + + path := p.String() + if strings.Contains(path, "*") { + wildcard = append(wildcard, path) + return false + } + if sf != nil && isNotUserSettable(sf) { + return false + } + + values := fv.fields[path] + if values == nil { + values = defaultValues(typ.Kind()) + } + if values == nil { + return false + } + fields = append(fields, field{ + path: path, + kind: typ.Kind(), + values: values, + required: isRequired(resourceType, path), + }) + return false + }) + return fields, wildcard +} + +// isNotUserSettable reports whether the bundle marks a field as something the user +// never writes: an output the CLI fills in (id, url) or an internal bookkeeping field. +func isNotUserSettable(sf *reflect.StructField) bool { + tag := sf.Tag.Get("bundle") + return strings.Contains(tag, "readonly") || + strings.Contains(tag, "internal") || + sf.Tag.Get("json") == "-" +} From 81061dedcf9ede8a8bf8298985e5ccb9a9a3cfbe Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 11:31:45 +0200 Subject: [PATCH 004/118] acc: show that clearing a schema comment never converges Every field of catalog.UpdateSchema is omitempty and DoUpdate forwards only the config's own ForceSendFields, so removing the comment sends an empty PATCH body and UC keeps the old value. The deploy reports success and every later plan re-proposes the same update. Co-authored-by: Isaac --- .../drift/clear_comment/databricks.yml | 9 +++ .../schemas/drift/clear_comment/out.test.toml | 2 + .../schemas/drift/clear_comment/output.txt | 63 +++++++++++++++++++ .../schemas/drift/clear_comment/script | 28 +++++++++ .../schemas/drift/clear_comment/test.toml | 5 ++ 5 files changed, 107 insertions(+) create mode 100644 acceptance/bundle/resources/schemas/drift/clear_comment/databricks.yml create mode 100644 acceptance/bundle/resources/schemas/drift/clear_comment/out.test.toml create mode 100644 acceptance/bundle/resources/schemas/drift/clear_comment/output.txt create mode 100644 acceptance/bundle/resources/schemas/drift/clear_comment/script create mode 100644 acceptance/bundle/resources/schemas/drift/clear_comment/test.toml diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/databricks.yml b/acceptance/bundle/resources/schemas/drift/clear_comment/databricks.yml new file mode 100644 index 00000000000..6ae252f046b --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/clear_comment/databricks.yml @@ -0,0 +1,9 @@ +bundle: + name: test-bundle + +resources: + schemas: + schema1: + name: schema_clear_comment + catalog_name: main + comment: base comment diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/out.test.toml b/acceptance/bundle/resources/schemas/drift/clear_comment/out.test.toml new file mode 100644 index 00000000000..c502b28221b --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/clear_comment/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/output.txt b/acceptance/bundle/resources/schemas/drift/clear_comment/output.txt new file mode 100644 index 00000000000..0454197b637 --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/clear_comment/output.txt @@ -0,0 +1,63 @@ + +=== Initial deployment +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Created schemas.schema1 +Files: 5 uploaded, 0 deleted +Resources: 1 created, 0 changed, 0 deleted, 0 unchanged + +=== Remove the comment from the config +>>> update_file.py databricks.yml comment: base comment + +=== Plan proposes clearing it +>>> [CLI] bundle plan +update schemas.schema1 + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle plan --output json +{ + "comment": { + "action": "update", + "old": "base comment", + "remote": "base comment" + } +} + +=== Deploy reports success +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Updated schemas.schema1 +Files: 3 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged + +=== Still drifting: the same update is proposed again +>>> [CLI] bundle plan +update schemas.schema1 + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle plan --output json +{ + "comment": { + "action": "update", + "remote": "base comment" + } +} + +=== The update PATCH carried an empty body, which is why nothing changed +>>> print_requests.py //unity --sort +{ + "method": "PATCH", + "path": "/api/2.1/unity-catalog/schemas/main.schema_clear_comment", + "body": {} +} +{ + "method": "POST", + "path": "/api/2.1/unity-catalog/schemas", + "body": { + "catalog_name": "main", + "comment": "base comment", + "name": "schema_clear_comment" + } +} diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/script b/acceptance/bundle/resources/schemas/drift/clear_comment/script new file mode 100644 index 00000000000..32f9893dd89 --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/clear_comment/script @@ -0,0 +1,28 @@ +# BUG: removing a schema field from the config never clears it remotely, so the deploy +# never converges. Every field of catalog.UpdateSchema is omitempty and DoUpdate carries +# only the config's own ForceSendFields (bundle/direct/dresources/schema.go), so an +# absent comment is dropped from the PATCH body and UC keeps the old value. The PATCH +# that gets sent is literally empty, and the plan re-proposes the same update forever. +# +# This test records the broken behaviour. Once it is fixed the "still drifting" step +# below reports no changes and this golden has to be updated -- that is the point. + +title "Initial deployment" +trace $CLI bundle deploy + +title "Remove the comment from the config" +trace update_file.py databricks.yml " comment: base comment" "" + +title "Plan proposes clearing it" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete" +trace $CLI bundle plan --output json | jq '.plan["resources.schemas.schema1"].changes' + +title "Deploy reports success" +trace $CLI bundle deploy + +title "Still drifting: the same update is proposed again" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete" +trace $CLI bundle plan --output json | jq '.plan["resources.schemas.schema1"].changes' + +title "The update PATCH carried an empty body, which is why nothing changed" +trace print_requests.py //unity --sort diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/test.toml b/acceptance/bundle/resources/schemas/drift/clear_comment/test.toml new file mode 100644 index 00000000000..76fba7271dc --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/clear_comment/test.toml @@ -0,0 +1,5 @@ +Cloud = true + +# Terraform issues a spurious PATCH for enable_predictive_optimization on every +# deploy, which would mask the drift this test is about. +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] From d5f6195afc59584023e350c9be576d02bb2e05ab Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 11:32:03 +0200 Subject: [PATCH 005/118] acc: invariant field mutations (superseded by the Go field catalog) The earlier approach: "# ACTION:" annotations in the invariant configs drive apply_update and apply_remote_update, which plan, apply and re-plan one field at a time through the full CLI. apply_update is now covered far more thoroughly by bundle/direct/dresources_test. apply_remote_update is not -- it drifts the remote by restoring a state snapshot, which the Go catalog does not do yet -- so this is kept for that half. Drop the commit if the remote case lands there instead. Co-authored-by: Isaac --- .agents/rules/testing.md | 3 + acceptance/bin/apply_mutation.py | 179 ++++++++++++++++++ acceptance/bin/state_snapshot.py | 81 ++++++++ acceptance/bin/verify_no_drift.py | 102 ++++++++-- acceptance/bundle/invariant/README.md | 39 ++++ .../apply_remote_update/out.test.toml | 8 + .../invariant/apply_remote_update/output.txt | 1 + .../invariant/apply_remote_update/script | 34 ++++ .../invariant/apply_remote_update/test.toml | 14 ++ .../invariant/apply_update/out.test.toml | 8 + .../bundle/invariant/apply_update/output.txt | 1 + .../bundle/invariant/apply_update/script | 28 +++ .../bundle/invariant/apply_update/test.toml | 14 ++ .../bundle/invariant/configs/app.yml.tmpl | 5 + .../bundle/invariant/configs/job.yml.tmpl | 3 + .../configs/postgres_endpoint.yml.tmpl | 8 + .../bundle/invariant/configs/schema.yml.tmpl | 7 + acceptance/bundle/invariant/script.prepare | 69 +++++++ 18 files changed, 591 insertions(+), 13 deletions(-) create mode 100644 acceptance/bin/apply_mutation.py create mode 100644 acceptance/bin/state_snapshot.py create mode 100644 acceptance/bundle/invariant/apply_remote_update/out.test.toml create mode 100644 acceptance/bundle/invariant/apply_remote_update/output.txt create mode 100644 acceptance/bundle/invariant/apply_remote_update/script create mode 100644 acceptance/bundle/invariant/apply_remote_update/test.toml create mode 100644 acceptance/bundle/invariant/apply_update/out.test.toml create mode 100644 acceptance/bundle/invariant/apply_update/output.txt create mode 100644 acceptance/bundle/invariant/apply_update/script create mode 100644 acceptance/bundle/invariant/apply_update/test.toml diff --git a/.agents/rules/testing.md b/.agents/rules/testing.md index 1ad6ffc6704..96faad1677f 100644 --- a/.agents/rules/testing.md +++ b/.agents/rules/testing.md @@ -184,7 +184,10 @@ Available on `PATH` during test execution (from `acceptance/bin/`): - `find.py REGEX [--expect N]`: find files matching regex in current directory. `--expect N` asserts an exact count. - `diff.py DIR1 DIR2` or `diff.py FILE1 FILE2`: recursive diff with test replacements applied. - `print_state.py [-t TARGET] [--backup]`: print deployment state (terraform or direct). +- `state_snapshot.py save|restore [-t TARGET]`: snapshot the deployment state file and restore it later, so a change the CLI made itself looks like out-of-band remote drift. Restore re-stamps the serial past the current one, otherwise `statemgmt.readStates` prefers the newer copy pushed to the workspace and the restore is a no-op. +- `apply_mutation.py FILE [NAME|--list]`: list or apply the `# ACTION:` field mutations declared in an invariant config. See `acceptance/bundle/invariant/README.md`. - `edit_resource.py TYPE ID < script.py`: fetch resource by ID, execute Python on it (resource in `r`), then update it. TYPE is `jobs` or `pipelines`. +- `verify_no_drift.py PLAN.json [--expect-change FIELD --shape local|remote]`: assert every action in a JSON plan is `skip`, or that one field has a planned change with the given shape (`local`: config moved away from state; `remote`: only the remote moved). - `gron.py`: flatten JSON into greppable discrete assignments (simpler than `jq` for searching JSON). - `jq` is also available for JSON processing. diff --git a/acceptance/bin/apply_mutation.py b/acceptance/bin/apply_mutation.py new file mode 100644 index 00000000000..6f37e3eb6c1 --- /dev/null +++ b/acceptance/bin/apply_mutation.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +""" +Apply a single field mutation to a rendered databricks.yml. + +Mutations are declared in the config as trailing comments, so they are inert YAML +and do not affect the invariant targets that read the same config without mutating +it: + + description: hello # ACTION: REMOVE + #user_api_scopes: [iam.access] # ACTION: UNCOMMENT + #compute_size: LARGE # ACTION: SET XLARGE + +Each mutation is named ":", e.g. "description:remove". + +REMOVE and UNCOMMENT only work for a field the resource's update path can clear: +both targets return the remote to the base config, and for those two actions that +means clearing the field. A resource whose update request drops empty fields (an +omitempty payload without ForceSendFields, e.g. UpdateSchema) never converges, so +use SET there. + +EXPECT names the plan action the mutation should produce, defaulting to +"non-skip". A field the engine deliberately ignores expects "skip" instead, and +which of the two targets sees that depends on why it is ignored -- an +ignore_local_changes field is skipped when the config changes, an +ignore_remote_changes field when the remote drifts. So the expectation can be set +for both targets at once or for one of them: + + EXPECT: skip both targets + EXPECT_UPDATE: skip apply_update only + EXPECT_REMOTE_UPDATE: skip apply_remote_update only + +Usage: + apply_mutation.py FILE --list [--expect-key KEY] print " " per mutation + apply_mutation.py FILE NAME apply mutation NAME in place +""" + +import argparse +import re +import sys + +MARKER = "# ACTION:" +DEFAULT_EXPECT = "non-skip" +GENERIC_EXPECT_KEY = "EXPECT" +EXPECT_KEYS = (GENERIC_EXPECT_KEY, "EXPECT_UPDATE", "EXPECT_REMOTE_UPDATE") + +# Splits "SET XLARGE EXPECT_UPDATE: skip" into the action part and the key/value pairs. +EXPECT_RE = re.compile(r"\b(" + "|".join(EXPECT_KEYS) + r"):\s*(\S+)") + +REMOVE = "REMOVE" +UNCOMMENT = "UNCOMMENT" +SET = "SET" +ACTIONS = (REMOVE, UNCOMMENT, SET) + + +class Mutation: + def __init__(self, lineno, indent, body, key, action, arg, expects): + self.lineno = lineno + self.indent = indent + self.body = body + self.key = key + self.action = action + self.arg = arg + self.expects = expects + + @property + def name(self): + return f"{self.key}:{self.action.lower()}" + + def expect(self, expect_key): + for candidate in (expect_key, GENERIC_EXPECT_KEY): + if candidate in self.expects: + return self.expects[candidate] + return DEFAULT_EXPECT + + def render(self): + """Return the replacement line, or None when the line is dropped.""" + if self.action == REMOVE: + return None + if self.action == UNCOMMENT: + return self.indent + self.body + return f"{self.indent}{self.key}: {self.arg}" + + +def parse_line(lineno, line): + if MARKER not in line: + return None + + head, _, spec = line.partition(MARKER) + + expects = dict(EXPECT_RE.findall(spec)) + spec = EXPECT_RE.sub("", spec) + + action, _, arg = spec.strip().partition(" ") + action = action.upper() + arg = arg.strip() + + indent = head[: len(head) - len(head.lstrip())] + body = head.strip() + commented = body.startswith("#") + if commented: + body = body[1:].lstrip() + + key = body.partition(":")[0].strip() + + def fail(message): + sys.exit(f"{lineno}: {message}: {line.strip()!r}") + + if action not in ACTIONS: + fail(f"unknown action {action!r}, expected one of {', '.join(ACTIONS)}") + if not key: + fail("cannot determine field name") + if action == REMOVE and commented: + fail("REMOVE on a commented-out line is a no-op; use SET or drop the annotation") + if action == UNCOMMENT and not commented: + fail("UNCOMMENT on an active line is a no-op; use REMOVE or comment the line out") + if action == SET and not arg: + fail("SET requires a value") + + return Mutation(lineno, indent, body, key, action, arg, expects) + + +def parse(filename): + with open(filename) as fobj: + lines = fobj.read().splitlines(keepends=True) + + mutations = [] + seen = {} + for lineno, line in enumerate(lines, start=1): + mutation = parse_line(lineno, line) + if mutation is None: + continue + if mutation.name in seen: + sys.exit(f"{lineno}: duplicate mutation {mutation.name!r}, first seen on line {seen[mutation.name]}") + seen[mutation.name] = lineno + mutations.append(mutation) + + return lines, mutations + + +def apply(filename, lines, mutation): + replacement = mutation.render() + index = mutation.lineno - 1 + if replacement is None: + del lines[index] + else: + lines[index] = replacement + "\n" + with open(filename, "w") as fobj: + fobj.write("".join(lines)) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("filename") + parser.add_argument("name", nargs="?") + parser.add_argument("--list", action="store_true") + parser.add_argument("--expect-key", default=GENERIC_EXPECT_KEY, choices=EXPECT_KEYS) + args = parser.parse_args() + + lines, mutations = parse(args.filename) + + if args.list: + for mutation in mutations: + print(mutation.name, mutation.expect(args.expect_key)) + return + + if not args.name: + parser.error("either NAME or --list is required") + + for mutation in mutations: + if mutation.name == args.name: + apply(args.filename, lines, mutation) + return + + available = ", ".join(m.name for m in mutations) or "(none)" + sys.exit(f"mutation {args.name!r} not found in {args.filename}; available: {available}") + + +if __name__ == "__main__": + main() diff --git a/acceptance/bin/state_snapshot.py b/acceptance/bin/state_snapshot.py new file mode 100644 index 00000000000..18c66a2e491 --- /dev/null +++ b/acceptance/bin/state_snapshot.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +""" +Snapshot and restore the deployment state file. + +Restoring a snapshot makes an update the CLI performed itself look like an +out-of-band remote change: state says one thing, the remote says another. + +The engine is not hardcoded here -- print_state.get_state_file() resolves the +target directory and picks resources.json (direct) or terraform.tfstate +(terraform), so both engines go through the same helper. +""" + +import argparse +import json +import os +import shutil +import sys + +sys.path.insert(0, os.path.dirname(__file__)) +import print_state + +# Written during a deploy and merged into the state file at the end of it. A WAL +# left over from a crashed deploy would be replayed on top of a restored +# snapshot, undoing the restore, so drop it alongside. +WAL_SUFFIX = ".wal" + + +def snapshot_path(state_path): + return state_path + ".snapshot" + + +def save(target): + state_path = print_state.get_state_file(target, backup=False) + if not os.path.exists(state_path): + sys.exit(f"cannot snapshot {state_path}: file does not exist") + shutil.copyfile(state_path, snapshot_path(state_path)) + + +def read_serial(path): + with open(path) as fobj: + return json.load(fobj)["serial"] + + +def restore(target): + state_path = print_state.get_state_file(target, backup=False) + snapshot = snapshot_path(state_path) + if not os.path.exists(snapshot): + sys.exit(f"cannot restore {state_path}: {snapshot} does not exist") + + with open(snapshot) as fobj: + data = json.load(fobj) + + # The deploy that ran since the snapshot bumped the serial and pushed the state to + # the workspace. statemgmt.readStates picks whichever copy has the highest serial, so + # a snapshot restored at its original serial loses to that remote copy and the + # restore silently does nothing. Re-stamping it past the current serial makes the + # restored state authoritative again. + data["serial"] = read_serial(state_path) + 1 + + with open(state_path, "w") as fobj: + json.dump(data, fobj) + + wal = state_path + WAL_SUFFIX + if os.path.exists(wal): + os.remove(wal) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("action", choices=["save", "restore"]) + parser.add_argument("-t", "--target") + args = parser.parse_args() + + if args.action == "save": + save(args.target) + else: + restore(args.target) + + +if __name__ == "__main__": + main() diff --git a/acceptance/bin/verify_no_drift.py b/acceptance/bin/verify_no_drift.py index 9b272c1ce79..dba22e39d6d 100755 --- a/acceptance/bin/verify_no_drift.py +++ b/acceptance/bin/verify_no_drift.py @@ -1,37 +1,113 @@ #!/usr/bin/env python3 """ -Check that all actions in plan are "skip". +Check the actions in a JSON plan. + +By default every action must be "skip" -- i.e. a deploy left no drift behind. + +--expect-change instead requires a planned change for one field, with the shape the +caller created. "local" means the config moved away from state and remote +(old != new); "remote" means only the remote moved (old == new, remote differs). +Checking the shape and not just the action matters: reverting a config produces a +local change that looks like drift detection from the action alone. """ +import argparse import json import sys +SKIP = "skip" +LOCAL = "local" +REMOTE = "remote" -def check_plan(path): - with open(path) as fobj: - raw = fobj.read() - changes_detected = 0 +class Missing: + """Stands in for an omitted old/new/remote value; the plan omits empty ones.""" + + def __repr__(self): + return "absent" + +MISSING = Missing() + + +def load_plan(path): + with open(path) as fobj: + raw = fobj.read() try: - data = json.loads(raw) - for key, value in data["plan"].items(): - action = value.get("action") - if action != "skip": - print(f"Unexpected {action=} for {key}") - changes_detected += 1 + return raw, json.loads(raw)["plan"] except Exception: print(raw, flush=True) raise + +def fail(raw, message): + print(message) + print(raw, flush=True) + sys.exit(10) + + +def check_no_drift(path): + raw, plan = load_plan(path) + + changes_detected = 0 + for key, value in plan.items(): + action = value.get("action") + if action != SKIP: + print(f"Unexpected {action=} for {key}") + changes_detected += 1 + if changes_detected: print(raw, flush=True) sys.exit(10) +def find_change(plan, field): + for key, value in plan.items(): + change = value.get("changes", {}).get(field) + if change is not None: + return key, change + return None, None + + +def check_expected_change(path, field, shape): + raw, plan = load_plan(path) + + key, change = find_change(plan, field) + if change is None: + fail(raw, f"No planned change for field {field!r}") + + action = change.get("action") + if action == SKIP: + fail(raw, f"Change for {key}.{field} was skipped ({change.get('reason')!r})") + + old = change.get("old", MISSING) + new = change.get("new", MISSING) + remote = change.get("remote", MISSING) + + if shape == LOCAL and old == new: + fail(raw, f"Change for {key}.{field} is not a local change: old == new == {new!r}") + if shape == REMOTE: + if old != new: + fail(raw, f"Change for {key}.{field} is not remote-only: {old=} != {new=}") + if remote == new: + fail(raw, f"Change for {key}.{field} has no remote drift: remote == new == {new!r}") + + def main(): - for path in sys.argv[1:]: - check_plan(path) + parser = argparse.ArgumentParser() + parser.add_argument("filenames", nargs="+") + parser.add_argument("--expect-change", metavar="FIELD") + parser.add_argument("--shape", choices=[LOCAL, REMOTE]) + args = parser.parse_args() + + if bool(args.expect_change) != bool(args.shape): + parser.error("--expect-change and --shape must be given together") + + for path in args.filenames: + if args.expect_change: + check_expected_change(path, args.expect_change, args.shape) + else: + check_no_drift(path) if __name__ == "__main__": diff --git a/acceptance/bundle/invariant/README.md b/acceptance/bundle/invariant/README.md index 184d3f541c4..d253c1974e9 100644 --- a/acceptance/bundle/invariant/README.md +++ b/acceptance/bundle/invariant/README.md @@ -4,3 +4,42 @@ no_drift test checks that there are no actions planned after successful deploy. test will dump full JSON plan to the output. In order to add a new test, add a config to configs/ and include it in test.toml. + +## Field mutations + +`apply_update` and `apply_remote_update` check that a single field change is planned, +applied, and converges. They read the change to make from the config itself, as a +trailing comment, so the same config still works for every other target: + +```yaml + comment: base comment # ACTION: SET "changed comment" + #description: test description # ACTION: UNCOMMENT + user_api_scopes: [iam.access] # ACTION: REMOVE +``` + +`apply_update` edits the config, so the plan must show a *local* change for that +field. `apply_remote_update` deploys the edit and then restores the pre-deploy state +snapshot, leaving the remote ahead of both state and config -- so the plan must show +a *remote* change for that field. Checking which of the two it is matters: reverting +a config also produces a change, so the action alone proves nothing. + +Both targets return the remote to the base config afterwards, which for REMOVE and +UNCOMMENT means clearing the field. A resource whose update request drops empty +fields never converges, so only SET is usable there (see `schema.yml.tmpl`). + +A field the engine deliberately ignores expects `skip` instead, and which target +sees that depends on why it is ignored -- `ignore_local_changes` is skipped when the +config changes, `ignore_remote_changes` when the remote drifts: + +```yaml + #compute_size: LARGE # ACTION: UNCOMMENT EXPECT_REMOTE_UPDATE: skip +``` + +Run one mutation on its own with `$MUTATION`: + +``` +MUTATION=comment:set go test ./acceptance \ + -run 'TestAccept/bundle/invariant/apply_update/.*INPUT_CONFIG=schema.yml.tmpl' +``` + +See `acceptance/bin/apply_mutation.py` for the full annotation reference. diff --git a/acceptance/bundle/invariant/apply_remote_update/out.test.toml b/acceptance/bundle/invariant/apply_remote_update/out.test.toml new file mode 100644 index 00000000000..60545df787b --- /dev/null +++ b/acceptance/bundle/invariant/apply_remote_update/out.test.toml @@ -0,0 +1,8 @@ +Cloud = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.INPUT_CONFIG = [ + "app.yml.tmpl", + "job.yml.tmpl", + "postgres_endpoint.yml.tmpl", + "schema.yml.tmpl" +] diff --git a/acceptance/bundle/invariant/apply_remote_update/output.txt b/acceptance/bundle/invariant/apply_remote_update/output.txt new file mode 100644 index 00000000000..7a28cb73a58 --- /dev/null +++ b/acceptance/bundle/invariant/apply_remote_update/output.txt @@ -0,0 +1 @@ +INPUT_CONFIG_OK diff --git a/acceptance/bundle/invariant/apply_remote_update/script b/acceptance/bundle/invariant/apply_remote_update/script new file mode 100644 index 00000000000..238a2000393 --- /dev/null +++ b/acceptance/bundle/invariant/apply_remote_update/script @@ -0,0 +1,34 @@ +# Invariant to test: drift the CLI itself introduced is detected once state no +# longer records it. Deploying a mutation and then restoring the pre-deploy state +# snapshot leaves remote ahead of both state and config -- exactly the shape of an +# out-of-band change made by someone else. + +EXPECT_KEY=EXPECT_REMOTE_UPDATE +SHAPE=remote + +invariant_setup + +invariant_deploy LOG.deploy $CLI bundle deploy +invariant_verify_no_drift + +run_mutation() { + local name="$1" + local expect="$2" + + state_snapshot.py save + + invariant_render_config "$name" + invariant_deploy_quiet "LOG.deploy.$name" + + # The CLI made this change, so drop the state that records it and put the + # config back: remote now differs from both. + state_snapshot.py restore + invariant_render_config + + invariant_verify_mutation "$name" "$expect" + + invariant_deploy_quiet "LOG.deploy.$name.converge" + invariant_verify_no_drift +} + +invariant_for_each_mutation run_mutation diff --git a/acceptance/bundle/invariant/apply_remote_update/test.toml b/acceptance/bundle/invariant/apply_remote_update/test.toml new file mode 100644 index 00000000000..798a2a9376b --- /dev/null +++ b/acceptance/bundle/invariant/apply_remote_update/test.toml @@ -0,0 +1,14 @@ +# Both targets assert on the plan's per-field "changes" map, which only the direct +# engine emits -- a terraform plan carries a resource-level action and nothing else. +# Pinned here rather than inherited so re-enabling terraform in the parent matrix +# does not break these two targets. +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] + +# Only configs carrying "# ACTION:" annotations are worth running here; without any +# the target degenerates into a second copy of no_drift. Overrides the parent list. +EnvMatrix.INPUT_CONFIG = [ + "app.yml.tmpl", + "job.yml.tmpl", + "postgres_endpoint.yml.tmpl", + "schema.yml.tmpl", +] diff --git a/acceptance/bundle/invariant/apply_update/out.test.toml b/acceptance/bundle/invariant/apply_update/out.test.toml new file mode 100644 index 00000000000..60545df787b --- /dev/null +++ b/acceptance/bundle/invariant/apply_update/out.test.toml @@ -0,0 +1,8 @@ +Cloud = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.INPUT_CONFIG = [ + "app.yml.tmpl", + "job.yml.tmpl", + "postgres_endpoint.yml.tmpl", + "schema.yml.tmpl" +] diff --git a/acceptance/bundle/invariant/apply_update/output.txt b/acceptance/bundle/invariant/apply_update/output.txt new file mode 100644 index 00000000000..7a28cb73a58 --- /dev/null +++ b/acceptance/bundle/invariant/apply_update/output.txt @@ -0,0 +1 @@ +INPUT_CONFIG_OK diff --git a/acceptance/bundle/invariant/apply_update/script b/acceptance/bundle/invariant/apply_update/script new file mode 100644 index 00000000000..cc167ff892a --- /dev/null +++ b/acceptance/bundle/invariant/apply_update/script @@ -0,0 +1,28 @@ +# Invariant to test: a config change is planned, applied, and leaves no drift. +# Each "# ACTION:" annotation in the config is one mutation; all of them run +# against a single deployed resource, converging back to base in between. + +EXPECT_KEY=EXPECT_UPDATE +SHAPE=local + +invariant_setup + +invariant_deploy LOG.deploy $CLI bundle deploy +invariant_verify_no_drift + +run_mutation() { + local name="$1" + local expect="$2" + + invariant_render_config "$name" + invariant_verify_mutation "$name" "$expect" + invariant_deploy_quiet "LOG.deploy.$name" + invariant_verify_no_drift + + # Back to base so the next mutation starts from the same remote state. + invariant_render_config + invariant_deploy_quiet "LOG.deploy.$name.revert" + invariant_verify_no_drift +} + +invariant_for_each_mutation run_mutation diff --git a/acceptance/bundle/invariant/apply_update/test.toml b/acceptance/bundle/invariant/apply_update/test.toml new file mode 100644 index 00000000000..798a2a9376b --- /dev/null +++ b/acceptance/bundle/invariant/apply_update/test.toml @@ -0,0 +1,14 @@ +# Both targets assert on the plan's per-field "changes" map, which only the direct +# engine emits -- a terraform plan carries a resource-level action and nothing else. +# Pinned here rather than inherited so re-enabling terraform in the parent matrix +# does not break these two targets. +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] + +# Only configs carrying "# ACTION:" annotations are worth running here; without any +# the target degenerates into a second copy of no_drift. Overrides the parent list. +EnvMatrix.INPUT_CONFIG = [ + "app.yml.tmpl", + "job.yml.tmpl", + "postgres_endpoint.yml.tmpl", + "schema.yml.tmpl", +] diff --git a/acceptance/bundle/invariant/configs/app.yml.tmpl b/acceptance/bundle/invariant/configs/app.yml.tmpl index 153b04e9b56..6e152e648c9 100644 --- a/acceptance/bundle/invariant/configs/app.yml.tmpl +++ b/acceptance/bundle/invariant/configs/app.yml.tmpl @@ -6,6 +6,11 @@ resources: foo: name: app-$UNIQUE_NAME source_code_path: ./app + # Mutations for apply_update / apply_remote_update; see acceptance/bin/apply_mutation.py. + #description: test description # ACTION: UNCOMMENT + # compute_size is a backend_default: with no value in config the backend's own + # value must be accepted (skip), but an explicit value must still be applied. + #compute_size: LARGE # ACTION: UNCOMMENT EXPECT_REMOTE_UPDATE: skip permissions: - level: CAN_USE group_name: users diff --git a/acceptance/bundle/invariant/configs/job.yml.tmpl b/acceptance/bundle/invariant/configs/job.yml.tmpl index 696483648b5..6e5dd89df3b 100644 --- a/acceptance/bundle/invariant/configs/job.yml.tmpl +++ b/acceptance/bundle/invariant/configs/job.yml.tmpl @@ -5,6 +5,9 @@ resources: jobs: foo: name: test-job-$UNIQUE_NAME + # Mutations for apply_update / apply_remote_update; see acceptance/bin/apply_mutation.py. + #description: test description # ACTION: UNCOMMENT + #max_concurrent_runs: 2 # ACTION: UNCOMMENT permissions: - level: CAN_VIEW group_name: users diff --git a/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl b/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl index fbffd428ff6..94104effb10 100644 --- a/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl +++ b/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl @@ -18,3 +18,11 @@ resources: parent: ${resources.postgres_branches.branch.name} endpoint_id: test-endpoint-$UNIQUE_NAME endpoint_type: ENDPOINT_TYPE_READ_WRITE + # Mutations for apply_update / apply_remote_update; see acceptance/bin/apply_mutation.py. + # BUG: the change is detected (old "300s", new "600s") but then suppressed with + # reason "empty". isEmptyStruct in bundle/direct/bundle_plan.go only looks at + # exported fields, and SDK duration.Duration wraps a single unexported + # *durationpb.Duration -- so every value of it looks like an empty struct and + # allEmpty(old, new) is always true. Expecting "skip" locks in the broken + # behaviour; flip to non-skip once isEmptyStruct handles these SDK types. + suspend_timeout_duration: 300s # ACTION: SET 600s EXPECT: skip diff --git a/acceptance/bundle/invariant/configs/schema.yml.tmpl b/acceptance/bundle/invariant/configs/schema.yml.tmpl index d9aebda0cef..934c3928236 100644 --- a/acceptance/bundle/invariant/configs/schema.yml.tmpl +++ b/acceptance/bundle/invariant/configs/schema.yml.tmpl @@ -6,6 +6,13 @@ resources: foo: catalog_name: main name: test-schema-$UNIQUE_NAME + # Mutations for apply_update / apply_remote_update; see acceptance/bin/apply_mutation.py. + # Only SET is usable here. Every field of UpdateSchema is omitempty and DoUpdate + # carries just the config's own ForceSendFields, so clearing a field drops it from + # the PATCH and the remote keeps its old value. Both targets have to return the + # remote to base, which for REMOVE/UNCOMMENT means clearing -- so those never + # converge for this resource. Applies to comment and properties alike. + comment: base comment # ACTION: SET "changed comment" grants: - principal: account users privileges: diff --git a/acceptance/bundle/invariant/script.prepare b/acceptance/bundle/invariant/script.prepare index 54f2a3dbf25..b08007028cf 100644 --- a/acceptance/bundle/invariant/script.prepare +++ b/acceptance/bundle/invariant/script.prepare @@ -52,3 +52,72 @@ invariant_verify_no_drift() { cat LOG.planjson.err | contains.py '!panic:' '!internal error' > /dev/null verify_no_drift.py LOG.planjson } + +# Asserts the plan reflects one mutation. expect=skip means the engine deliberately +# ignores this field, which is the strict "nothing planned at all" check. Otherwise the +# change must be planned for the mutated field, with the shape this target creates: +# SHAPE=local for a config change, SHAPE=remote for out-of-band remote drift. Checking +# the shape is what makes the two targets distinguishable -- reverting a config also +# produces a change, so the action alone proves nothing. +invariant_verify_mutation() { + local name="$1" + local expect="$2" + + $CLI bundle plan -o json > LOG.planjson 2>LOG.planjson.err + cat LOG.planjson.err | contains.py '!panic:' '!internal error' > /dev/null + + if [ "$expect" = "skip" ]; then + verify_no_drift.py LOG.planjson + else + verify_no_drift.py LOG.planjson --expect-change "${name%:*}" --shape "$SHAPE" + fi +} + +# Deploys without adding anything to output.txt. Unlike invariant_deploy this +# prints no INPUT_CONFIG_OK marker: config validity is already established by the +# base deploy that runs before any mutation. +invariant_deploy_quiet() { + local logfile="$1" + trace $CLI bundle deploy &> "$logfile" + cat "$logfile" | contains.py '!panic:' '!internal error' > /dev/null +} + +# Calls HANDLER once per "# ACTION:" annotation in the config. +# EXPECT_KEY selects which expectation the annotations are read for, since an +# ignored field is skipped by only one of the two targets. +# +# On success nothing is printed: mutation names differ per INPUT_CONFIG, and every +# EnvMatrix variant has to produce the same output.txt. The name is recorded in +# LOG.mutations and in the per-mutation LOG files, and printed on failure so a +# broken run says which mutation broke. +# +# $MUTATION pins a single mutation for debugging: +# MUTATION=description:remove go test ./acceptance -run '...' +invariant_for_each_mutation() { + local handler="$1" + + apply_mutation.py databricks.yml --list --expect-key "$EXPECT_KEY" > LOG.mutations + + while read -r name expect; do + if [ -n "${MUTATION:-}" ] && [ "${MUTATION:-}" != "$name" ]; then + continue + fi + "$handler" "$name" "$expect" || { + echo "FAILED mutation=$name expect=$expect" + exit 1 + } + done < LOG.mutations +} + +# Re-renders databricks.yml from the template, optionally applying one mutation. +# Re-rendering is what returns the config to base, so a mutation never sees a +# previous one's edit. Only the envsubst step is repeated: the data files and the +# per-config init script are already in place from invariant_setup. +invariant_render_config() { + local name="${1:-}" + envsubst < "$TESTDIR/../configs/$INPUT_CONFIG" > databricks.yml + if [ -n "$name" ]; then + apply_mutation.py databricks.yml "$name" + fi + cp databricks.yml "LOG.config.${name:-base}" +} From 2a925766bf68eee827c3640e9f89691b9a28d579 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 11:44:02 +0200 Subject: [PATCH 006/118] Fix field catalog context lifetime and skip the telemetry block A harness rebuilt inside a field-level subtest goes on being used by later fields, but it captured that subtest's t.Context(), which is cancelled the moment the subtest returns -- 56 observations came back as "context canceled" from the SDK rate limiter rather than a real verdict. Thread the resource-type context in instead. With those unmasked, model serving telemetry turned out to be rejected 36 times: the block only validates against a catalog and schema that exist and are writable. Skip the subtree, which needed skip keys to support a ".*" form. Co-authored-by: Isaac --- .../fields/model_serving_endpoints.yml | 6 +- bundle/direct/dresources_test/fields_test.go | 23 ++--- bundle/direct/dresources_test/harness_test.go | 8 +- .../dresources_test/out.catalogs.fields.txt | 12 +-- .../out.external_locations.fields.txt | 13 ++- .../out.model_serving_endpoints.fields.txt | 90 +++---------------- .../out.postgres_projects.fields.txt | 44 ++++----- .../out.registered_models.fields.txt | 16 ++-- .../dresources_test/out.schemas.fields.txt | 16 ++-- .../dresources_test/out.volumes.fields.txt | 11 ++- bundle/direct/dresources_test/values_test.go | 17 +++- 11 files changed, 108 insertions(+), 148 deletions(-) diff --git a/bundle/direct/dresources_test/fields/model_serving_endpoints.yml b/bundle/direct/dresources_test/fields/model_serving_endpoints.yml index 6a9928972f9..1f36e5043a5 100644 --- a/bundle/direct/dresources_test/fields/model_serving_endpoints.yml +++ b/bundle/direct/dresources_test/fields/model_serving_endpoints.yml @@ -4,6 +4,6 @@ cloud: false skip: - # The inference table name is only accepted alongside a catalog and schema that exist - # and are writable, which one-field-at-a-time cannot arrange. - telemetry_config.inference_table_config.name: needs a writable catalog and schema + # The telemetry block is only accepted as a coherent whole, against a catalog and + # schema that exist and are writable, which one-field-at-a-time cannot arrange. + telemetry_config.*: needs a writable catalog and schema diff --git a/bundle/direct/dresources_test/fields_test.go b/bundle/direct/dresources_test/fields_test.go index fc34ed7abde..a50bb48eba9 100644 --- a/bundle/direct/dresources_test/fields_test.go +++ b/bundle/direct/dresources_test/fields_test.go @@ -26,6 +26,7 @@ package dresources_test import ( + "context" "errors" "fmt" "regexp" @@ -68,11 +69,13 @@ func TestFields(t *testing.T) { // state. On cloud this is the same real workspace either way. client := newClient(t) user := workspaceUser(t, client) + // Outlives the field-level subtests that rebuild harnesses. + ctx := t.Context() rep := &report{resourceType: resourceType} //exhaustruct:ignore for _, cfg := range configs { t.Run(cfg.name, func(t *testing.T) { - runConfig(t, client, user, cfg, fv, rep) + runConfig(t, ctx, client, user, cfg, fv, rep) }) } rep.write(t) @@ -80,7 +83,7 @@ func TestFields(t *testing.T) { } } -func runConfig(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, rep *report) { +func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, rep *report) { adapter, err := dresources.NewAdapter(dresources.SupportedResources[cfg.resourceType], cfg.resourceType, client) require.NoError(t, err) @@ -89,7 +92,7 @@ func runConfig(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, // The base deploy establishes that the config itself is deployable. If it is not, // nothing below can be attributed to a field. - h, err := newBaseline(t, client, user, cfg) + h, err := newBaseline(t, ctx, client, user, cfg) if err != nil { rep.add(result{cfg.name, "(base config)", "create", verdictBaseError, oneLine(err.Error())}) return @@ -98,7 +101,7 @@ func runConfig(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, base := h.fieldSnapshot() for _, f := range fields { - if reason, ok := fv.skip[f.path]; ok { + if reason, ok := fv.skipReason(f.path); ok { rep.add(result{cfg.name, f.path, "-", verdictSkipped, reason}) continue } @@ -112,7 +115,7 @@ func runConfig(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, } // The resource is in an unknown state; carrying it into the next // transition would turn one failure into a run of them. - rebuilt, err := rebuild(t, client, user, cfg, h) + rebuilt, err := rebuild(t, ctx, client, user, cfg, h) if err != nil { t.Skipf("could not rebuild baseline: %s", err) } @@ -127,7 +130,7 @@ func runConfig(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, // what makes that cheap: reusing the old one waits out an asynchronous delete. h.restoreField(base, f.path) if !h.converged() { - rebuilt, err := rebuild(t, client, user, cfg, h) + rebuilt, err := rebuild(t, ctx, client, user, cfg, h) if err != nil { rep.add(result{cfg.name, f.path, "-", verdictBaseError, oneLine(err.Error())}) return @@ -207,8 +210,8 @@ func runTransition(h *bundleHarness, config, path string, tr transition) result } // newBaseline builds a harness and deploys the config as written. -func newBaseline(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig) (*bundleHarness, error) { - h, err := newHarness(t, client, user, cfg.name, uniqueName()) +func newBaseline(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig) (*bundleHarness, error) { + h, err := newHarness(t, ctx, client, user, cfg.name, uniqueName()) if err != nil { return nil, err } @@ -227,9 +230,9 @@ func newBaseline(t *testing.T, client *databricks.WorkspaceClient, user *iam.Use // rebuild starts over on a fresh resource, leaving the old one to be destroyed at the // end of the test. A new name is what makes this cheap: reusing the old one can mean // waiting out an asynchronous delete. -func rebuild(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, old *bundleHarness) (*bundleHarness, error) { +func rebuild(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, old *bundleHarness) (*bundleHarness, error) { t.Cleanup(func() { _ = old.destroy() }) - return newBaseline(t, client, user, cfg) + return newBaseline(t, ctx, client, user, cfg) } // explainSkip says why a plan came back with nothing to do for the field under test. diff --git a/bundle/direct/dresources_test/harness_test.go b/bundle/direct/dresources_test/harness_test.go index 207f4447a5d..3b8735770e7 100644 --- a/bundle/direct/dresources_test/harness_test.go +++ b/bundle/direct/dresources_test/harness_test.go @@ -145,7 +145,7 @@ func maxWaitSeconds() string { // built is usually the environment (an expired token, a workspace that rejects the // config), and the run is more useful if that lands in the report as one bad verdict // than if it aborts thousands of pending observations. -func newHarness(t *testing.T, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string) (*bundleHarness, error) { +func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string) (*bundleHarness, error) { dir := t.TempDir() if err := copyDir(dataDir, dir); err != nil { return nil, err @@ -173,7 +173,11 @@ func newHarness(t *testing.T, client *databricks.WorkspaceClient, user *iam.User return nil, err } - ctx := dbr.MockRuntime(t.Context(), dbr.Environment{}) //exhaustruct:ignore + // The caller's context, not t.Context(): a harness is rebuilt inside a field-level + // subtest but goes on being used by later fields, and t.Context() is cancelled the + // moment its subtest returns -- which surfaced as "context canceled" from the SDK + // rate limiter on whatever ran next. + ctx = dbr.MockRuntime(ctx, dbr.Environment{}) //exhaustruct:ignore // Thousands of deploys run through here, so cap how long any one of them waits for // a resource to become ready. Without a cap a resource that never reaches its // terminal state stalls the whole suite instead of showing up as one bad verdict. diff --git a/bundle/direct/dresources_test/out.catalogs.fields.txt b/bundle/direct/dresources_test/out.catalogs.fields.txt index 4d7657670b5..e6c54bcc206 100644 --- a/bundle/direct/dresources_test/out.catalogs.fields.txt +++ b/bundle/direct/dresources_test/out.catalogs.fields.txt @@ -1,9 +1,9 @@ === catalog.yml.tmpl comment absent->x OK comment absent->y OK -comment x->absent DRIFT comment +comment x->absent OK comment x->y OK -comment y->absent DRIFT comment +comment y->absent OK comment y->x OK connection_name absent->x OK_RECREATE connection_name absent->y OK_RECREATE @@ -73,9 +73,9 @@ storage_root y->x OK_RECREATE === catalog_optional_fields.yml.tmpl comment absent->x OK comment absent->y OK -comment x->absent DRIFT comment +comment x->absent OK comment x->y OK -comment y->absent DRIFT comment +comment y->absent OK comment y->x OK connection_name absent->x OK_RECREATE connection_name absent->y OK_RECREATE @@ -143,10 +143,10 @@ storage_root y->absent OK_RECREATE storage_root y->x OK_RECREATE === summary -OK 72 +OK 76 OK_RECREATE 48 NOT_OBSERVABLE 12 -DRIFT 8 +DRIFT 4 === not covered: 4 fields under a slice or map grants[*].principal diff --git a/bundle/direct/dresources_test/out.external_locations.fields.txt b/bundle/direct/dresources_test/out.external_locations.fields.txt index 5a0cd89c93e..ec72fc0eb1e 100644 --- a/bundle/direct/dresources_test/out.external_locations.fields.txt +++ b/bundle/direct/dresources_test/out.external_locations.fields.txt @@ -1,9 +1,9 @@ === external_location.yml.tmpl comment absent->x OK comment absent->y OK -comment x->absent DRIFT comment +comment x->absent OK comment x->y OK -comment y->absent DRIFT comment +comment y->absent OK comment y->x OK credential_name x->y OK_RECREATE credential_name y->x OK_RECREATE @@ -111,7 +111,7 @@ effective_file_event_queue.provided_sqs.queue_url y->absent OK effective_file_event_queue.provided_sqs.queue_url y->x OK enable_file_events absent->false SUPPRESSED empty enable_file_events absent->true OK -enable_file_events false->absent SUPPRESSED remote_already_set +enable_file_events false->absent SUPPRESSED empty enable_file_events false->true OK enable_file_events true->absent OK enable_file_events true->false OK @@ -129,7 +129,7 @@ encryption_details.sse_encryption_details.aws_kms_key_arn y->absent OK_ encryption_details.sse_encryption_details.aws_kms_key_arn y->x OK_RECREATE fallback absent->false SUPPRESSED empty fallback absent->true OK -fallback false->absent SUPPRESSED remote_already_set +fallback false->absent SUPPRESSED empty fallback false->true OK fallback true->absent OK fallback true->false OK @@ -239,7 +239,7 @@ name x->y OK name y->x OK read_only absent->false SUPPRESSED empty read_only absent->true OK -read_only false->absent SUPPRESSED remote_already_set +read_only false->absent SUPPRESSED empty read_only false->true OK read_only true->absent OK read_only true->false OK @@ -253,11 +253,10 @@ url x->y OK url y->x OK === summary -OK 124 +OK 126 OK_RECREATE 110 SUPPRESSED 10 NOT_OBSERVABLE 6 -DRIFT 2 === not covered: 2 fields under a slice or map grants[*].principal diff --git a/bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt b/bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt index 42456e6d3e5..e27fd98e2e2 100644 --- a/bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt +++ b/bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt @@ -106,42 +106,12 @@ route_optimized false->true OK_RECREATE route_optimized true->absent OK_RECREATE route_optimized true->false OK_RECREATE telemetry_config.inference_table_config.name - SKIPPED needs a writable catalog and schema -telemetry_config.inference_table_config.sampling_fraction 1->2 BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.inference_table_config.sampling_fraction 1->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.inference_table_config.sampling_fraction 2->1 BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.inference_table_config.sampling_fraction 2->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.inference_table_config.sampling_fraction absent->1 BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... -telemetry_config.inference_table_config.sampling_fraction absent->2 BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.annotations_table absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... -telemetry_config.table_names.annotations_table absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.annotations_table x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.annotations_table x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.annotations_table y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.annotations_table y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.logs_table absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... -telemetry_config.table_names.logs_table absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.logs_table x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.logs_table x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.logs_table y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.logs_table y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.metrics_table absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... -telemetry_config.table_names.metrics_table absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.metrics_table x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.metrics_table x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.metrics_table y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.metrics_table y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.traces_table absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... -telemetry_config.table_names.traces_table absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.traces_table x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.traces_table x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.traces_table y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.table_names.traces_table y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.telemetry_profile_id absent->x BACKEND_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... -telemetry_config.telemetry_profile_id absent->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.telemetry_profile_id x->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.telemetry_profile_id x->y BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.telemetry_profile_id y->absent BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled -telemetry_config.telemetry_profile_id y->x BASE_ERROR cannot create resources.model_serving_endpoints.foo: failed in rate limiter: context canceled +telemetry_config.inference_table_config.sampling_fraction - SKIPPED needs a writable catalog and schema +telemetry_config.table_names.annotations_table - SKIPPED needs a writable catalog and schema +telemetry_config.table_names.logs_table - SKIPPED needs a writable catalog and schema +telemetry_config.table_names.metrics_table - SKIPPED needs a writable catalog and schema +telemetry_config.table_names.traces_table - SKIPPED needs a writable catalog and schema +telemetry_config.telemetry_profile_id - SKIPPED needs a writable catalog and schema === model_serving_endpoint_telemetry.yml.tmpl ai_gateway.fallback_config.enabled false->true OK @@ -251,52 +221,20 @@ route_optimized false->true OK_RECREATE route_optimized true->absent OK_RECREATE route_optimized true->false OK_RECREATE telemetry_config.inference_table_config.name - SKIPPED needs a writable catalog and schema -telemetry_config.inference_table_config.sampling_fraction 1->2 OK -telemetry_config.inference_table_config.sampling_fraction 1->absent OK -telemetry_config.inference_table_config.sampling_fraction 2->1 OK -telemetry_config.inference_table_config.sampling_fraction 2->absent OK -telemetry_config.inference_table_config.sampling_fraction absent->1 OK -telemetry_config.inference_table_config.sampling_fraction absent->2 OK -telemetry_config.table_names.annotations_table absent->x OK -telemetry_config.table_names.annotations_table absent->y OK -telemetry_config.table_names.annotations_table x->absent OK -telemetry_config.table_names.annotations_table x->y OK -telemetry_config.table_names.annotations_table y->absent OK -telemetry_config.table_names.annotations_table y->x OK -telemetry_config.table_names.logs_table absent->x OK -telemetry_config.table_names.logs_table absent->y OK -telemetry_config.table_names.logs_table x->absent OK -telemetry_config.table_names.logs_table x->y OK -telemetry_config.table_names.logs_table y->absent OK -telemetry_config.table_names.logs_table y->x OK -telemetry_config.table_names.metrics_table absent->x OK -telemetry_config.table_names.metrics_table absent->y OK -telemetry_config.table_names.metrics_table x->absent OK -telemetry_config.table_names.metrics_table x->y OK -telemetry_config.table_names.metrics_table y->absent OK -telemetry_config.table_names.metrics_table y->x OK -telemetry_config.table_names.traces_table absent->x OK -telemetry_config.table_names.traces_table absent->y OK -telemetry_config.table_names.traces_table x->absent OK -telemetry_config.table_names.traces_table x->y OK -telemetry_config.table_names.traces_table y->absent OK -telemetry_config.table_names.traces_table y->x OK -telemetry_config.telemetry_profile_id absent->x OK -telemetry_config.telemetry_profile_id absent->y OK -telemetry_config.telemetry_profile_id x->absent OK -telemetry_config.telemetry_profile_id x->y OK -telemetry_config.telemetry_profile_id y->absent OK -telemetry_config.telemetry_profile_id y->x OK +telemetry_config.inference_table_config.sampling_fraction - SKIPPED needs a writable catalog and schema +telemetry_config.table_names.annotations_table - SKIPPED needs a writable catalog and schema +telemetry_config.table_names.logs_table - SKIPPED needs a writable catalog and schema +telemetry_config.table_names.metrics_table - SKIPPED needs a writable catalog and schema +telemetry_config.table_names.traces_table - SKIPPED needs a writable catalog and schema +telemetry_config.telemetry_profile_id - SKIPPED needs a writable catalog and schema === summary -OK 139 +OK 103 OK_RECREATE 57 SUPPRESSED 29 NOT_OBSERVABLE 16 DRIFT 7 -BACKEND_ERROR 6 -SKIPPED 2 -BASE_ERROR 30 +SKIPPED 14 === not covered: 95 fields under a slice or map ai_gateway.guardrails.input.invalid_keywords[*] diff --git a/bundle/direct/dresources_test/out.postgres_projects.fields.txt b/bundle/direct/dresources_test/out.postgres_projects.fields.txt index 5dabe17e388..ae074fb3cad 100644 --- a/bundle/direct/dresources_test/out.postgres_projects.fields.txt +++ b/bundle/direct/dresources_test/out.postgres_projects.fields.txt @@ -1,45 +1,45 @@ === postgres_project.yml.tmpl budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK +budget_policy_id absent->y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +budget_policy_id x->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... budget_policy_id x->y OK -budget_policy_id y->absent OK +budget_policy_id y->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... budget_policy_id y->x OK default_branch absent->x OK -default_branch absent->y OK -default_branch x->absent OK +default_branch absent->y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... +default_branch x->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_branch x->y OK -default_branch y->absent OK +default_branch y->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_branch y->x OK default_endpoint_settings.autoscaling_limit_max_cu 1->2 OK -default_endpoint_settings.autoscaling_limit_max_cu 1->absent OK +default_endpoint_settings.autoscaling_limit_max_cu 1->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_max_cu 2->1 OK -default_endpoint_settings.autoscaling_limit_max_cu 2->absent OK +default_endpoint_settings.autoscaling_limit_max_cu 2->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_max_cu absent->1 OK -default_endpoint_settings.autoscaling_limit_max_cu absent->2 OK +default_endpoint_settings.autoscaling_limit_max_cu absent->2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_min_cu 1->2 OK -default_endpoint_settings.autoscaling_limit_min_cu 1->absent OK +default_endpoint_settings.autoscaling_limit_min_cu 1->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_min_cu 2->1 OK -default_endpoint_settings.autoscaling_limit_min_cu 2->absent OK +default_endpoint_settings.autoscaling_limit_min_cu 2->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_min_cu absent->1 OK -default_endpoint_settings.autoscaling_limit_min_cu absent->2 OK +default_endpoint_settings.autoscaling_limit_min_cu absent->2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.no_suspension absent->false OK -default_endpoint_settings.no_suspension absent->true OK +default_endpoint_settings.no_suspension absent->true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension false->absent OK -default_endpoint_settings.no_suspension false->true OK -default_endpoint_settings.no_suspension true->absent OK -default_endpoint_settings.no_suspension true->false OK -display_name absent->x OK +default_endpoint_settings.no_suspension false->true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true->false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +display_name absent->x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name absent->y OK -display_name x->absent OK +display_name x->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name x->y OK -display_name y->absent OK +display_name y->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name y->x OK enable_pg_native_login absent->false SUPPRESSED empty enable_pg_native_login absent->true OK enable_pg_native_login false->absent SUPPRESSED empty enable_pg_native_login false->true OK -enable_pg_native_login true->absent OK +enable_pg_native_login true->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... enable_pg_native_login true->false OK lifecycle.prevent_destroy absent->false NOT_OBSERVABLE lifecycle.prevent_destroy absent->true NOT_OBSERVABLE @@ -63,10 +63,12 @@ purge_on_delete true->absent OK purge_on_delete true->false OK === summary -OK 44 +OK 24 OK_RECREATE 8 SUPPRESSED 4 NOT_OBSERVABLE 6 +BACKEND_ERROR 15 +BASE_ERROR 5 === not covered: 7 fields under a slice or map custom_tags[*].key diff --git a/bundle/direct/dresources_test/out.registered_models.fields.txt b/bundle/direct/dresources_test/out.registered_models.fields.txt index c1cf187fc2b..d8f4e1d717f 100644 --- a/bundle/direct/dresources_test/out.registered_models.fields.txt +++ b/bundle/direct/dresources_test/out.registered_models.fields.txt @@ -13,9 +13,9 @@ catalog_name y->absent OK_RECREATE catalog_name y->x OK_RECREATE comment absent->x OK comment absent->y OK -comment x->absent DRIFT comment +comment x->absent OK comment x->y OK -comment y->absent DRIFT comment +comment y->absent OK comment y->x OK created_at 1->2 OK created_at 1->absent OK @@ -41,12 +41,12 @@ lifecycle.prevent_destroy false->absent NOT_OBSERVABLE lifecycle.prevent_destroy false->true NOT_OBSERVABLE lifecycle.prevent_destroy true->absent NOT_OBSERVABLE lifecycle.prevent_destroy true->false NOT_OBSERVABLE -metastore_id absent->x DRIFT metastore_id -metastore_id absent->y DRIFT metastore_id +metastore_id absent->x OK +metastore_id absent->y OK metastore_id x->absent OK -metastore_id x->y DRIFT metastore_id +metastore_id x->y OK metastore_id y->absent OK -metastore_id y->x DRIFT metastore_id +metastore_id y->x OK name absent->x OK_RECREATE name absent->y OK_RECREATE name x->absent OK_RECREATE @@ -85,11 +85,11 @@ updated_by y->absent OK updated_by y->x OK === summary -OK 38 +OK 44 OK_RECREATE 24 SUPPRESSED 2 NOT_OBSERVABLE 6 -DRIFT 14 +DRIFT 8 === not covered: 8 fields under a slice or map aliases[*].alias_name diff --git a/bundle/direct/dresources_test/out.schemas.fields.txt b/bundle/direct/dresources_test/out.schemas.fields.txt index 2776ca51f54..ab091a892b6 100644 --- a/bundle/direct/dresources_test/out.schemas.fields.txt +++ b/bundle/direct/dresources_test/out.schemas.fields.txt @@ -1,9 +1,9 @@ === schema.yml.tmpl comment absent->x OK comment absent->y OK -comment x->absent DRIFT comment +comment x->absent OK comment x->y OK -comment y->absent DRIFT comment +comment y->absent OK comment y->x OK custom_max_retention_hours 1->2 OK custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours @@ -24,9 +24,9 @@ storage_root - SKIPPED needs a === schema_empty_grants.yml.tmpl comment absent->x OK comment absent->y OK -comment x->absent DRIFT comment +comment x->absent OK comment x->y OK -comment y->absent DRIFT comment +comment y->absent OK comment y->x OK custom_max_retention_hours 1->2 OK custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours @@ -47,9 +47,9 @@ storage_root - SKIPPED needs a === schema_uppercase_name.yml.tmpl comment absent->x OK comment absent->y OK -comment x->absent DRIFT comment +comment x->absent OK comment x->y OK -comment y->absent DRIFT comment +comment y->absent OK comment y->x OK custom_max_retention_hours 1->2 OK custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours @@ -68,10 +68,10 @@ name y->x OK_RECREATE storage_root - SKIPPED needs an external location === summary -OK 24 +OK 30 OK_RECREATE 6 NOT_OBSERVABLE 18 -DRIFT 12 +DRIFT 6 SKIPPED 3 === not covered: 3 fields under a slice or map diff --git a/bundle/direct/dresources_test/out.volumes.fields.txt b/bundle/direct/dresources_test/out.volumes.fields.txt index b0f558666ca..26d4252283b 100644 --- a/bundle/direct/dresources_test/out.volumes.fields.txt +++ b/bundle/direct/dresources_test/out.volumes.fields.txt @@ -3,9 +3,9 @@ catalog_name x->y OK_RECREATE catalog_name y->x OK_RECREATE comment absent->x OK comment absent->y OK -comment x->absent DRIFT comment +comment x->absent OK comment x->y OK -comment y->absent DRIFT comment +comment y->absent OK comment y->x OK lifecycle.prevent_destroy absent->false NOT_OBSERVABLE lifecycle.prevent_destroy absent->true NOT_OBSERVABLE @@ -24,9 +24,9 @@ catalog_name x->y OK_RECREATE catalog_name y->x OK_RECREATE comment absent->x OK comment absent->y OK -comment x->absent DRIFT comment +comment x->absent OK comment x->y OK -comment y->absent DRIFT comment +comment y->absent OK comment y->x OK lifecycle.prevent_destroy absent->false NOT_OBSERVABLE lifecycle.prevent_destroy absent->true NOT_OBSERVABLE @@ -41,10 +41,9 @@ schema_name y->x OK_RECREATE storage_location - SKIPPED needs an external location === summary -OK 12 +OK 16 OK_RECREATE 8 NOT_OBSERVABLE 12 -DRIFT 4 SKIPPED 2 === not covered: 2 fields under a slice or map diff --git a/bundle/direct/dresources_test/values_test.go b/bundle/direct/dresources_test/values_test.go index 89c25b897ea..ff13cade752 100644 --- a/bundle/direct/dresources_test/values_test.go +++ b/bundle/direct/dresources_test/values_test.go @@ -34,7 +34,8 @@ type fieldValues struct { // it completes in reasonable time. cloud bool - // skip lists field paths to leave out entirely, with a reason. + // skip lists field paths to leave out entirely, with a reason. A key ending in ".*" + // skips that whole subtree. skip map[string]string // fields maps a field path to the values to try. @@ -169,6 +170,20 @@ func requiredFields(resourceType, parent string) []string { return generated.RequiredFields[key] } +// skipReason returns why a field is excluded, if it is. A ".*" key covers every +// field beneath it, for a block that only works as a whole. +func (fv *fieldValues) skipReason(path string) (string, bool) { + if reason, ok := fv.skip[path]; ok { + return reason, true + } + for key, reason := range fv.skip { + if prefix, ok := strings.CutSuffix(key, ".*"); ok && strings.HasPrefix(path, prefix+".") { + return reason, true + } + } + return "", false +} + func isRequired(resourceType, path string) bool { parent, name := "", path if i := strings.LastIndex(path, "."); i >= 0 { From b7aea86381cb049b6045733ec927010650afaf89 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 12:20:30 +0200 Subject: [PATCH 007/118] Run every resource type on cloud, and stop cancelling the harness context cloud: true was the wrong default -- a resource type should be covered everywhere, and the expensive ones still have to be covered. Replaced with slow: true, which only drops a type under -short: the PR cloud leg skips them, the nightly does not. Files that said nothing but cloud: true are gone. t.Context() is cancelled just before Cleanup functions run, so every harness destroy was issuing its API calls on a dead context and silently leaking the resource; a harness rebuilt inside a field subtest hit the same thing once that subtest returned. context.WithoutCancel keeps the values and drops the deadline. Reports move to output/, so the package directory reads as seven entries instead of thirty. Co-authored-by: Isaac --- bundle/direct/dresources_test/.gitignore | 6 ++-- bundle/direct/dresources_test/README.md | 25 ++++++------- bundle/direct/dresources_test/fields/apps.yml | 11 +++--- .../dresources_test/fields/catalogs.yml | 3 -- .../dresources_test/fields/experiments.yml | 3 -- bundle/direct/dresources_test/fields/jobs.yml | 3 +- .../fields/model_serving_endpoints.yml | 4 +-- .../direct/dresources_test/fields/models.yml | 3 -- .../fields/postgres_projects.yml | 4 +-- .../fields/registered_models.yml | 3 -- .../direct/dresources_test/fields/schemas.yml | 2 -- .../direct/dresources_test/fields/volumes.yml | 2 -- bundle/direct/dresources_test/fields_test.go | 4 +-- bundle/direct/dresources_test/harness_test.go | 13 ++++--- .../alerts.txt} | 0 .../{out.apps.fields.txt => output/apps.txt} | 0 .../catalogs.txt} | 0 .../cluster_policies.txt} | 0 .../clusters.txt} | 0 .../{out.configs.txt => output/configs.txt} | 0 .../dashboards.txt} | 0 .../database_instances.txt} | 0 .../experiments.txt} | 0 .../external_locations.txt} | 0 .../genie_spaces.txt} | 0 .../instance_pools.txt} | 0 .../{out.jobs.fields.txt => output/jobs.txt} | 0 .../model_serving_endpoints.txt} | 0 .../models.txt} | 0 .../pipelines.txt} | 0 .../postgres_projects.txt} | 0 .../postgres_synced_tables.txt} | 0 .../registered_models.txt} | 0 .../schemas.txt} | 0 .../secret_scopes.txt} | 0 .../sql_warehouses.txt} | 0 .../volumes.txt} | 0 bundle/direct/dresources_test/report_test.go | 36 ++++++++++++------- bundle/direct/dresources_test/values_test.go | 15 ++++---- 39 files changed, 69 insertions(+), 68 deletions(-) delete mode 100644 bundle/direct/dresources_test/fields/catalogs.yml delete mode 100644 bundle/direct/dresources_test/fields/experiments.yml delete mode 100644 bundle/direct/dresources_test/fields/models.yml delete mode 100644 bundle/direct/dresources_test/fields/registered_models.yml rename bundle/direct/dresources_test/{out.alerts.fields.txt => output/alerts.txt} (100%) rename bundle/direct/dresources_test/{out.apps.fields.txt => output/apps.txt} (100%) rename bundle/direct/dresources_test/{out.catalogs.fields.txt => output/catalogs.txt} (100%) rename bundle/direct/dresources_test/{out.cluster_policies.fields.txt => output/cluster_policies.txt} (100%) rename bundle/direct/dresources_test/{out.clusters.fields.txt => output/clusters.txt} (100%) rename bundle/direct/dresources_test/{out.configs.txt => output/configs.txt} (100%) rename bundle/direct/dresources_test/{out.dashboards.fields.txt => output/dashboards.txt} (100%) rename bundle/direct/dresources_test/{out.database_instances.fields.txt => output/database_instances.txt} (100%) rename bundle/direct/dresources_test/{out.experiments.fields.txt => output/experiments.txt} (100%) rename bundle/direct/dresources_test/{out.external_locations.fields.txt => output/external_locations.txt} (100%) rename bundle/direct/dresources_test/{out.genie_spaces.fields.txt => output/genie_spaces.txt} (100%) rename bundle/direct/dresources_test/{out.instance_pools.fields.txt => output/instance_pools.txt} (100%) rename bundle/direct/dresources_test/{out.jobs.fields.txt => output/jobs.txt} (100%) rename bundle/direct/dresources_test/{out.model_serving_endpoints.fields.txt => output/model_serving_endpoints.txt} (100%) rename bundle/direct/dresources_test/{out.models.fields.txt => output/models.txt} (100%) rename bundle/direct/dresources_test/{out.pipelines.fields.txt => output/pipelines.txt} (100%) rename bundle/direct/dresources_test/{out.postgres_projects.fields.txt => output/postgres_projects.txt} (100%) rename bundle/direct/dresources_test/{out.postgres_synced_tables.fields.txt => output/postgres_synced_tables.txt} (100%) rename bundle/direct/dresources_test/{out.registered_models.fields.txt => output/registered_models.txt} (100%) rename bundle/direct/dresources_test/{out.schemas.fields.txt => output/schemas.txt} (100%) rename bundle/direct/dresources_test/{out.secret_scopes.fields.txt => output/secret_scopes.txt} (100%) rename bundle/direct/dresources_test/{out.sql_warehouses.fields.txt => output/sql_warehouses.txt} (100%) rename bundle/direct/dresources_test/{out.volumes.fields.txt => output/volumes.txt} (100%) diff --git a/bundle/direct/dresources_test/.gitignore b/bundle/direct/dresources_test/.gitignore index ae9f4974f37..7dc023cabdf 100644 --- a/bundle/direct/dresources_test/.gitignore +++ b/bundle/direct/dresources_test/.gitignore @@ -1,4 +1,4 @@ # Cloud runs record what a real backend accepted, which varies by workspace. -out.*.fields.aws.txt -out.*.fields.azure.txt -out.*.fields.gcp.txt +output/*.aws.txt +output/*.azure.txt +output/*.gcp.txt diff --git a/bundle/direct/dresources_test/README.md b/bundle/direct/dresources_test/README.md index 3cb25b1e893..880eea0ec77 100644 --- a/bundle/direct/dresources_test/README.md +++ b/bundle/direct/dresources_test/README.md @@ -3,7 +3,7 @@ This suite answers one question for every field a user can set on a bundle resource: **if I change this field, does the direct engine notice, apply it, and converge?** -The answer for every field lands in `out..fields.txt`. Those files are +The answer for every field lands in `output/.txt`. Those files are the deliverable — a map of where field support is solid and where it is not. A bad outcome does not fail the test; a *changed* outcome does, because the goldens are committed. That makes the suite a regression detector for things like an SDK bump @@ -51,24 +51,25 @@ The `SUPPRESSED` reason string is the engine explaining itself, which is differe ## Value library `fields/.yml` supplies values the generic per-kind defaults cannot -guess — enums, ids, anything the backend constrains — and opts a resource type into -cloud runs: +guess — enums, ids, anything the backend constrains: ```yaml -cloud: true # include in cloud runs; off by default +slow: true # expensive on cloud; dropped only under -short skip: name: rename waits out an asynchronous delete fields: compute_size: [MEDIUM, LARGE] ``` -A field with no entry gets two values for its Go kind, which is enough to see a -value-to-value move on top of add and remove, so a file that only says `cloud: true` is -a normal thing to have. +Every resource type runs everywhere by default, so most types need no file at all. A +field with no entry gets two values for its Go kind, which is enough to see a +value-to-value move on top of add and remove. -`cloud` is off by default because a cloud run pays real API latency on every transition: -`jobs` alone takes ~17 minutes against a live workspace, against 40 seconds for the -whole suite locally. Turn it on for a resource type once you have checked what it costs. +`slow: true` marks a type whose cloud run is expensive — one that provisions compute, or +simply has a large field surface. It still runs on cloud; it is only dropped when +`-short` is passed, the same way `CloudSlow` narrows acceptance tests. So the PR cloud +leg (`task integration-short`) skips them and the nightly (`task integration`) covers +everything. `skip` is for a field no single-field edit can exercise — one that only validates as part of a set (a job's `git_source`), or whose change is correct but ruinously slow (an @@ -90,7 +91,7 @@ TestFields/schemas/schema.yml.tmpl/comment/x->absent ``` On cloud (`CLOUD_ENV` set) only resource types with `cloud: true` run, and the results -go to `out..fields..txt`, which is not committed: which values a +go to `output/..txt`, which is not committed: which values a real backend accepts depends on the workspace. ## Out of scope for now @@ -99,6 +100,6 @@ real backend accepts depends on the workspace. - `permissions` and `grants`, which are stripped from every config before planning: they are separate plan nodes describing an ACL, and leaving them in made every recreate report a drifted child against whichever field triggered it. Two configs that differ - only in those blocks therefore collapse to one, recorded in `out.configs.txt`. + only in those blocks therefore collapse to one, recorded in `output/configs.txt`. - configs with more than one resource, or with an `-init.sh` - fields under a slice or map, listed at the end of each report diff --git a/bundle/direct/dresources_test/fields/apps.yml b/bundle/direct/dresources_test/fields/apps.yml index 66c5f2ee38f..51f0cd657b7 100644 --- a/bundle/direct/dresources_test/fields/apps.yml +++ b/bundle/direct/dresources_test/fields/apps.yml @@ -1,14 +1,13 @@ # Value library for apps. See ../README.md. # -# Cloud is off: an app create provisions compute, so a per-transition rebuild costs -# minutes rather than milliseconds. -cloud: false - -fields: - compute_size: [MEDIUM, LARGE] +# Every app create provisions compute, so a cloud run costs minutes per rebuild. +slow: true skip: # Renaming an app recreates it, and an app delete is asynchronous: the name stays # reserved while the old app sits in DELETING, so every rename waits out the delete. # Recreate-on-rename is covered by acceptance/bundle/resources/apps. name: rename waits out an asynchronous delete + +fields: + compute_size: [MEDIUM, LARGE] diff --git a/bundle/direct/dresources_test/fields/catalogs.yml b/bundle/direct/dresources_test/fields/catalogs.yml deleted file mode 100644 index c4170e0883a..00000000000 --- a/bundle/direct/dresources_test/fields/catalogs.yml +++ /dev/null @@ -1,3 +0,0 @@ -# Value library for catalogs. See ../README.md. -# No explicit values needed; the per-kind defaults cover every field. -cloud: true diff --git a/bundle/direct/dresources_test/fields/experiments.yml b/bundle/direct/dresources_test/fields/experiments.yml deleted file mode 100644 index 89d9b583650..00000000000 --- a/bundle/direct/dresources_test/fields/experiments.yml +++ /dev/null @@ -1,3 +0,0 @@ -# Value library for experiments. See ../README.md. -# No explicit values needed; the per-kind defaults cover every field. -cloud: true diff --git a/bundle/direct/dresources_test/fields/jobs.yml b/bundle/direct/dresources_test/fields/jobs.yml index b75eba5361a..ff4ec827f1d 100644 --- a/bundle/direct/dresources_test/fields/jobs.yml +++ b/bundle/direct/dresources_test/fields/jobs.yml @@ -1,5 +1,6 @@ # Value library for jobs. See ../README.md. -cloud: true +# jobs is the largest surface here (1300+ observations), so it is slow on cloud. +slow: true skip: # git_source only validates as a coherent set: a provider, a url, and exactly one of diff --git a/bundle/direct/dresources_test/fields/model_serving_endpoints.yml b/bundle/direct/dresources_test/fields/model_serving_endpoints.yml index 1f36e5043a5..23c9a962991 100644 --- a/bundle/direct/dresources_test/fields/model_serving_endpoints.yml +++ b/bundle/direct/dresources_test/fields/model_serving_endpoints.yml @@ -1,7 +1,7 @@ # Value library for model serving endpoints. See ../README.md. # -# Cloud is off: creating an endpoint provisions serving compute. -cloud: false +# Creating an endpoint provisions serving compute. +slow: true skip: # The telemetry block is only accepted as a coherent whole, against a catalog and diff --git a/bundle/direct/dresources_test/fields/models.yml b/bundle/direct/dresources_test/fields/models.yml deleted file mode 100644 index 16d0966761f..00000000000 --- a/bundle/direct/dresources_test/fields/models.yml +++ /dev/null @@ -1,3 +0,0 @@ -# Value library for models. See ../README.md. -# No explicit values needed; the per-kind defaults cover every field. -cloud: true diff --git a/bundle/direct/dresources_test/fields/postgres_projects.yml b/bundle/direct/dresources_test/fields/postgres_projects.yml index 807a4650bb9..0df79c92b25 100644 --- a/bundle/direct/dresources_test/fields/postgres_projects.yml +++ b/bundle/direct/dresources_test/fields/postgres_projects.yml @@ -1,7 +1,7 @@ # Value library for postgres projects. See ../README.md. # -# Cloud is off: Lakebase is AWS-only and provisioning a project is slow. -cloud: false +# Provisioning a Lakebase project is slow, and it is AWS-only. +slow: true fields: # project_id is part of the resource's hierarchical name, so a change recreates it. diff --git a/bundle/direct/dresources_test/fields/registered_models.yml b/bundle/direct/dresources_test/fields/registered_models.yml deleted file mode 100644 index cff26271f0c..00000000000 --- a/bundle/direct/dresources_test/fields/registered_models.yml +++ /dev/null @@ -1,3 +0,0 @@ -# Value library for registered_models. See ../README.md. -# No explicit values needed; the per-kind defaults cover every field. -cloud: true diff --git a/bundle/direct/dresources_test/fields/schemas.yml b/bundle/direct/dresources_test/fields/schemas.yml index bd7c7c2115c..58cdd10c2f3 100644 --- a/bundle/direct/dresources_test/fields/schemas.yml +++ b/bundle/direct/dresources_test/fields/schemas.yml @@ -1,6 +1,4 @@ # Value library for schemas. See ../README.md. -cloud: true - skip: # storage_root must point at a real external location. storage_root: needs an external location diff --git a/bundle/direct/dresources_test/fields/volumes.yml b/bundle/direct/dresources_test/fields/volumes.yml index cd7d215618a..2fd5d790e7d 100644 --- a/bundle/direct/dresources_test/fields/volumes.yml +++ b/bundle/direct/dresources_test/fields/volumes.yml @@ -1,6 +1,4 @@ # Value library for volumes. See ../README.md. -cloud: true - skip: # An external volume needs a storage location backed by a real external location and # cloud IAM, which this suite does not provision. diff --git a/bundle/direct/dresources_test/fields_test.go b/bundle/direct/dresources_test/fields_test.go index a50bb48eba9..96c8fea77a0 100644 --- a/bundle/direct/dresources_test/fields_test.go +++ b/bundle/direct/dresources_test/fields_test.go @@ -61,8 +61,8 @@ func TestFields(t *testing.T) { fv, err := loadFieldValues(resourceType) require.NoError(t, err) - if isCloud() && !fv.cloud { - t.Skipf("not enabled for cloud runs; set cloud: true in fields/%s.yml", resourceType) + if isCloud() && fv.slow && testing.Short() { + t.Skipf("slow on cloud and -short was passed; the nightly run covers it") } // A testserver per type keeps the parallel runs from sharing workspace diff --git a/bundle/direct/dresources_test/harness_test.go b/bundle/direct/dresources_test/harness_test.go index 3b8735770e7..ec862aaeadb 100644 --- a/bundle/direct/dresources_test/harness_test.go +++ b/bundle/direct/dresources_test/harness_test.go @@ -173,11 +173,14 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC return nil, err } - // The caller's context, not t.Context(): a harness is rebuilt inside a field-level - // subtest but goes on being used by later fields, and t.Context() is cancelled the - // moment its subtest returns -- which surfaced as "context canceled" from the SDK - // rate limiter on whatever ran next. - ctx = dbr.MockRuntime(ctx, dbr.Environment{}) //exhaustruct:ignore + // WithoutCancel because a harness outlives the scope that created it in two ways, + // and t.Context() is cancelled at the end of both: a harness rebuilt inside a + // field-level subtest is reused by later fields, and every harness is destroyed from + // t.Cleanup, which runs *after* t.Context() is cancelled. Either way the SDK rate + // limiter refuses the call with "context canceled" -- silently leaking the resource + // in the cleanup case. Values (dbr, logdiag, cmdio, env) still propagate; the test + // binary's own -timeout remains the backstop. + ctx = dbr.MockRuntime(context.WithoutCancel(ctx), dbr.Environment{}) //exhaustruct:ignore // Thousands of deploys run through here, so cap how long any one of them waits for // a resource to become ready. Without a cap a resource that never reaches its // terminal state stalls the whole suite instead of showing up as one bad verdict. diff --git a/bundle/direct/dresources_test/out.alerts.fields.txt b/bundle/direct/dresources_test/output/alerts.txt similarity index 100% rename from bundle/direct/dresources_test/out.alerts.fields.txt rename to bundle/direct/dresources_test/output/alerts.txt diff --git a/bundle/direct/dresources_test/out.apps.fields.txt b/bundle/direct/dresources_test/output/apps.txt similarity index 100% rename from bundle/direct/dresources_test/out.apps.fields.txt rename to bundle/direct/dresources_test/output/apps.txt diff --git a/bundle/direct/dresources_test/out.catalogs.fields.txt b/bundle/direct/dresources_test/output/catalogs.txt similarity index 100% rename from bundle/direct/dresources_test/out.catalogs.fields.txt rename to bundle/direct/dresources_test/output/catalogs.txt diff --git a/bundle/direct/dresources_test/out.cluster_policies.fields.txt b/bundle/direct/dresources_test/output/cluster_policies.txt similarity index 100% rename from bundle/direct/dresources_test/out.cluster_policies.fields.txt rename to bundle/direct/dresources_test/output/cluster_policies.txt diff --git a/bundle/direct/dresources_test/out.clusters.fields.txt b/bundle/direct/dresources_test/output/clusters.txt similarity index 100% rename from bundle/direct/dresources_test/out.clusters.fields.txt rename to bundle/direct/dresources_test/output/clusters.txt diff --git a/bundle/direct/dresources_test/out.configs.txt b/bundle/direct/dresources_test/output/configs.txt similarity index 100% rename from bundle/direct/dresources_test/out.configs.txt rename to bundle/direct/dresources_test/output/configs.txt diff --git a/bundle/direct/dresources_test/out.dashboards.fields.txt b/bundle/direct/dresources_test/output/dashboards.txt similarity index 100% rename from bundle/direct/dresources_test/out.dashboards.fields.txt rename to bundle/direct/dresources_test/output/dashboards.txt diff --git a/bundle/direct/dresources_test/out.database_instances.fields.txt b/bundle/direct/dresources_test/output/database_instances.txt similarity index 100% rename from bundle/direct/dresources_test/out.database_instances.fields.txt rename to bundle/direct/dresources_test/output/database_instances.txt diff --git a/bundle/direct/dresources_test/out.experiments.fields.txt b/bundle/direct/dresources_test/output/experiments.txt similarity index 100% rename from bundle/direct/dresources_test/out.experiments.fields.txt rename to bundle/direct/dresources_test/output/experiments.txt diff --git a/bundle/direct/dresources_test/out.external_locations.fields.txt b/bundle/direct/dresources_test/output/external_locations.txt similarity index 100% rename from bundle/direct/dresources_test/out.external_locations.fields.txt rename to bundle/direct/dresources_test/output/external_locations.txt diff --git a/bundle/direct/dresources_test/out.genie_spaces.fields.txt b/bundle/direct/dresources_test/output/genie_spaces.txt similarity index 100% rename from bundle/direct/dresources_test/out.genie_spaces.fields.txt rename to bundle/direct/dresources_test/output/genie_spaces.txt diff --git a/bundle/direct/dresources_test/out.instance_pools.fields.txt b/bundle/direct/dresources_test/output/instance_pools.txt similarity index 100% rename from bundle/direct/dresources_test/out.instance_pools.fields.txt rename to bundle/direct/dresources_test/output/instance_pools.txt diff --git a/bundle/direct/dresources_test/out.jobs.fields.txt b/bundle/direct/dresources_test/output/jobs.txt similarity index 100% rename from bundle/direct/dresources_test/out.jobs.fields.txt rename to bundle/direct/dresources_test/output/jobs.txt diff --git a/bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt b/bundle/direct/dresources_test/output/model_serving_endpoints.txt similarity index 100% rename from bundle/direct/dresources_test/out.model_serving_endpoints.fields.txt rename to bundle/direct/dresources_test/output/model_serving_endpoints.txt diff --git a/bundle/direct/dresources_test/out.models.fields.txt b/bundle/direct/dresources_test/output/models.txt similarity index 100% rename from bundle/direct/dresources_test/out.models.fields.txt rename to bundle/direct/dresources_test/output/models.txt diff --git a/bundle/direct/dresources_test/out.pipelines.fields.txt b/bundle/direct/dresources_test/output/pipelines.txt similarity index 100% rename from bundle/direct/dresources_test/out.pipelines.fields.txt rename to bundle/direct/dresources_test/output/pipelines.txt diff --git a/bundle/direct/dresources_test/out.postgres_projects.fields.txt b/bundle/direct/dresources_test/output/postgres_projects.txt similarity index 100% rename from bundle/direct/dresources_test/out.postgres_projects.fields.txt rename to bundle/direct/dresources_test/output/postgres_projects.txt diff --git a/bundle/direct/dresources_test/out.postgres_synced_tables.fields.txt b/bundle/direct/dresources_test/output/postgres_synced_tables.txt similarity index 100% rename from bundle/direct/dresources_test/out.postgres_synced_tables.fields.txt rename to bundle/direct/dresources_test/output/postgres_synced_tables.txt diff --git a/bundle/direct/dresources_test/out.registered_models.fields.txt b/bundle/direct/dresources_test/output/registered_models.txt similarity index 100% rename from bundle/direct/dresources_test/out.registered_models.fields.txt rename to bundle/direct/dresources_test/output/registered_models.txt diff --git a/bundle/direct/dresources_test/out.schemas.fields.txt b/bundle/direct/dresources_test/output/schemas.txt similarity index 100% rename from bundle/direct/dresources_test/out.schemas.fields.txt rename to bundle/direct/dresources_test/output/schemas.txt diff --git a/bundle/direct/dresources_test/out.secret_scopes.fields.txt b/bundle/direct/dresources_test/output/secret_scopes.txt similarity index 100% rename from bundle/direct/dresources_test/out.secret_scopes.fields.txt rename to bundle/direct/dresources_test/output/secret_scopes.txt diff --git a/bundle/direct/dresources_test/out.sql_warehouses.fields.txt b/bundle/direct/dresources_test/output/sql_warehouses.txt similarity index 100% rename from bundle/direct/dresources_test/out.sql_warehouses.fields.txt rename to bundle/direct/dresources_test/output/sql_warehouses.txt diff --git a/bundle/direct/dresources_test/out.volumes.fields.txt b/bundle/direct/dresources_test/output/volumes.txt similarity index 100% rename from bundle/direct/dresources_test/out.volumes.fields.txt rename to bundle/direct/dresources_test/output/volumes.txt diff --git a/bundle/direct/dresources_test/report_test.go b/bundle/direct/dresources_test/report_test.go index ec9a1cb0d18..b1d8ebb71b2 100644 --- a/bundle/direct/dresources_test/report_test.go +++ b/bundle/direct/dresources_test/report_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io/fs" "os" + "path/filepath" "slices" "strings" @@ -151,21 +152,16 @@ func (r *report) render() string { // Cloud runs go to a separate, uncommitted file: which values a real backend accepts // depends on the workspace, so those results are for reading, not for diffing. func (r *report) write(t testutil.TestingT) { - name := "out." + r.resourceType + ".fields.txt" + name := reportPath(r.resourceType + ".txt") body := r.render() if isCloud() { - name = "out." + r.resourceType + ".fields." + cloudName() + ".txt" - if err := os.WriteFile(name, []byte(body), 0o644); err != nil { - t.Errorf("writing %s: %s", name, err) - } + writeReport(t, reportPath(r.resourceType+"."+cloudName()+".txt"), body) return } if testdiff.OverwriteMode { - if err := os.WriteFile(name, []byte(body), 0o644); err != nil { - t.Errorf("writing %s: %s", name, err) - } + writeReport(t, name, body) return } @@ -177,6 +173,16 @@ func (r *report) write(t testutil.TestingT) { testdiff.AssertEqualTexts(t, name, name, testdiff.NormalizeNewlines(string(expected)), body) } +func writeReport(t testutil.TestingT, name, body string) { + if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil { + t.Errorf("creating %s: %s", filepath.Dir(name), err) + return + } + if err := os.WriteFile(name, []byte(body), 0o644); err != nil { + t.Errorf("writing %s: %s", name, err) + } +} + // writeCorpusReport records which configs the suite drives and why each of the others // is left out, so the catalog accounts for the whole corpus rather than just the part // it happens to cover. @@ -197,15 +203,13 @@ func writeCorpusReport(t testutil.TestingT, usable []testConfig, skipped map[str fmt.Fprintf(&sb, "%-48s %s\n", name, skipped[name]) } - const name = "out.configs.txt" + name := reportPath("configs.txt") body := sb.String() if isCloud() { return } if testdiff.OverwriteMode { - if err := os.WriteFile(name, []byte(body), 0o644); err != nil { - t.Errorf("writing %s: %s", name, err) - } + writeReport(t, name, body) return } expected, err := os.ReadFile(name) @@ -216,6 +220,14 @@ func writeCorpusReport(t testutil.TestingT, usable []testConfig, skipped map[str testdiff.AssertEqualTexts(t, name, name, testdiff.NormalizeNewlines(string(expected)), body) } +// outputDir holds the committed reports. Kept out of the package directory so the Go +// files stay readable next to two dozen goldens. +const outputDir = "output" + +func reportPath(name string) string { + return filepath.Join(outputDir, name) +} + func cloudName() string { switch env := os.Getenv("CLOUD_ENV"); env { case "ucws": diff --git a/bundle/direct/dresources_test/values_test.go b/bundle/direct/dresources_test/values_test.go index ff13cade752..0595d8c5b60 100644 --- a/bundle/direct/dresources_test/values_test.go +++ b/bundle/direct/dresources_test/values_test.go @@ -29,10 +29,11 @@ var absent = dyn.InvalidValue // constrains (enums, ids, cross-referenced names) need an explicit entry, otherwise // every value is rejected and the field reports only BACKEND_ERROR. type fieldValues struct { - // cloud enables this resource type in cloud runs. Off by default: a cloud run pays - // real API latency per transition, so a type opts in once someone has checked that - // it completes in reasonable time. - cloud bool + // slow marks a resource type whose cloud run is expensive -- one that provisions + // compute, say. It still runs on cloud; it is only dropped under -short, the same + // way CloudSlow narrows acceptance tests, so PR cloud runs stay bounded and the + // nightly covers everything. + slow bool // skip lists field paths to leave out entirely, with a reason. A key ending in ".*" // skips that whole subtree. @@ -46,7 +47,7 @@ type fieldValues struct { // yamlloader rather than a yaml package, so the values arrive as dyn.Value -- the same // representation the bundle config uses, which is what they are written into. func loadFieldValues(resourceType string) (*fieldValues, error) { - fv := &fieldValues{cloud: false, skip: map[string]string{}, fields: map[string][]dyn.Value{}} + fv := &fieldValues{slow: false, skip: map[string]string{}, fields: map[string][]dyn.Value{}} path := filepath.Join("fields", resourceType+".yml") data, err := os.ReadFile(path) @@ -62,8 +63,8 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { return nil, fmt.Errorf("%s: %w", path, err) } - if v, err := dyn.Get(root, "cloud"); err == nil { - fv.cloud, _ = v.AsBool() + if v, err := dyn.Get(root, "slow"); err == nil { + fv.slow, _ = v.AsBool() } if v, err := dyn.Get(root, "skip"); err == nil { m, _ := v.AsMap() From 80c8175183b5aa8b11dc1e2bed7a7dd3be14e7d7 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 12:36:53 +0200 Subject: [PATCH 008/118] Move the field catalog to bundle/direct/tests It drives CalculatePlan and Apply, so it tests the engine rather than any one resource implementation, and it belongs beside the engine instead of under dresources. The old dresources_test directory name also misapplied Go's _test package-name convention to a directory. Value libraries move to testdata/fields, which the go tool ignores by name. Goldens stay in output/. Every file in the package is a _test.go, so GoFiles is empty and none of this reaches the binary. Co-authored-by: Isaac --- Taskfile.yml | 2 +- bundle/direct/{dresources_test => tests}/.gitignore | 0 bundle/direct/{dresources_test => tests}/README.md | 6 +++--- .../direct/{dresources_test => tests}/corpus_test.go | 2 +- .../direct/{dresources_test => tests}/fields_test.go | 12 ++++++++---- .../{dresources_test => tests}/harness_test.go | 2 +- .../{dresources_test => tests}/output/alerts.txt | 0 .../{dresources_test => tests}/output/apps.txt | 0 .../{dresources_test => tests}/output/catalogs.txt | 0 .../output/cluster_policies.txt | 0 .../{dresources_test => tests}/output/clusters.txt | 0 .../{dresources_test => tests}/output/configs.txt | 0 .../{dresources_test => tests}/output/dashboards.txt | 0 .../output/database_instances.txt | 0 .../output/experiments.txt | 0 .../output/external_locations.txt | 0 .../output/genie_spaces.txt | 0 .../output/instance_pools.txt | 0 .../{dresources_test => tests}/output/jobs.txt | 0 .../output/model_serving_endpoints.txt | 0 .../{dresources_test => tests}/output/models.txt | 0 .../{dresources_test => tests}/output/pipelines.txt | 0 .../output/postgres_projects.txt | 0 .../output/postgres_synced_tables.txt | 0 .../output/registered_models.txt | 0 .../{dresources_test => tests}/output/schemas.txt | 0 .../output/secret_scopes.txt | 0 .../output/sql_warehouses.txt | 0 .../{dresources_test => tests}/output/volumes.txt | 0 .../direct/{dresources_test => tests}/report_test.go | 2 +- .../testdata}/fields/apps.yml | 0 .../testdata}/fields/jobs.yml | 0 .../testdata}/fields/model_serving_endpoints.yml | 0 .../testdata}/fields/postgres_projects.yml | 0 .../testdata}/fields/schemas.yml | 0 .../testdata}/fields/volumes.yml | 0 .../direct/{dresources_test => tests}/values_test.go | 11 +++++++---- 37 files changed, 22 insertions(+), 15 deletions(-) rename bundle/direct/{dresources_test => tests}/.gitignore (100%) rename bundle/direct/{dresources_test => tests}/README.md (95%) rename bundle/direct/{dresources_test => tests}/corpus_test.go (99%) rename bundle/direct/{dresources_test => tests}/fields_test.go (96%) rename bundle/direct/{dresources_test => tests}/harness_test.go (99%) rename bundle/direct/{dresources_test => tests}/output/alerts.txt (100%) rename bundle/direct/{dresources_test => tests}/output/apps.txt (100%) rename bundle/direct/{dresources_test => tests}/output/catalogs.txt (100%) rename bundle/direct/{dresources_test => tests}/output/cluster_policies.txt (100%) rename bundle/direct/{dresources_test => tests}/output/clusters.txt (100%) rename bundle/direct/{dresources_test => tests}/output/configs.txt (100%) rename bundle/direct/{dresources_test => tests}/output/dashboards.txt (100%) rename bundle/direct/{dresources_test => tests}/output/database_instances.txt (100%) rename bundle/direct/{dresources_test => tests}/output/experiments.txt (100%) rename bundle/direct/{dresources_test => tests}/output/external_locations.txt (100%) rename bundle/direct/{dresources_test => tests}/output/genie_spaces.txt (100%) rename bundle/direct/{dresources_test => tests}/output/instance_pools.txt (100%) rename bundle/direct/{dresources_test => tests}/output/jobs.txt (100%) rename bundle/direct/{dresources_test => tests}/output/model_serving_endpoints.txt (100%) rename bundle/direct/{dresources_test => tests}/output/models.txt (100%) rename bundle/direct/{dresources_test => tests}/output/pipelines.txt (100%) rename bundle/direct/{dresources_test => tests}/output/postgres_projects.txt (100%) rename bundle/direct/{dresources_test => tests}/output/postgres_synced_tables.txt (100%) rename bundle/direct/{dresources_test => tests}/output/registered_models.txt (100%) rename bundle/direct/{dresources_test => tests}/output/schemas.txt (100%) rename bundle/direct/{dresources_test => tests}/output/secret_scopes.txt (100%) rename bundle/direct/{dresources_test => tests}/output/sql_warehouses.txt (100%) rename bundle/direct/{dresources_test => tests}/output/volumes.txt (100%) rename bundle/direct/{dresources_test => tests}/report_test.go (99%) rename bundle/direct/{dresources_test => tests/testdata}/fields/apps.yml (100%) rename bundle/direct/{dresources_test => tests/testdata}/fields/jobs.yml (100%) rename bundle/direct/{dresources_test => tests/testdata}/fields/model_serving_endpoints.yml (100%) rename bundle/direct/{dresources_test => tests/testdata}/fields/postgres_projects.yml (100%) rename bundle/direct/{dresources_test => tests/testdata}/fields/schemas.yml (100%) rename bundle/direct/{dresources_test => tests/testdata}/fields/volumes.yml (100%) rename bundle/direct/{dresources_test => tests}/values_test.go (95%) diff --git a/Taskfile.yml b/Taskfile.yml index b00dac1cc77..b0aaa42ef9b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -10,7 +10,7 @@ vars: TEST_PACKAGES: ./acceptance/internal ./libs/... ./internal/... ./cmd/... ./bundle/... ./experimental/ssh/... . # Field-support catalog. Covered by TEST_PACKAGES for local/CI runs via ./bundle/..., # but named separately because the cloud tasks below list their packages explicitly. - FIELDS_PACKAGE: ./bundle/direct/dresources_test + FIELDS_PACKAGE: ./bundle/direct/tests ACCEPTANCE_TEST_FILTER: "" # Single brace-expansion glob covering every //go:embed target in the repo, # computed by grepping `//go:embed` directives. Evaluated lazily by Task so diff --git a/bundle/direct/dresources_test/.gitignore b/bundle/direct/tests/.gitignore similarity index 100% rename from bundle/direct/dresources_test/.gitignore rename to bundle/direct/tests/.gitignore diff --git a/bundle/direct/dresources_test/README.md b/bundle/direct/tests/README.md similarity index 95% rename from bundle/direct/dresources_test/README.md rename to bundle/direct/tests/README.md index 880eea0ec77..171db7bff62 100644 --- a/bundle/direct/dresources_test/README.md +++ b/bundle/direct/tests/README.md @@ -50,7 +50,7 @@ The `SUPPRESSED` reason string is the engine explaining itself, which is differe ## Value library -`fields/.yml` supplies values the generic per-kind defaults cannot +`testdata/fields/.yml` supplies values the generic per-kind defaults cannot guess — enums, ids, anything the backend constrains: ```yaml @@ -79,8 +79,8 @@ the report as `SKIPPED` so it is not mistaken for coverage. ## Running it ```bash -go test ./bundle/direct/dresources_test # against the testserver -go test ./bundle/direct/dresources_test -run 'TestFields/schemas' -v +go test ./bundle/direct/tests # against the testserver +go test ./bundle/direct/tests -run 'TestFields/schemas' -v ./task test-update-fields # regenerate the goldens ``` diff --git a/bundle/direct/dresources_test/corpus_test.go b/bundle/direct/tests/corpus_test.go similarity index 99% rename from bundle/direct/dresources_test/corpus_test.go rename to bundle/direct/tests/corpus_test.go index b922f408e1a..2279536d9f7 100644 --- a/bundle/direct/dresources_test/corpus_test.go +++ b/bundle/direct/tests/corpus_test.go @@ -1,4 +1,4 @@ -package dresources_test +package tests import ( "encoding/json" diff --git a/bundle/direct/dresources_test/fields_test.go b/bundle/direct/tests/fields_test.go similarity index 96% rename from bundle/direct/dresources_test/fields_test.go rename to bundle/direct/tests/fields_test.go index 96c8fea77a0..d1ee90ea105 100644 --- a/bundle/direct/dresources_test/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -1,5 +1,9 @@ -// Package dresources_test catalogs how the direct engine handles a change to every -// field a user can set on a bundle resource. +// Package tests exercises the direct engine end to end -- plan, apply, and the plan +// that follows -- rather than any one resource implementation. It lives beside the +// engine for that reason, not under dresources. +// +// TestFields catalogs how the engine handles a change to every field a user can set on +// a bundle resource. // // For each resource it deploys a base config once, then walks the resource's input // struct and, per field, moves the field through every ordered pair of a small set of @@ -11,7 +15,7 @@ // dominated by bundle-file sync, and there are thousands of permutations. // // A bad outcome does not fail the test. The suite is a catalog: every result lands in -// out..fields.txt, and that golden is the report. A regression shows up +// output/.txt, and that golden is the report. A regression shows up // as a diff in it -- for instance after an SDK bump moves a field's type. // // Permissions and grants are out of scope, and are stripped from every config before @@ -23,7 +27,7 @@ // - remote drift (a change made outside the bundle); this suite only edits config // - configs with more than one resource, or with an -init.sh // - fields under a slice or map (listed at the end of each report) -package dresources_test +package tests import ( "context" diff --git a/bundle/direct/dresources_test/harness_test.go b/bundle/direct/tests/harness_test.go similarity index 99% rename from bundle/direct/dresources_test/harness_test.go rename to bundle/direct/tests/harness_test.go index ec862aaeadb..1afb1dc0a9a 100644 --- a/bundle/direct/dresources_test/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -1,4 +1,4 @@ -package dresources_test +package tests import ( "context" diff --git a/bundle/direct/dresources_test/output/alerts.txt b/bundle/direct/tests/output/alerts.txt similarity index 100% rename from bundle/direct/dresources_test/output/alerts.txt rename to bundle/direct/tests/output/alerts.txt diff --git a/bundle/direct/dresources_test/output/apps.txt b/bundle/direct/tests/output/apps.txt similarity index 100% rename from bundle/direct/dresources_test/output/apps.txt rename to bundle/direct/tests/output/apps.txt diff --git a/bundle/direct/dresources_test/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt similarity index 100% rename from bundle/direct/dresources_test/output/catalogs.txt rename to bundle/direct/tests/output/catalogs.txt diff --git a/bundle/direct/dresources_test/output/cluster_policies.txt b/bundle/direct/tests/output/cluster_policies.txt similarity index 100% rename from bundle/direct/dresources_test/output/cluster_policies.txt rename to bundle/direct/tests/output/cluster_policies.txt diff --git a/bundle/direct/dresources_test/output/clusters.txt b/bundle/direct/tests/output/clusters.txt similarity index 100% rename from bundle/direct/dresources_test/output/clusters.txt rename to bundle/direct/tests/output/clusters.txt diff --git a/bundle/direct/dresources_test/output/configs.txt b/bundle/direct/tests/output/configs.txt similarity index 100% rename from bundle/direct/dresources_test/output/configs.txt rename to bundle/direct/tests/output/configs.txt diff --git a/bundle/direct/dresources_test/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt similarity index 100% rename from bundle/direct/dresources_test/output/dashboards.txt rename to bundle/direct/tests/output/dashboards.txt diff --git a/bundle/direct/dresources_test/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt similarity index 100% rename from bundle/direct/dresources_test/output/database_instances.txt rename to bundle/direct/tests/output/database_instances.txt diff --git a/bundle/direct/dresources_test/output/experiments.txt b/bundle/direct/tests/output/experiments.txt similarity index 100% rename from bundle/direct/dresources_test/output/experiments.txt rename to bundle/direct/tests/output/experiments.txt diff --git a/bundle/direct/dresources_test/output/external_locations.txt b/bundle/direct/tests/output/external_locations.txt similarity index 100% rename from bundle/direct/dresources_test/output/external_locations.txt rename to bundle/direct/tests/output/external_locations.txt diff --git a/bundle/direct/dresources_test/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt similarity index 100% rename from bundle/direct/dresources_test/output/genie_spaces.txt rename to bundle/direct/tests/output/genie_spaces.txt diff --git a/bundle/direct/dresources_test/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt similarity index 100% rename from bundle/direct/dresources_test/output/instance_pools.txt rename to bundle/direct/tests/output/instance_pools.txt diff --git a/bundle/direct/dresources_test/output/jobs.txt b/bundle/direct/tests/output/jobs.txt similarity index 100% rename from bundle/direct/dresources_test/output/jobs.txt rename to bundle/direct/tests/output/jobs.txt diff --git a/bundle/direct/dresources_test/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt similarity index 100% rename from bundle/direct/dresources_test/output/model_serving_endpoints.txt rename to bundle/direct/tests/output/model_serving_endpoints.txt diff --git a/bundle/direct/dresources_test/output/models.txt b/bundle/direct/tests/output/models.txt similarity index 100% rename from bundle/direct/dresources_test/output/models.txt rename to bundle/direct/tests/output/models.txt diff --git a/bundle/direct/dresources_test/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt similarity index 100% rename from bundle/direct/dresources_test/output/pipelines.txt rename to bundle/direct/tests/output/pipelines.txt diff --git a/bundle/direct/dresources_test/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt similarity index 100% rename from bundle/direct/dresources_test/output/postgres_projects.txt rename to bundle/direct/tests/output/postgres_projects.txt diff --git a/bundle/direct/dresources_test/output/postgres_synced_tables.txt b/bundle/direct/tests/output/postgres_synced_tables.txt similarity index 100% rename from bundle/direct/dresources_test/output/postgres_synced_tables.txt rename to bundle/direct/tests/output/postgres_synced_tables.txt diff --git a/bundle/direct/dresources_test/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt similarity index 100% rename from bundle/direct/dresources_test/output/registered_models.txt rename to bundle/direct/tests/output/registered_models.txt diff --git a/bundle/direct/dresources_test/output/schemas.txt b/bundle/direct/tests/output/schemas.txt similarity index 100% rename from bundle/direct/dresources_test/output/schemas.txt rename to bundle/direct/tests/output/schemas.txt diff --git a/bundle/direct/dresources_test/output/secret_scopes.txt b/bundle/direct/tests/output/secret_scopes.txt similarity index 100% rename from bundle/direct/dresources_test/output/secret_scopes.txt rename to bundle/direct/tests/output/secret_scopes.txt diff --git a/bundle/direct/dresources_test/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt similarity index 100% rename from bundle/direct/dresources_test/output/sql_warehouses.txt rename to bundle/direct/tests/output/sql_warehouses.txt diff --git a/bundle/direct/dresources_test/output/volumes.txt b/bundle/direct/tests/output/volumes.txt similarity index 100% rename from bundle/direct/dresources_test/output/volumes.txt rename to bundle/direct/tests/output/volumes.txt diff --git a/bundle/direct/dresources_test/report_test.go b/bundle/direct/tests/report_test.go similarity index 99% rename from bundle/direct/dresources_test/report_test.go rename to bundle/direct/tests/report_test.go index b1d8ebb71b2..c75fbe65f9d 100644 --- a/bundle/direct/dresources_test/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -1,4 +1,4 @@ -package dresources_test +package tests import ( "cmp" diff --git a/bundle/direct/dresources_test/fields/apps.yml b/bundle/direct/tests/testdata/fields/apps.yml similarity index 100% rename from bundle/direct/dresources_test/fields/apps.yml rename to bundle/direct/tests/testdata/fields/apps.yml diff --git a/bundle/direct/dresources_test/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml similarity index 100% rename from bundle/direct/dresources_test/fields/jobs.yml rename to bundle/direct/tests/testdata/fields/jobs.yml diff --git a/bundle/direct/dresources_test/fields/model_serving_endpoints.yml b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml similarity index 100% rename from bundle/direct/dresources_test/fields/model_serving_endpoints.yml rename to bundle/direct/tests/testdata/fields/model_serving_endpoints.yml diff --git a/bundle/direct/dresources_test/fields/postgres_projects.yml b/bundle/direct/tests/testdata/fields/postgres_projects.yml similarity index 100% rename from bundle/direct/dresources_test/fields/postgres_projects.yml rename to bundle/direct/tests/testdata/fields/postgres_projects.yml diff --git a/bundle/direct/dresources_test/fields/schemas.yml b/bundle/direct/tests/testdata/fields/schemas.yml similarity index 100% rename from bundle/direct/dresources_test/fields/schemas.yml rename to bundle/direct/tests/testdata/fields/schemas.yml diff --git a/bundle/direct/dresources_test/fields/volumes.yml b/bundle/direct/tests/testdata/fields/volumes.yml similarity index 100% rename from bundle/direct/dresources_test/fields/volumes.yml rename to bundle/direct/tests/testdata/fields/volumes.yml diff --git a/bundle/direct/dresources_test/values_test.go b/bundle/direct/tests/values_test.go similarity index 95% rename from bundle/direct/dresources_test/values_test.go rename to bundle/direct/tests/values_test.go index 0595d8c5b60..d2861547917 100644 --- a/bundle/direct/dresources_test/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -1,4 +1,4 @@ -package dresources_test +package tests import ( "bytes" @@ -23,7 +23,7 @@ import ( // with absent on one side. var absent = dyn.InvalidValue -// fieldValues is the per-resource-type value library, e.g. fields/schemas.yml. +// fieldValues is the per-resource-type value library, e.g. testdata/fields/schemas.yml. // // A field with no entry falls back to defaultValues for its Go kind. Fields the backend // constrains (enums, ids, cross-referenced names) need an explicit entry, otherwise @@ -43,13 +43,16 @@ type fieldValues struct { fields map[string][]dyn.Value } -// loadFieldValues reads fields/.yml. Parsing goes through the repo's own +// fieldsDir holds the per-resource-type value libraries. +const fieldsDir = "testdata/fields" + +// loadFieldValues reads testdata/fields/.yml. Parsing goes through the repo's own // yamlloader rather than a yaml package, so the values arrive as dyn.Value -- the same // representation the bundle config uses, which is what they are written into. func loadFieldValues(resourceType string) (*fieldValues, error) { fv := &fieldValues{slow: false, skip: map[string]string{}, fields: map[string][]dyn.Value{}} - path := filepath.Join("fields", resourceType+".yml") + path := filepath.Join(fieldsDir, resourceType+".yml") data, err := os.ReadFile(path) if errors.Is(err, fs.ErrNotExist) { return fv, nil From 5056bd4546699db683a0789153ed36ce7a5691a3 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 12:56:45 +0200 Subject: [PATCH 009/118] Report only findings, and make one case re-runnable The committed output/.txt now lists just the results worth looking at, one line each, with from and to as their own columns. output/.full.txt keeps everything plus the summary and the not-covered list, and is gitignored since it moves whenever a passing row does. 304 findings out of 5442 observations. A SUPPRESSED whose reason is "empty" is not one of them: both sides were empty, so nothing the user expressed was lost. DRIFT becomes POST_DEPLOY_DRIFT, which is what it measures. Transition subtests are named absent_to_true rather than with an arrow, so a single case re-runs without shell quoting, and now -v prints the post-deploy plan behind any finding. Dropped the slow flag: every resource type runs everywhere. Renaming an app is a real operation, so it is no longer skipped for being slow. It records DEPLOY_ERROR "timed out: app is deleting", which is the finding. A per-operation deadline backs that up with a TIMEOUT verdict. Main fixed clearing a schema comment, so that test becomes clear_field: it now asserts comment converges and records that custom_max_retention_hours does not. Co-authored-by: Isaac --- .../schemas/drift/clear_comment/script | 28 - .../schemas/drift/clear_comment/test.toml | 5 - .../databricks.yml | 3 +- .../out.test.toml | 0 .../{clear_comment => clear_field}/output.txt | 48 +- .../schemas/drift/clear_field/script | 22 + .../schemas/drift/clear_field/test.toml | 8 + bundle/direct/tests/README.md | 33 +- bundle/direct/tests/fields_test.go | 46 +- bundle/direct/tests/harness_test.go | 32 +- bundle/direct/tests/output/alerts.full.txt | 201 ++ bundle/direct/tests/output/alerts.txt | 206 +- bundle/direct/tests/output/apps.full.txt | 568 +++++ bundle/direct/tests/output/apps.txt | 598 +---- bundle/direct/tests/output/catalogs.full.txt | 155 ++ bundle/direct/tests/output/catalogs.txt | 156 +- .../tests/output/cluster_policies.full.txt | 64 + .../direct/tests/output/cluster_policies.txt | 64 - bundle/direct/tests/output/clusters.full.txt | 777 +++++++ bundle/direct/tests/output/clusters.txt | 778 +------ .../direct/tests/output/dashboards.full.txt | 97 + bundle/direct/tests/output/dashboards.txt | 110 +- .../tests/output/database_instances.full.txt | 186 ++ .../tests/output/database_instances.txt | 305 +-- .../direct/tests/output/experiments.full.txt | 44 + bundle/direct/tests/output/experiments.txt | 44 - .../tests/output/external_locations.full.txt | 263 +++ .../tests/output/external_locations.txt | 263 --- .../direct/tests/output/genie_spaces.full.txt | 55 + bundle/direct/tests/output/genie_spaces.txt | 72 +- .../tests/output/instance_pools.full.txt | 162 ++ bundle/direct/tests/output/instance_pools.txt | 165 +- bundle/direct/tests/output/jobs.full.txt | 2055 ++++++++++++++++ bundle/direct/tests/output/jobs.txt | 2068 +---------------- .../output/model_serving_endpoints.full.txt | 334 +++ .../tests/output/model_serving_endpoints.txt | 348 +-- bundle/direct/tests/output/models.full.txt | 28 + bundle/direct/tests/output/models.txt | 28 - bundle/direct/tests/output/pipelines.full.txt | 1406 +++++++++++ bundle/direct/tests/output/pipelines.txt | 1425 +----------- .../tests/output/postgres_projects.full.txt | 80 + .../direct/tests/output/postgres_projects.txt | 99 +- .../output/postgres_synced_tables.full.txt | 96 + .../tests/output/postgres_synced_tables.txt | 96 - .../tests/output/registered_models.full.txt | 102 + .../direct/tests/output/registered_models.txt | 109 +- bundle/direct/tests/output/schemas.full.txt | 80 + bundle/direct/tests/output/schemas.txt | 81 +- .../tests/output/secret_scopes.full.txt | 49 + bundle/direct/tests/output/secret_scopes.txt | 49 - .../tests/output/sql_warehouses.full.txt | 111 + bundle/direct/tests/output/sql_warehouses.txt | 112 +- bundle/direct/tests/output/volumes.full.txt | 51 + bundle/direct/tests/output/volumes.txt | 51 - bundle/direct/tests/report_test.go | 82 +- bundle/direct/tests/testdata/fields/apps.yml | 9 - bundle/direct/tests/testdata/fields/jobs.yml | 3 - .../fields/model_serving_endpoints.yml | 4 - .../testdata/fields/postgres_projects.yml | 4 - bundle/direct/tests/values_test.go | 17 +- 60 files changed, 7464 insertions(+), 7071 deletions(-) delete mode 100644 acceptance/bundle/resources/schemas/drift/clear_comment/script delete mode 100644 acceptance/bundle/resources/schemas/drift/clear_comment/test.toml rename acceptance/bundle/resources/schemas/drift/{clear_comment => clear_field}/databricks.yml (63%) rename acceptance/bundle/resources/schemas/drift/{clear_comment => clear_field}/out.test.toml (100%) rename acceptance/bundle/resources/schemas/drift/{clear_comment => clear_field}/output.txt (55%) create mode 100644 acceptance/bundle/resources/schemas/drift/clear_field/script create mode 100644 acceptance/bundle/resources/schemas/drift/clear_field/test.toml create mode 100644 bundle/direct/tests/output/alerts.full.txt create mode 100644 bundle/direct/tests/output/apps.full.txt create mode 100644 bundle/direct/tests/output/catalogs.full.txt create mode 100644 bundle/direct/tests/output/cluster_policies.full.txt create mode 100644 bundle/direct/tests/output/clusters.full.txt create mode 100644 bundle/direct/tests/output/dashboards.full.txt create mode 100644 bundle/direct/tests/output/database_instances.full.txt create mode 100644 bundle/direct/tests/output/experiments.full.txt create mode 100644 bundle/direct/tests/output/external_locations.full.txt create mode 100644 bundle/direct/tests/output/genie_spaces.full.txt create mode 100644 bundle/direct/tests/output/instance_pools.full.txt create mode 100644 bundle/direct/tests/output/jobs.full.txt create mode 100644 bundle/direct/tests/output/model_serving_endpoints.full.txt create mode 100644 bundle/direct/tests/output/models.full.txt create mode 100644 bundle/direct/tests/output/pipelines.full.txt create mode 100644 bundle/direct/tests/output/postgres_projects.full.txt create mode 100644 bundle/direct/tests/output/postgres_synced_tables.full.txt create mode 100644 bundle/direct/tests/output/registered_models.full.txt create mode 100644 bundle/direct/tests/output/schemas.full.txt create mode 100644 bundle/direct/tests/output/secret_scopes.full.txt create mode 100644 bundle/direct/tests/output/sql_warehouses.full.txt create mode 100644 bundle/direct/tests/output/volumes.full.txt diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/script b/acceptance/bundle/resources/schemas/drift/clear_comment/script deleted file mode 100644 index 32f9893dd89..00000000000 --- a/acceptance/bundle/resources/schemas/drift/clear_comment/script +++ /dev/null @@ -1,28 +0,0 @@ -# BUG: removing a schema field from the config never clears it remotely, so the deploy -# never converges. Every field of catalog.UpdateSchema is omitempty and DoUpdate carries -# only the config's own ForceSendFields (bundle/direct/dresources/schema.go), so an -# absent comment is dropped from the PATCH body and UC keeps the old value. The PATCH -# that gets sent is literally empty, and the plan re-proposes the same update forever. -# -# This test records the broken behaviour. Once it is fixed the "still drifting" step -# below reports no changes and this golden has to be updated -- that is the point. - -title "Initial deployment" -trace $CLI bundle deploy - -title "Remove the comment from the config" -trace update_file.py databricks.yml " comment: base comment" "" - -title "Plan proposes clearing it" -trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete" -trace $CLI bundle plan --output json | jq '.plan["resources.schemas.schema1"].changes' - -title "Deploy reports success" -trace $CLI bundle deploy - -title "Still drifting: the same update is proposed again" -trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete" -trace $CLI bundle plan --output json | jq '.plan["resources.schemas.schema1"].changes' - -title "The update PATCH carried an empty body, which is why nothing changed" -trace print_requests.py //unity --sort diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/test.toml b/acceptance/bundle/resources/schemas/drift/clear_comment/test.toml deleted file mode 100644 index 76fba7271dc..00000000000 --- a/acceptance/bundle/resources/schemas/drift/clear_comment/test.toml +++ /dev/null @@ -1,5 +0,0 @@ -Cloud = true - -# Terraform issues a spurious PATCH for enable_predictive_optimization on every -# deploy, which would mask the drift this test is about. -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/databricks.yml b/acceptance/bundle/resources/schemas/drift/clear_field/databricks.yml similarity index 63% rename from acceptance/bundle/resources/schemas/drift/clear_comment/databricks.yml rename to acceptance/bundle/resources/schemas/drift/clear_field/databricks.yml index 6ae252f046b..95a79d34ddb 100644 --- a/acceptance/bundle/resources/schemas/drift/clear_comment/databricks.yml +++ b/acceptance/bundle/resources/schemas/drift/clear_field/databricks.yml @@ -4,6 +4,7 @@ bundle: resources: schemas: schema1: - name: schema_clear_comment + name: schema_clear_field catalog_name: main comment: base comment + custom_max_retention_hours: 24 diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/out.test.toml b/acceptance/bundle/resources/schemas/drift/clear_field/out.test.toml similarity index 100% rename from acceptance/bundle/resources/schemas/drift/clear_comment/out.test.toml rename to acceptance/bundle/resources/schemas/drift/clear_field/out.test.toml diff --git a/acceptance/bundle/resources/schemas/drift/clear_comment/output.txt b/acceptance/bundle/resources/schemas/drift/clear_field/output.txt similarity index 55% rename from acceptance/bundle/resources/schemas/drift/clear_comment/output.txt rename to acceptance/bundle/resources/schemas/drift/clear_field/output.txt index 0454197b637..28ec26bb237 100644 --- a/acceptance/bundle/resources/schemas/drift/clear_comment/output.txt +++ b/acceptance/bundle/resources/schemas/drift/clear_field/output.txt @@ -6,51 +6,48 @@ Created schemas.schema1 Files: 5 uploaded, 0 deleted Resources: 1 created, 0 changed, 0 deleted, 0 unchanged -=== Remove the comment from the config +=== Removing comment converges >>> update_file.py databricks.yml comment: base comment -=== Plan proposes clearing it +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Updated schemas.schema1 +Files: 3 uploaded, 0 deleted +Resources: 0 created, 1 changed, 0 deleted, 0 unchanged + >>> [CLI] bundle plan -update schemas.schema1 +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged -Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged +=== Removing custom_max_retention_hours does not +>>> update_file.py databricks.yml custom_max_retention_hours: 24 ->>> [CLI] bundle plan --output json -{ - "comment": { - "action": "update", - "old": "base comment", - "remote": "base comment" - } -} - -=== Deploy reports success >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Updated schemas.schema1 Files: 3 uploaded, 0 deleted Resources: 0 created, 1 changed, 0 deleted, 0 unchanged -=== Still drifting: the same update is proposed again >>> [CLI] bundle plan update schemas.schema1 Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged ->>> [CLI] bundle plan --output json +=== The update PATCH carried an empty body, which is why nothing changed +>>> print_requests.py //unity --sort { - "comment": { - "action": "update", - "remote": "base comment" + "method": "PATCH", + "path": "/api/2.1/unity-catalog/schemas/main.schema_clear_field", + "body": { + "comment": "", + "custom_max_retention_hours": 24 } } - -=== The update PATCH carried an empty body, which is why nothing changed ->>> print_requests.py //unity --sort { "method": "PATCH", - "path": "/api/2.1/unity-catalog/schemas/main.schema_clear_comment", - "body": {} + "path": "/api/2.1/unity-catalog/schemas/main.schema_clear_field", + "body": { + "comment": "" + } } { "method": "POST", @@ -58,6 +55,7 @@ Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged "body": { "catalog_name": "main", "comment": "base comment", - "name": "schema_clear_comment" + "custom_max_retention_hours": 24, + "name": "schema_clear_field" } } diff --git a/acceptance/bundle/resources/schemas/drift/clear_field/script b/acceptance/bundle/resources/schemas/drift/clear_field/script new file mode 100644 index 00000000000..4efa3534588 --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/clear_field/script @@ -0,0 +1,22 @@ +# Removing a field from the config should clear it remotely and converge. comment does. +# custom_max_retention_hours does not, and this records that difference. + +title "Initial deployment" +trace $CLI bundle deploy + +title "Removing comment converges" +trace update_file.py databricks.yml " comment: base comment" "" +trace $CLI bundle deploy +trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete" + +# BUG: custom_max_retention_hours is omitempty in catalog.UpdateSchema and DoUpdate +# forwards only the config's own ForceSendFields, so dropping it from the config drops it +# from the PATCH body too. UC keeps the old value, the deploy reports success, and every +# later plan re-proposes the same update. See test.toml. +title "Removing custom_max_retention_hours does not" +trace update_file.py databricks.yml " custom_max_retention_hours: 24" "" +trace $CLI bundle deploy +trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete" + +title "The update PATCH carried an empty body, which is why nothing changed" +trace print_requests.py //unity --sort diff --git a/acceptance/bundle/resources/schemas/drift/clear_field/test.toml b/acceptance/bundle/resources/schemas/drift/clear_field/test.toml new file mode 100644 index 00000000000..988bb121e36 --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/clear_field/test.toml @@ -0,0 +1,8 @@ +Badness = "Removing custom_max_retention_hours from the config never clears it: the field is omitempty in UpdateSchema and DoUpdate forwards only the config's own ForceSendFields, so the PATCH body comes out empty and every later plan re-proposes the same update" + +Cloud = true +RecordRequests = true + +# Terraform issues a spurious PATCH for enable_predictive_optimization on every deploy, +# which would mask the drift this test is about. +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index 171db7bff62..8c9d51ba9d4 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -32,17 +32,23 @@ permutations, a `bundle deploy` each would be dominated by sync. | `SUPPRESSED` | the planner diffed the field and dropped the change; detail is the engine's own reason | | `NOT_OBSERVABLE` | the two values are identical in the state sent to the API, so there is nothing to see — an unset bool and an explicit `false` are the usual case | | `NO_PLAN` | the field diff exists but no action was planned | -| `DRIFT` | applied, yet a later plan still wants the same change: deploying never converges | -| `DRIFT_CHILD` | the field converged but another node of the resource did not; a guard that should stay empty | +| `POST_DEPLOY_DRIFT` | applied, yet the plan taken straight afterwards still wants the same change: deploying never converges | +| `POST_DEPLOY_DRIFT_CHILD` | the field converged but another node of the resource did not; a guard that should stay empty | | `BACKEND_ERROR` | the API rejected the value — usually the value library needs a valid value for this field | | `DEPLOY_ERROR` | apply failed for a non-API reason | +| `TIMEOUT` | the operation did not finish inside the per-operation deadline | | `PLAN_ERROR` | planning failed | | `UNSETTABLE` | the value could not be written into the config at all | | `BASE_ERROR` | the transition's starting point would not deploy, so nothing was observed | | `NOT_IN_STATE` | the field exists in bundle config but not in the state type, so it never reaches the API | -`SUPPRESSED` and `DRIFT` are where the interesting gaps are. `NOT_OBSERVABLE` is not a -gap; it is the wire format telling the truth. +`POST_DEPLOY_DRIFT` and `SUPPRESSED` are where the interesting gaps are. `NOT_OBSERVABLE` +is not a gap; it is the wire format telling the truth. + +Two files per resource type: `output/.txt` holds only the results worth looking at, +one line each, so any diff to it is a change in behaviour. `output/.full.txt` holds +every result plus the summary and the not-covered list; it is gitignored, because it moves +whenever a passing row moves. The suite deliberately does **not** read `resources.yml`. That file is the engine's answer to many of these cases, so consulting it would just restate the implementation. @@ -54,23 +60,16 @@ The `SUPPRESSED` reason string is the engine explaining itself, which is differe guess — enums, ids, anything the backend constrains: ```yaml -slow: true # expensive on cloud; dropped only under -short skip: name: rename waits out an asynchronous delete fields: compute_size: [MEDIUM, LARGE] ``` -Every resource type runs everywhere by default, so most types need no file at all. A -field with no entry gets two values for its Go kind, which is enough to see a +Every resource type runs everywhere — local and cloud alike — so most types need no file +at all. A field with no entry gets two values for its Go kind, which is enough to see a value-to-value move on top of add and remove. -`slow: true` marks a type whose cloud run is expensive — one that provisions compute, or -simply has a large field surface. It still runs on cloud; it is only dropped when -`-short` is passed, the same way `CloudSlow` narrows acceptance tests. So the PR cloud -leg (`task integration-short`) skips them and the nightly (`task integration`) covers -everything. - `skip` is for a field no single-field edit can exercise — one that only validates as part of a set (a job's `git_source`), or whose change is correct but ruinously slow (an app rename waits out an asynchronous delete). Each entry needs a reason, and shows up in @@ -84,10 +83,12 @@ go test ./bundle/direct/tests -run 'TestFields/schemas' -v ./task test-update-fields # regenerate the goldens ``` -One transition is addressable on its own: +One transition is addressable on its own, and the subtest names carry no shell +metacharacters so no quoting is needed. `-v` prints the post-deploy plan behind any +problem verdict: -``` -TestFields/schemas/schema.yml.tmpl/comment/x->absent +```bash +go test ./bundle/direct/tests -run TestFields/pipelines/.*/dry_run/absent_to_true -v ``` On cloud (`CLOUD_ENV` set) only resource types with `cloud: true` run, and the results diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index d1ee90ea105..34f8cb6e400 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -31,6 +31,7 @@ package tests import ( "context" + "encoding/json" "errors" "fmt" "regexp" @@ -65,10 +66,6 @@ func TestFields(t *testing.T) { fv, err := loadFieldValues(resourceType) require.NoError(t, err) - if isCloud() && fv.slow && testing.Short() { - t.Skipf("slow on cloud and -short was passed; the nightly run covers it") - } - // A testserver per type keeps the parallel runs from sharing workspace // state. On cloud this is the same real workspace either way. client := newClient(t) @@ -98,7 +95,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // nothing below can be attributed to a field. h, err := newBaseline(t, ctx, client, user, cfg) if err != nil { - rep.add(result{cfg.name, "(base config)", "create", verdictBaseError, oneLine(err.Error())}) + rep.add(result{cfg.name, "(base config)", "", "create", verdictBaseError, oneLine(err.Error())}) return } @@ -106,13 +103,13 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl for _, f := range fields { if reason, ok := fv.skipReason(f.path); ok { - rep.add(result{cfg.name, f.path, "-", verdictSkipped, reason}) + rep.add(result{cfg.name, f.path, "", "", verdictSkipped, reason}) continue } t.Run(f.path, func(t *testing.T) { for _, tr := range f.transitions() { t.Run(tr.label(), func(t *testing.T) { - res := runTransition(h, cfg.name, f.path, tr) + res := runTransition(t, h, cfg.name, f.path, tr) rep.add(res) if res.verdict.leavesResourceUsable() { return @@ -136,7 +133,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl if !h.converged() { rebuilt, err := rebuild(t, ctx, client, user, cfg, h) if err != nil { - rep.add(result{cfg.name, f.path, "-", verdictBaseError, oneLine(err.Error())}) + rep.add(result{cfg.name, f.path, "", "", verdictBaseError, oneLine(err.Error())}) return } h = rebuilt @@ -146,9 +143,9 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } // runTransition moves one field from one value to another and reports what happened. -func runTransition(h *bundleHarness, config, path string, tr transition) result { +func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition) result { from, to := tr.from, tr.to - res := result{config: config, field: path, transition: tr.label()} //exhaustruct:ignore + res := result{config: config, field: path, from: valueLabel(tr.from), to: valueLabel(tr.to)} //exhaustruct:ignore // Reach the starting value. if err := h.setField(path, from); err != nil { @@ -182,9 +179,13 @@ func runTransition(h *bundleHarness, config, path string, tr transition) result } if diags := h.apply(pending, plan); diags.HasError() { - res.verdict = verdictDeployError - if isAPIError(diags) { + switch { + case isTimeout(diags): + res.verdict = verdictTimeout + case isAPIError(diags): res.verdict = verdictBackendError + default: + res.verdict = verdictDeployError } res.detail = firstError(diags) return res @@ -197,6 +198,9 @@ func runTransition(h *bundleHarness, config, path string, tr transition) result return res } if own, child := driftDetail(after, h.node); own != "" || child != "" { + // The plan that still wants a change is the whole evidence for this verdict, so + // print it. Subtests never fail here, so -v is how you see it. + t.Logf("post-deploy plan still proposes a change:\n%s", planJSON(after)) // Attribute to the field only when the field's own node is still drifted; a // child left behind by a recreate is a different problem with a different fix. res.verdict, res.detail = verdictDrift, own @@ -294,6 +298,15 @@ var apiErrorCodes = []string{ "NOT_FOUND", } +func isTimeout(diags diag.Diagnostics) bool { + for _, d := range diags { + if strings.Contains(d.Summary, context.DeadlineExceeded.Error()) { + return true + } + } + return false +} + func isAPIError(diags diag.Diagnostics) bool { for _, d := range diags { for _, code := range apiErrorCodes { @@ -338,6 +351,15 @@ func driftDetail(plan *deployplan.Plan, node string) (own, child string) { // to be redacted or the goldens differ on every run. var idPattern = regexp.MustCompile(`f[0-9a-f]{20}`) +// planJSON renders a plan for a -v dump. +func planJSON(plan *deployplan.Plan) string { + body, err := json.MarshalIndent(plan, "", " ") + if err != nil { + return err.Error() + } + return string(body) +} + func oneLine(s string) string { s = idPattern.ReplaceAllString(s, "[UNIQUE_NAME]") s = strings.ReplaceAll(s, "\n", " ") diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index 1afb1dc0a9a..0c5767ee5f1 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -10,6 +10,7 @@ import ( "slices" "strings" "testing" + "time" "github.com/databricks/cli/bundle" bundleconfig "github.com/databricks/cli/bundle/config" @@ -256,18 +257,29 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC // CalculatePlan short-circuits to a bare "planning failed" whenever that flag is set. A // per-operation context is what keeps one failed transition from poisoning every plan // after it. -func (h *bundleHarness) opCtx() context.Context { +func (h *bundleHarness) opCtx() (context.Context, func()) { ctx := logdiag.IsolatedContext(h.ctx) logdiag.SetCollect(ctx, true) - return ctx + // A deadline so one operation that cannot finish is recorded as TIMEOUT instead of + // stalling the run. Apps are the reason: renaming one recreates it, and the delete + // half waits for the old name to leave DELETING, which the API does not cap. + return context.WithTimeout(ctx, opTimeout()) +} + +func opTimeout() time.Duration { + if isCloud() { + return 10 * time.Minute + } + return 20 * time.Second } // plan opens the state fresh each time: dstate.Open panics on an already-open // receiver, and Finalize resets it, so one DeploymentBundle serves one operation. func (h *bundleHarness) plan() (*pendingApply, *deployplan.Plan, diag.Diagnostics) { - ctx := h.opCtx() + ctx, cancel := h.opCtx() db := &direct.DeploymentBundle{} //exhaustruct:ignore if err := db.StateDB.Open(ctx, h.statePath, dstate.WithRecovery(false), dstate.WithWrite(false)); err != nil { + cancel() return nil, nil, diag.FromErr(err) } plan, err := db.CalculatePlan(ctx, h.client, &h.bundle.Config) @@ -275,18 +287,20 @@ func (h *bundleHarness) plan() (*pendingApply, *deployplan.Plan, diag.Diagnostic if err != nil && !diags.HasError() { diags = append(diags, diag.FromErr(err)...) } - return &pendingApply{ctx: ctx, db: db}, plan, diags + return &pendingApply{ctx: ctx, cancel: cancel, db: db}, plan, diags } // pendingApply carries a planned DeploymentBundle together with the context it was // planned under, so the apply reports into the same isolated diagnostics sink. type pendingApply struct { - ctx context.Context - db *direct.DeploymentBundle + ctx context.Context + cancel func() + db *direct.DeploymentBundle } // apply consumes the DeploymentBundle returned by plan. func (h *bundleHarness) apply(p *pendingApply, plan *deployplan.Plan) diag.Diagnostics { + defer p.cancel() ctx, db := p.ctx, p.db if err := db.StateDB.UpgradeToWrite(); err != nil { return diag.FromErr(err) @@ -315,17 +329,19 @@ func (h *bundleHarness) deploy() (deployplan.ActionType, diag.Diagnostics) { // destroy deletes everything in state. A nil configRoot makes the planner treat // every state entry as a delete (same call phases.Destroy makes). func (h *bundleHarness) destroy() diag.Diagnostics { - ctx := h.opCtx() + ctx, cancel := h.opCtx() db := &direct.DeploymentBundle{} //exhaustruct:ignore if err := db.StateDB.Open(ctx, h.statePath, dstate.WithRecovery(false), dstate.WithWrite(false)); err != nil { + cancel() return diag.FromErr(err) } plan, err := db.CalculatePlan(ctx, h.client, nil) if err != nil { _, _ = db.StateDB.Finalize(ctx) + cancel() return diag.FromErr(err) } - return h.apply(&pendingApply{ctx: ctx, db: db}, plan) + return h.apply(&pendingApply{ctx: ctx, cancel: cancel, db: db}, plan) } func nodeAction(plan *deployplan.Plan, node string) deployplan.ActionType { diff --git a/bundle/direct/tests/output/alerts.full.txt b/bundle/direct/tests/output/alerts.full.txt new file mode 100644 index 00000000000..017fb4e1b4f --- /dev/null +++ b/bundle/direct/tests/output/alerts.full.txt @@ -0,0 +1,201 @@ +=== alert.yml.tmpl +create_time absent x OK +create_time absent y OK +create_time x absent OK +create_time x y OK +create_time y absent OK +create_time y x OK +custom_description absent x OK +custom_description absent y OK +custom_description x absent OK +custom_description x y OK +custom_description y absent OK +custom_description y x OK +custom_summary absent x OK +custom_summary absent y OK +custom_summary x absent OK +custom_summary x y OK +custom_summary y absent OK +custom_summary y x OK +display_name x y OK +display_name y x OK +effective_run_as.service_principal_name absent x OK +effective_run_as.service_principal_name absent y OK +effective_run_as.service_principal_name x absent OK +effective_run_as.service_principal_name x y OK +effective_run_as.service_principal_name y absent OK +effective_run_as.service_principal_name y x OK +effective_run_as.user_name absent x OK +effective_run_as.user_name absent y OK +effective_run_as.user_name x absent OK +effective_run_as.user_name x y OK +effective_run_as.user_name y absent OK +effective_run_as.user_name y x OK +evaluation.comparison_operator x y OK +evaluation.comparison_operator y x OK +evaluation.empty_result_state absent x OK +evaluation.empty_result_state absent y OK +evaluation.empty_result_state x absent OK +evaluation.empty_result_state x y OK +evaluation.empty_result_state y absent OK +evaluation.empty_result_state y x OK +evaluation.last_evaluated_at absent x OK +evaluation.last_evaluated_at absent y OK +evaluation.last_evaluated_at x absent OK +evaluation.last_evaluated_at x y OK +evaluation.last_evaluated_at y absent OK +evaluation.last_evaluated_at y x OK +evaluation.notification.notify_on_ok absent false NOT_OBSERVABLE +evaluation.notification.notify_on_ok absent true OK +evaluation.notification.notify_on_ok false absent SUPPRESSED empty +evaluation.notification.notify_on_ok false true OK +evaluation.notification.notify_on_ok true absent OK +evaluation.notification.notify_on_ok true false OK +evaluation.notification.retrigger_seconds 1 2 OK +evaluation.notification.retrigger_seconds 1 absent OK +evaluation.notification.retrigger_seconds 2 1 OK +evaluation.notification.retrigger_seconds 2 absent OK +evaluation.notification.retrigger_seconds absent 1 OK +evaluation.notification.retrigger_seconds absent 2 OK +evaluation.source.aggregation absent x OK +evaluation.source.aggregation absent y OK +evaluation.source.aggregation x absent OK +evaluation.source.aggregation x y OK +evaluation.source.aggregation y absent OK +evaluation.source.aggregation y x OK +evaluation.source.display absent x OK +evaluation.source.display absent y OK +evaluation.source.display x absent OK +evaluation.source.display x y OK +evaluation.source.display y absent OK +evaluation.source.display y x OK +evaluation.source.name x y OK +evaluation.source.name y x OK +evaluation.state absent x OK +evaluation.state absent y OK +evaluation.state x absent OK +evaluation.state x y OK +evaluation.state y absent OK +evaluation.state y x OK +evaluation.threshold.column.aggregation absent x OK +evaluation.threshold.column.aggregation absent y OK +evaluation.threshold.column.aggregation x absent OK +evaluation.threshold.column.aggregation x y OK +evaluation.threshold.column.aggregation y absent OK +evaluation.threshold.column.aggregation y x OK +evaluation.threshold.column.display absent x OK +evaluation.threshold.column.display absent y OK +evaluation.threshold.column.display x absent OK +evaluation.threshold.column.display x y OK +evaluation.threshold.column.display y absent OK +evaluation.threshold.column.display y x OK +evaluation.threshold.column.name x y OK +evaluation.threshold.column.name y x OK +evaluation.threshold.value.bool_value absent false SUPPRESSED empty +evaluation.threshold.value.bool_value absent true OK +evaluation.threshold.value.bool_value false absent SUPPRESSED empty +evaluation.threshold.value.bool_value false true OK +evaluation.threshold.value.bool_value true absent OK +evaluation.threshold.value.bool_value true false OK +evaluation.threshold.value.double_value 1 2 OK +evaluation.threshold.value.double_value 1 absent OK +evaluation.threshold.value.double_value 2 1 OK +evaluation.threshold.value.double_value 2 absent OK +evaluation.threshold.value.double_value absent 1 OK +evaluation.threshold.value.double_value absent 2 OK +evaluation.threshold.value.string_value absent x OK +evaluation.threshold.value.string_value absent y OK +evaluation.threshold.value.string_value x absent OK +evaluation.threshold.value.string_value x y OK +evaluation.threshold.value.string_value y absent OK +evaluation.threshold.value.string_value y x OK +file_path absent x NOT_OBSERVABLE +file_path absent y NOT_OBSERVABLE +file_path x absent NOT_OBSERVABLE +file_path x y NOT_OBSERVABLE +file_path y absent NOT_OBSERVABLE +file_path y x NOT_OBSERVABLE +id absent x SUPPRESSED spec:output_only +id absent y SUPPRESSED spec:output_only +id x absent SUPPRESSED spec:output_only +id x y SUPPRESSED spec:output_only +id y absent SUPPRESSED spec:output_only +id y x SUPPRESSED spec:output_only +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle_state absent x OK +lifecycle_state absent y OK +lifecycle_state x absent OK +lifecycle_state x y OK +lifecycle_state y absent OK +lifecycle_state y x OK +owner_user_name absent x OK +owner_user_name absent y OK +owner_user_name x absent OK +owner_user_name x y OK +owner_user_name y absent OK +owner_user_name y x OK +parent_path absent x OK +parent_path absent y OK +parent_path x absent OK +parent_path x y OK +parent_path y absent OK +parent_path y x OK +query_text x y OK +query_text y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +run_as_user_name absent x OK +run_as_user_name absent y OK +run_as_user_name x absent OK +run_as_user_name x y OK +run_as_user_name y absent OK +run_as_user_name y x OK +schedule.pause_status absent x OK +schedule.pause_status absent y OK +schedule.pause_status x absent OK +schedule.pause_status x y OK +schedule.pause_status y absent OK +schedule.pause_status y x OK +schedule.quartz_cron_schedule x y OK +schedule.quartz_cron_schedule y x OK +schedule.timezone_id x y OK +schedule.timezone_id y x OK +update_time absent x OK +update_time absent y OK +update_time x absent OK +update_time x y OK +update_time y absent OK +update_time y x OK +warehouse_id x y OK +warehouse_id y x OK + +=== summary +OK 162 +SUPPRESSED 9 +NOT_OBSERVABLE 13 + +=== not covered: 9 fields under a slice or map +evaluation.notification.subscriptions[*].destination_id +evaluation.notification.subscriptions[*].user_email +parameters[*].name +parameters[*].type +parameters[*].value +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/tests/output/alerts.txt b/bundle/direct/tests/output/alerts.txt index c374b32a3a1..3c6206aae9f 100644 --- a/bundle/direct/tests/output/alerts.txt +++ b/bundle/direct/tests/output/alerts.txt @@ -1,201 +1,7 @@ === alert.yml.tmpl -create_time absent->x OK -create_time absent->y OK -create_time x->absent OK -create_time x->y OK -create_time y->absent OK -create_time y->x OK -custom_description absent->x OK -custom_description absent->y OK -custom_description x->absent OK -custom_description x->y OK -custom_description y->absent OK -custom_description y->x OK -custom_summary absent->x OK -custom_summary absent->y OK -custom_summary x->absent OK -custom_summary x->y OK -custom_summary y->absent OK -custom_summary y->x OK -display_name x->y OK -display_name y->x OK -effective_run_as.service_principal_name absent->x OK -effective_run_as.service_principal_name absent->y OK -effective_run_as.service_principal_name x->absent OK -effective_run_as.service_principal_name x->y OK -effective_run_as.service_principal_name y->absent OK -effective_run_as.service_principal_name y->x OK -effective_run_as.user_name absent->x OK -effective_run_as.user_name absent->y OK -effective_run_as.user_name x->absent OK -effective_run_as.user_name x->y OK -effective_run_as.user_name y->absent OK -effective_run_as.user_name y->x OK -evaluation.comparison_operator x->y OK -evaluation.comparison_operator y->x OK -evaluation.empty_result_state absent->x OK -evaluation.empty_result_state absent->y OK -evaluation.empty_result_state x->absent OK -evaluation.empty_result_state x->y OK -evaluation.empty_result_state y->absent OK -evaluation.empty_result_state y->x OK -evaluation.last_evaluated_at absent->x OK -evaluation.last_evaluated_at absent->y OK -evaluation.last_evaluated_at x->absent OK -evaluation.last_evaluated_at x->y OK -evaluation.last_evaluated_at y->absent OK -evaluation.last_evaluated_at y->x OK -evaluation.notification.notify_on_ok absent->false NOT_OBSERVABLE -evaluation.notification.notify_on_ok absent->true OK -evaluation.notification.notify_on_ok false->absent SUPPRESSED empty -evaluation.notification.notify_on_ok false->true OK -evaluation.notification.notify_on_ok true->absent OK -evaluation.notification.notify_on_ok true->false OK -evaluation.notification.retrigger_seconds 1->2 OK -evaluation.notification.retrigger_seconds 1->absent OK -evaluation.notification.retrigger_seconds 2->1 OK -evaluation.notification.retrigger_seconds 2->absent OK -evaluation.notification.retrigger_seconds absent->1 OK -evaluation.notification.retrigger_seconds absent->2 OK -evaluation.source.aggregation absent->x OK -evaluation.source.aggregation absent->y OK -evaluation.source.aggregation x->absent OK -evaluation.source.aggregation x->y OK -evaluation.source.aggregation y->absent OK -evaluation.source.aggregation y->x OK -evaluation.source.display absent->x OK -evaluation.source.display absent->y OK -evaluation.source.display x->absent OK -evaluation.source.display x->y OK -evaluation.source.display y->absent OK -evaluation.source.display y->x OK -evaluation.source.name x->y OK -evaluation.source.name y->x OK -evaluation.state absent->x OK -evaluation.state absent->y OK -evaluation.state x->absent OK -evaluation.state x->y OK -evaluation.state y->absent OK -evaluation.state y->x OK -evaluation.threshold.column.aggregation absent->x OK -evaluation.threshold.column.aggregation absent->y OK -evaluation.threshold.column.aggregation x->absent OK -evaluation.threshold.column.aggregation x->y OK -evaluation.threshold.column.aggregation y->absent OK -evaluation.threshold.column.aggregation y->x OK -evaluation.threshold.column.display absent->x OK -evaluation.threshold.column.display absent->y OK -evaluation.threshold.column.display x->absent OK -evaluation.threshold.column.display x->y OK -evaluation.threshold.column.display y->absent OK -evaluation.threshold.column.display y->x OK -evaluation.threshold.column.name x->y OK -evaluation.threshold.column.name y->x OK -evaluation.threshold.value.bool_value absent->false SUPPRESSED empty -evaluation.threshold.value.bool_value absent->true OK -evaluation.threshold.value.bool_value false->absent SUPPRESSED empty -evaluation.threshold.value.bool_value false->true OK -evaluation.threshold.value.bool_value true->absent OK -evaluation.threshold.value.bool_value true->false OK -evaluation.threshold.value.double_value 1->2 OK -evaluation.threshold.value.double_value 1->absent OK -evaluation.threshold.value.double_value 2->1 OK -evaluation.threshold.value.double_value 2->absent OK -evaluation.threshold.value.double_value absent->1 OK -evaluation.threshold.value.double_value absent->2 OK -evaluation.threshold.value.string_value absent->x OK -evaluation.threshold.value.string_value absent->y OK -evaluation.threshold.value.string_value x->absent OK -evaluation.threshold.value.string_value x->y OK -evaluation.threshold.value.string_value y->absent OK -evaluation.threshold.value.string_value y->x OK -file_path absent->x NOT_OBSERVABLE -file_path absent->y NOT_OBSERVABLE -file_path x->absent NOT_OBSERVABLE -file_path x->y NOT_OBSERVABLE -file_path y->absent NOT_OBSERVABLE -file_path y->x NOT_OBSERVABLE -id absent->x SUPPRESSED spec:output_only -id absent->y SUPPRESSED spec:output_only -id x->absent SUPPRESSED spec:output_only -id x->y SUPPRESSED spec:output_only -id y->absent SUPPRESSED spec:output_only -id y->x SUPPRESSED spec:output_only -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle_state absent->x OK -lifecycle_state absent->y OK -lifecycle_state x->absent OK -lifecycle_state x->y OK -lifecycle_state y->absent OK -lifecycle_state y->x OK -owner_user_name absent->x OK -owner_user_name absent->y OK -owner_user_name x->absent OK -owner_user_name x->y OK -owner_user_name y->absent OK -owner_user_name y->x OK -parent_path absent->x OK -parent_path absent->y OK -parent_path x->absent OK -parent_path x->y OK -parent_path y->absent OK -parent_path y->x OK -query_text x->y OK -query_text y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -run_as_user_name absent->x OK -run_as_user_name absent->y OK -run_as_user_name x->absent OK -run_as_user_name x->y OK -run_as_user_name y->absent OK -run_as_user_name y->x OK -schedule.pause_status absent->x OK -schedule.pause_status absent->y OK -schedule.pause_status x->absent OK -schedule.pause_status x->y OK -schedule.pause_status y->absent OK -schedule.pause_status y->x OK -schedule.quartz_cron_schedule x->y OK -schedule.quartz_cron_schedule y->x OK -schedule.timezone_id x->y OK -schedule.timezone_id y->x OK -update_time absent->x OK -update_time absent->y OK -update_time x->absent OK -update_time x->y OK -update_time y->absent OK -update_time y->x OK -warehouse_id x->y OK -warehouse_id y->x OK - -=== summary -OK 162 -SUPPRESSED 9 -NOT_OBSERVABLE 13 - -=== not covered: 9 fields under a slice or map -evaluation.notification.subscriptions[*].destination_id -evaluation.notification.subscriptions[*].user_email -parameters[*].name -parameters[*].type -parameters[*].value -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name +id absent x SUPPRESSED spec:output_only +id absent y SUPPRESSED spec:output_only +id x absent SUPPRESSED spec:output_only +id x y SUPPRESSED spec:output_only +id y absent SUPPRESSED spec:output_only +id y x SUPPRESSED spec:output_only diff --git a/bundle/direct/tests/output/apps.full.txt b/bundle/direct/tests/output/apps.full.txt new file mode 100644 index 00000000000..9d0e3e35fe9 --- /dev/null +++ b/bundle/direct/tests/output/apps.full.txt @@ -0,0 +1,568 @@ +=== app.yml.tmpl +active_deployment.create_time absent x OK +active_deployment.create_time absent y OK +active_deployment.create_time x absent OK +active_deployment.create_time x y OK +active_deployment.create_time y absent OK +active_deployment.create_time y x OK +active_deployment.creator absent x OK +active_deployment.creator absent y OK +active_deployment.creator x absent OK +active_deployment.creator x y OK +active_deployment.creator y absent OK +active_deployment.creator y x OK +active_deployment.deployment_artifacts.source_code_path absent x OK +active_deployment.deployment_artifacts.source_code_path absent y OK +active_deployment.deployment_artifacts.source_code_path x absent OK +active_deployment.deployment_artifacts.source_code_path x y OK +active_deployment.deployment_artifacts.source_code_path y absent OK +active_deployment.deployment_artifacts.source_code_path y x OK +active_deployment.deployment_id absent x OK +active_deployment.deployment_id absent y OK +active_deployment.deployment_id x absent OK +active_deployment.deployment_id x y OK +active_deployment.deployment_id y absent OK +active_deployment.deployment_id y x OK +active_deployment.git_source.branch absent x OK +active_deployment.git_source.branch absent y OK +active_deployment.git_source.branch x absent OK +active_deployment.git_source.branch x y OK +active_deployment.git_source.branch y absent OK +active_deployment.git_source.branch y x OK +active_deployment.git_source.commit absent x OK +active_deployment.git_source.commit absent y OK +active_deployment.git_source.commit x absent OK +active_deployment.git_source.commit x y OK +active_deployment.git_source.commit y absent OK +active_deployment.git_source.commit y x OK +active_deployment.git_source.git_repository.auto_deploy absent false OK +active_deployment.git_source.git_repository.auto_deploy absent true OK +active_deployment.git_source.git_repository.auto_deploy false absent OK +active_deployment.git_source.git_repository.auto_deploy false true OK +active_deployment.git_source.git_repository.auto_deploy true absent OK +active_deployment.git_source.git_repository.auto_deploy true false OK +active_deployment.git_source.git_repository.caller_credential_id 1 2 OK +active_deployment.git_source.git_repository.caller_credential_id 1 absent OK +active_deployment.git_source.git_repository.caller_credential_id 2 1 OK +active_deployment.git_source.git_repository.caller_credential_id 2 absent OK +active_deployment.git_source.git_repository.caller_credential_id absent 1 OK +active_deployment.git_source.git_repository.caller_credential_id absent 2 OK +active_deployment.git_source.git_repository.provider x y OK +active_deployment.git_source.git_repository.provider y x OK +active_deployment.git_source.git_repository.url x y OK +active_deployment.git_source.git_repository.url y x OK +active_deployment.git_source.resolved_commit absent x OK +active_deployment.git_source.resolved_commit absent y OK +active_deployment.git_source.resolved_commit x absent OK +active_deployment.git_source.resolved_commit x y OK +active_deployment.git_source.resolved_commit y absent OK +active_deployment.git_source.resolved_commit y x OK +active_deployment.git_source.source_code_path absent x OK +active_deployment.git_source.source_code_path absent y OK +active_deployment.git_source.source_code_path x absent OK +active_deployment.git_source.source_code_path x y OK +active_deployment.git_source.source_code_path y absent OK +active_deployment.git_source.source_code_path y x OK +active_deployment.git_source.tag absent x OK +active_deployment.git_source.tag absent y OK +active_deployment.git_source.tag x absent OK +active_deployment.git_source.tag x y OK +active_deployment.git_source.tag y absent OK +active_deployment.git_source.tag y x OK +active_deployment.mode absent x OK +active_deployment.mode absent y OK +active_deployment.mode x absent OK +active_deployment.mode x y OK +active_deployment.mode y absent OK +active_deployment.mode y x OK +active_deployment.source_code_path absent x OK +active_deployment.source_code_path absent y OK +active_deployment.source_code_path x absent OK +active_deployment.source_code_path x y OK +active_deployment.source_code_path y absent OK +active_deployment.source_code_path y x OK +active_deployment.status.message absent x OK +active_deployment.status.message absent y OK +active_deployment.status.message x absent OK +active_deployment.status.message x y OK +active_deployment.status.message y absent OK +active_deployment.status.message y x OK +active_deployment.status.state absent x OK +active_deployment.status.state absent y OK +active_deployment.status.state x absent OK +active_deployment.status.state x y OK +active_deployment.status.state y absent OK +active_deployment.status.state y x OK +active_deployment.update_time absent x OK +active_deployment.update_time absent y OK +active_deployment.update_time x absent OK +active_deployment.update_time x y OK +active_deployment.update_time y absent OK +active_deployment.update_time y x OK +app_status.message absent x OK +app_status.message absent y OK +app_status.message x absent OK +app_status.message x y OK +app_status.message y absent OK +app_status.message y x OK +app_status.running_instances 1 2 OK +app_status.running_instances 1 absent OK +app_status.running_instances 2 1 OK +app_status.running_instances 2 absent OK +app_status.running_instances absent 1 OK +app_status.running_instances absent 2 OK +app_status.state absent x OK +app_status.state absent y OK +app_status.state x absent OK +app_status.state x y OK +app_status.state y absent OK +app_status.state y x OK +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent POST_DEPLOY_DRIFT budget_policy_id +budget_policy_id x y OK +budget_policy_id y absent POST_DEPLOY_DRIFT budget_policy_id +budget_policy_id y x OK +compute_max_instances 1 2 OK +compute_max_instances 1 absent POST_DEPLOY_DRIFT compute_max_instances +compute_max_instances 2 1 OK +compute_max_instances 2 absent POST_DEPLOY_DRIFT compute_max_instances +compute_max_instances absent 1 OK +compute_max_instances absent 2 OK +compute_min_instances 1 2 OK +compute_min_instances 1 absent POST_DEPLOY_DRIFT compute_min_instances +compute_min_instances 2 1 OK +compute_min_instances 2 absent POST_DEPLOY_DRIFT compute_min_instances +compute_min_instances absent 1 OK +compute_min_instances absent 2 OK +compute_size LARGE MEDIUM OK +compute_size LARGE absent OK +compute_size MEDIUM LARGE OK +compute_size MEDIUM absent OK +compute_size absent LARGE OK +compute_size absent MEDIUM SUPPRESSED remote_already_set +compute_status.active_instances 1 2 OK +compute_status.active_instances 1 absent OK +compute_status.active_instances 2 1 OK +compute_status.active_instances 2 absent OK +compute_status.active_instances absent 1 OK +compute_status.active_instances absent 2 OK +compute_status.message absent x OK +compute_status.message absent y OK +compute_status.message x absent OK +compute_status.message x y OK +compute_status.message y absent OK +compute_status.message y x OK +compute_status.state absent x OK +compute_status.state absent y OK +compute_status.state x absent OK +compute_status.state x y OK +compute_status.state y absent OK +compute_status.state y x OK +create_time absent x OK +create_time absent y OK +create_time x absent OK +create_time x y OK +create_time y absent OK +create_time y x OK +creator absent x OK +creator absent y OK +creator x absent OK +creator x y OK +creator y absent OK +creator y x OK +default_git_source.branch absent x OK +default_git_source.branch absent y OK +default_git_source.branch x absent OK +default_git_source.branch x y OK +default_git_source.branch y absent OK +default_git_source.branch y x OK +default_git_source.commit absent x OK +default_git_source.commit absent y OK +default_git_source.commit x absent OK +default_git_source.commit x y OK +default_git_source.commit y absent OK +default_git_source.commit y x OK +default_git_source.git_repository.auto_deploy absent false OK +default_git_source.git_repository.auto_deploy absent true OK +default_git_source.git_repository.auto_deploy false absent OK +default_git_source.git_repository.auto_deploy false true OK +default_git_source.git_repository.auto_deploy true absent OK +default_git_source.git_repository.auto_deploy true false OK +default_git_source.git_repository.caller_credential_id 1 2 OK +default_git_source.git_repository.caller_credential_id 1 absent OK +default_git_source.git_repository.caller_credential_id 2 1 OK +default_git_source.git_repository.caller_credential_id 2 absent OK +default_git_source.git_repository.caller_credential_id absent 1 OK +default_git_source.git_repository.caller_credential_id absent 2 OK +default_git_source.git_repository.provider x y OK +default_git_source.git_repository.provider y x OK +default_git_source.git_repository.url x y OK +default_git_source.git_repository.url y x OK +default_git_source.resolved_commit absent x OK +default_git_source.resolved_commit absent y OK +default_git_source.resolved_commit x absent OK +default_git_source.resolved_commit x y OK +default_git_source.resolved_commit y absent OK +default_git_source.resolved_commit y x OK +default_git_source.source_code_path absent x OK +default_git_source.source_code_path absent y OK +default_git_source.source_code_path x absent OK +default_git_source.source_code_path x y OK +default_git_source.source_code_path y absent OK +default_git_source.source_code_path y x OK +default_git_source.tag absent x OK +default_git_source.tag absent y OK +default_git_source.tag x absent OK +default_git_source.tag x y OK +default_git_source.tag y absent OK +default_git_source.tag y x OK +default_source_code_path absent x OK +default_source_code_path absent y OK +default_source_code_path x absent OK +default_source_code_path x y OK +default_source_code_path y absent OK +default_source_code_path y x OK +description absent x OK +description absent y OK +description x absent POST_DEPLOY_DRIFT description +description x y OK +description y absent POST_DEPLOY_DRIFT description +description y x OK +effective_budget_policy_id absent x OK +effective_budget_policy_id absent y OK +effective_budget_policy_id x absent OK +effective_budget_policy_id x y OK +effective_budget_policy_id y absent OK +effective_budget_policy_id y x OK +effective_usage_policy_id absent x OK +effective_usage_policy_id absent y OK +effective_usage_policy_id x absent OK +effective_usage_policy_id x y OK +effective_usage_policy_id y absent OK +effective_usage_policy_id y x OK +forward_user_access_token absent false OK +forward_user_access_token absent true OK +forward_user_access_token false absent SUPPRESSED empty +forward_user_access_token false true OK +forward_user_access_token true absent OK +forward_user_access_token true false OK +git_repository.auto_deploy absent false OK +git_repository.auto_deploy absent true OK +git_repository.auto_deploy false absent SUPPRESSED empty +git_repository.auto_deploy false true OK +git_repository.auto_deploy true absent POST_DEPLOY_DRIFT git_repository.auto_deploy +git_repository.auto_deploy true false OK +git_repository.caller_credential_id 1 2 OK +git_repository.caller_credential_id 1 absent POST_DEPLOY_DRIFT git_repository.caller_credential_id +git_repository.caller_credential_id 2 1 OK +git_repository.caller_credential_id 2 absent POST_DEPLOY_DRIFT git_repository.caller_credential_id +git_repository.caller_credential_id absent 1 OK +git_repository.caller_credential_id absent 2 OK +git_repository.provider x y OK +git_repository.provider y x OK +git_repository.url x y OK +git_repository.url y x OK +git_source.branch absent x NOT_OBSERVABLE +git_source.branch absent y NOT_OBSERVABLE +git_source.branch x absent NOT_OBSERVABLE +git_source.branch x y NOT_OBSERVABLE +git_source.branch y absent NOT_OBSERVABLE +git_source.branch y x NOT_OBSERVABLE +git_source.commit absent x NOT_OBSERVABLE +git_source.commit absent y NOT_OBSERVABLE +git_source.commit x absent NOT_OBSERVABLE +git_source.commit x y NOT_OBSERVABLE +git_source.commit y absent NOT_OBSERVABLE +git_source.commit y x NOT_OBSERVABLE +git_source.git_repository.auto_deploy absent false NOT_OBSERVABLE +git_source.git_repository.auto_deploy absent true NOT_OBSERVABLE +git_source.git_repository.auto_deploy false absent NOT_OBSERVABLE +git_source.git_repository.auto_deploy false true NOT_OBSERVABLE +git_source.git_repository.auto_deploy true absent NOT_OBSERVABLE +git_source.git_repository.auto_deploy true false NOT_OBSERVABLE +git_source.git_repository.caller_credential_id 1 2 NOT_OBSERVABLE +git_source.git_repository.caller_credential_id 1 absent NOT_OBSERVABLE +git_source.git_repository.caller_credential_id 2 1 NOT_OBSERVABLE +git_source.git_repository.caller_credential_id 2 absent NOT_OBSERVABLE +git_source.git_repository.caller_credential_id absent 1 NOT_OBSERVABLE +git_source.git_repository.caller_credential_id absent 2 NOT_OBSERVABLE +git_source.git_repository.provider x y NOT_OBSERVABLE +git_source.git_repository.provider y x NOT_OBSERVABLE +git_source.git_repository.url x y NOT_OBSERVABLE +git_source.git_repository.url y x NOT_OBSERVABLE +git_source.resolved_commit absent x NOT_OBSERVABLE +git_source.resolved_commit absent y NOT_OBSERVABLE +git_source.resolved_commit x absent NOT_OBSERVABLE +git_source.resolved_commit x y NOT_OBSERVABLE +git_source.resolved_commit y absent NOT_OBSERVABLE +git_source.resolved_commit y x NOT_OBSERVABLE +git_source.source_code_path absent x NOT_OBSERVABLE +git_source.source_code_path absent y NOT_OBSERVABLE +git_source.source_code_path x absent NOT_OBSERVABLE +git_source.source_code_path x y NOT_OBSERVABLE +git_source.source_code_path y absent NOT_OBSERVABLE +git_source.source_code_path y x NOT_OBSERVABLE +git_source.tag absent x NOT_OBSERVABLE +git_source.tag absent y NOT_OBSERVABLE +git_source.tag x absent NOT_OBSERVABLE +git_source.tag x y NOT_OBSERVABLE +git_source.tag y absent NOT_OBSERVABLE +git_source.tag y x NOT_OBSERVABLE +id absent x SUPPRESSED spec:output_only +id absent y SUPPRESSED spec:output_only +id x absent SUPPRESSED spec:output_only +id x y SUPPRESSED spec:output_only +id y absent SUPPRESSED spec:output_only +id y x SUPPRESSED spec:output_only +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle.started absent false NOT_OBSERVABLE +lifecycle.started absent true OK +lifecycle.started false absent OK +lifecycle.started false true OK +lifecycle.started true absent OK +lifecycle.started true false OK +name x y OK_RECREATE +name y x DEPLOY_ERROR cannot recreate resources.apps.foo: timed out: app is deleting, retrying create +oauth2_app_client_id absent x OK +oauth2_app_client_id absent y OK +oauth2_app_client_id x absent OK +oauth2_app_client_id x y OK +oauth2_app_client_id y absent OK +oauth2_app_client_id y x OK +oauth2_app_integration_id absent x OK +oauth2_app_integration_id absent y OK +oauth2_app_integration_id x absent OK +oauth2_app_integration_id x y OK +oauth2_app_integration_id y absent OK +oauth2_app_integration_id y x OK +pending_deployment.create_time absent x OK +pending_deployment.create_time absent y OK +pending_deployment.create_time x absent OK +pending_deployment.create_time x y OK +pending_deployment.create_time y absent OK +pending_deployment.create_time y x OK +pending_deployment.creator absent x OK +pending_deployment.creator absent y OK +pending_deployment.creator x absent OK +pending_deployment.creator x y OK +pending_deployment.creator y absent OK +pending_deployment.creator y x OK +pending_deployment.deployment_artifacts.source_code_path absent x OK +pending_deployment.deployment_artifacts.source_code_path absent y OK +pending_deployment.deployment_artifacts.source_code_path x absent OK +pending_deployment.deployment_artifacts.source_code_path x y OK +pending_deployment.deployment_artifacts.source_code_path y absent OK +pending_deployment.deployment_artifacts.source_code_path y x OK +pending_deployment.deployment_id absent x OK +pending_deployment.deployment_id absent y OK +pending_deployment.deployment_id x absent OK +pending_deployment.deployment_id x y OK +pending_deployment.deployment_id y absent OK +pending_deployment.deployment_id y x OK +pending_deployment.git_source.branch absent x OK +pending_deployment.git_source.branch absent y OK +pending_deployment.git_source.branch x absent OK +pending_deployment.git_source.branch x y OK +pending_deployment.git_source.branch y absent OK +pending_deployment.git_source.branch y x OK +pending_deployment.git_source.commit absent x OK +pending_deployment.git_source.commit absent y OK +pending_deployment.git_source.commit x absent OK +pending_deployment.git_source.commit x y OK +pending_deployment.git_source.commit y absent OK +pending_deployment.git_source.commit y x OK +pending_deployment.git_source.git_repository.auto_deploy absent false OK +pending_deployment.git_source.git_repository.auto_deploy absent true OK +pending_deployment.git_source.git_repository.auto_deploy false absent OK +pending_deployment.git_source.git_repository.auto_deploy false true OK +pending_deployment.git_source.git_repository.auto_deploy true absent OK +pending_deployment.git_source.git_repository.auto_deploy true false OK +pending_deployment.git_source.git_repository.caller_credential_id 1 2 OK +pending_deployment.git_source.git_repository.caller_credential_id 1 absent OK +pending_deployment.git_source.git_repository.caller_credential_id 2 1 OK +pending_deployment.git_source.git_repository.caller_credential_id 2 absent OK +pending_deployment.git_source.git_repository.caller_credential_id absent 1 OK +pending_deployment.git_source.git_repository.caller_credential_id absent 2 OK +pending_deployment.git_source.git_repository.provider x y OK +pending_deployment.git_source.git_repository.provider y x OK +pending_deployment.git_source.git_repository.url x y OK +pending_deployment.git_source.git_repository.url y x OK +pending_deployment.git_source.resolved_commit absent x OK +pending_deployment.git_source.resolved_commit absent y OK +pending_deployment.git_source.resolved_commit x absent OK +pending_deployment.git_source.resolved_commit x y OK +pending_deployment.git_source.resolved_commit y absent OK +pending_deployment.git_source.resolved_commit y x OK +pending_deployment.git_source.source_code_path absent x OK +pending_deployment.git_source.source_code_path absent y OK +pending_deployment.git_source.source_code_path x absent OK +pending_deployment.git_source.source_code_path x y OK +pending_deployment.git_source.source_code_path y absent OK +pending_deployment.git_source.source_code_path y x OK +pending_deployment.git_source.tag absent x OK +pending_deployment.git_source.tag absent y OK +pending_deployment.git_source.tag x absent OK +pending_deployment.git_source.tag x y OK +pending_deployment.git_source.tag y absent OK +pending_deployment.git_source.tag y x OK +pending_deployment.mode absent x OK +pending_deployment.mode absent y OK +pending_deployment.mode x absent OK +pending_deployment.mode x y OK +pending_deployment.mode y absent OK +pending_deployment.mode y x OK +pending_deployment.source_code_path absent x OK +pending_deployment.source_code_path absent y OK +pending_deployment.source_code_path x absent OK +pending_deployment.source_code_path x y OK +pending_deployment.source_code_path y absent OK +pending_deployment.source_code_path y x OK +pending_deployment.status.message absent x OK +pending_deployment.status.message absent y OK +pending_deployment.status.message x absent OK +pending_deployment.status.message x y OK +pending_deployment.status.message y absent OK +pending_deployment.status.message y x OK +pending_deployment.status.state absent x OK +pending_deployment.status.state absent y OK +pending_deployment.status.state x absent OK +pending_deployment.status.state x y OK +pending_deployment.status.state y absent OK +pending_deployment.status.state y x OK +pending_deployment.update_time absent x OK +pending_deployment.update_time absent y OK +pending_deployment.update_time x absent OK +pending_deployment.update_time x y OK +pending_deployment.update_time y absent OK +pending_deployment.update_time y x OK +service_principal_client_id absent x OK +service_principal_client_id absent y OK +service_principal_client_id x absent OK +service_principal_client_id x y OK +service_principal_client_id y absent OK +service_principal_client_id y x OK +service_principal_id 1 2 OK +service_principal_id 1 absent OK +service_principal_id 2 1 OK +service_principal_id 2 absent OK +service_principal_id absent 1 OK +service_principal_id absent 2 OK +service_principal_name absent x OK +service_principal_name absent y OK +service_principal_name x absent OK +service_principal_name x y OK +service_principal_name y absent OK +service_principal_name y x OK +source_code_path absent x SUPPRESSED no active deployment +source_code_path absent y SUPPRESSED no active deployment +source_code_path x absent SUPPRESSED no active deployment +source_code_path x y SUPPRESSED no active deployment +source_code_path y absent SUPPRESSED no active deployment +source_code_path y x SUPPRESSED no active deployment +space absent x OK +space absent y OK +space x absent OK +space x y OK +space y absent OK +space y x OK +thumbnail_url absent x OK +thumbnail_url absent y OK +thumbnail_url x absent OK +thumbnail_url x y OK +thumbnail_url y absent OK +thumbnail_url y x OK +update_time absent x OK +update_time absent y OK +update_time x absent OK +update_time x y OK +update_time y absent OK +update_time y x OK +updater absent x OK +updater absent y OK +updater x absent OK +updater x y OK +updater y absent OK +updater y x OK +url absent x SUPPRESSED spec:output_only +url absent y SUPPRESSED spec:output_only +url x absent SUPPRESSED spec:output_only +url x y SUPPRESSED spec:output_only +url y absent SUPPRESSED spec:output_only +url y x SUPPRESSED spec:output_only +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent POST_DEPLOY_DRIFT usage_policy_id +usage_policy_id x y OK +usage_policy_id y absent POST_DEPLOY_DRIFT usage_policy_id +usage_policy_id y x OK + +=== summary +OK 413 +OK_RECREATE 1 +SUPPRESSED 21 +NOT_OBSERVABLE 59 +POST_DEPLOY_DRIFT 13 +DEPLOY_ERROR 1 + +=== not covered: 49 fields under a slice or map +active_deployment.command[*] +active_deployment.env_vars[*].name +active_deployment.env_vars[*].value +active_deployment.env_vars[*].value_from +config.command[*] +config.env[*].name +config.env[*].value +config.env[*].value_from +effective_user_api_scopes[*] +pending_deployment.command[*] +pending_deployment.env_vars[*].name +pending_deployment.env_vars[*].value +pending_deployment.env_vars[*].value_from +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +resources[*].app.name +resources[*].app.permission +resources[*].database.database_name +resources[*].database.instance_name +resources[*].database.permission +resources[*].description +resources[*].experiment.experiment_id +resources[*].experiment.permission +resources[*].genie_space.name +resources[*].genie_space.permission +resources[*].genie_space.space_id +resources[*].job.id +resources[*].job.permission +resources[*].name +resources[*].postgres.branch +resources[*].postgres.database +resources[*].postgres.permission +resources[*].secret.key +resources[*].secret.permission +resources[*].secret.scope +resources[*].serving_endpoint.name +resources[*].serving_endpoint.permission +resources[*].sql_warehouse.id +resources[*].sql_warehouse.permission +resources[*].uc_securable.permission +resources[*].uc_securable.securable_full_name +resources[*].uc_securable.securable_kind +resources[*].uc_securable.securable_type +telemetry_export_destinations[*].unity_catalog.logs_table +telemetry_export_destinations[*].unity_catalog.metrics_table +telemetry_export_destinations[*].unity_catalog.traces_table +user_api_scopes[*] diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 07b1c9c31ac..1ef4b3393ff 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -1,566 +1,34 @@ === app.yml.tmpl -active_deployment.create_time absent->x OK -active_deployment.create_time absent->y OK -active_deployment.create_time x->absent OK -active_deployment.create_time x->y OK -active_deployment.create_time y->absent OK -active_deployment.create_time y->x OK -active_deployment.creator absent->x OK -active_deployment.creator absent->y OK -active_deployment.creator x->absent OK -active_deployment.creator x->y OK -active_deployment.creator y->absent OK -active_deployment.creator y->x OK -active_deployment.deployment_artifacts.source_code_path absent->x OK -active_deployment.deployment_artifacts.source_code_path absent->y OK -active_deployment.deployment_artifacts.source_code_path x->absent OK -active_deployment.deployment_artifacts.source_code_path x->y OK -active_deployment.deployment_artifacts.source_code_path y->absent OK -active_deployment.deployment_artifacts.source_code_path y->x OK -active_deployment.deployment_id absent->x OK -active_deployment.deployment_id absent->y OK -active_deployment.deployment_id x->absent OK -active_deployment.deployment_id x->y OK -active_deployment.deployment_id y->absent OK -active_deployment.deployment_id y->x OK -active_deployment.git_source.branch absent->x OK -active_deployment.git_source.branch absent->y OK -active_deployment.git_source.branch x->absent OK -active_deployment.git_source.branch x->y OK -active_deployment.git_source.branch y->absent OK -active_deployment.git_source.branch y->x OK -active_deployment.git_source.commit absent->x OK -active_deployment.git_source.commit absent->y OK -active_deployment.git_source.commit x->absent OK -active_deployment.git_source.commit x->y OK -active_deployment.git_source.commit y->absent OK -active_deployment.git_source.commit y->x OK -active_deployment.git_source.git_repository.auto_deploy absent->false OK -active_deployment.git_source.git_repository.auto_deploy absent->true OK -active_deployment.git_source.git_repository.auto_deploy false->absent OK -active_deployment.git_source.git_repository.auto_deploy false->true OK -active_deployment.git_source.git_repository.auto_deploy true->absent OK -active_deployment.git_source.git_repository.auto_deploy true->false OK -active_deployment.git_source.git_repository.caller_credential_id 1->2 OK -active_deployment.git_source.git_repository.caller_credential_id 1->absent OK -active_deployment.git_source.git_repository.caller_credential_id 2->1 OK -active_deployment.git_source.git_repository.caller_credential_id 2->absent OK -active_deployment.git_source.git_repository.caller_credential_id absent->1 OK -active_deployment.git_source.git_repository.caller_credential_id absent->2 OK -active_deployment.git_source.git_repository.provider x->y OK -active_deployment.git_source.git_repository.provider y->x OK -active_deployment.git_source.git_repository.url x->y OK -active_deployment.git_source.git_repository.url y->x OK -active_deployment.git_source.resolved_commit absent->x OK -active_deployment.git_source.resolved_commit absent->y OK -active_deployment.git_source.resolved_commit x->absent OK -active_deployment.git_source.resolved_commit x->y OK -active_deployment.git_source.resolved_commit y->absent OK -active_deployment.git_source.resolved_commit y->x OK -active_deployment.git_source.source_code_path absent->x OK -active_deployment.git_source.source_code_path absent->y OK -active_deployment.git_source.source_code_path x->absent OK -active_deployment.git_source.source_code_path x->y OK -active_deployment.git_source.source_code_path y->absent OK -active_deployment.git_source.source_code_path y->x OK -active_deployment.git_source.tag absent->x OK -active_deployment.git_source.tag absent->y OK -active_deployment.git_source.tag x->absent OK -active_deployment.git_source.tag x->y OK -active_deployment.git_source.tag y->absent OK -active_deployment.git_source.tag y->x OK -active_deployment.mode absent->x OK -active_deployment.mode absent->y OK -active_deployment.mode x->absent OK -active_deployment.mode x->y OK -active_deployment.mode y->absent OK -active_deployment.mode y->x OK -active_deployment.source_code_path absent->x OK -active_deployment.source_code_path absent->y OK -active_deployment.source_code_path x->absent OK -active_deployment.source_code_path x->y OK -active_deployment.source_code_path y->absent OK -active_deployment.source_code_path y->x OK -active_deployment.status.message absent->x OK -active_deployment.status.message absent->y OK -active_deployment.status.message x->absent OK -active_deployment.status.message x->y OK -active_deployment.status.message y->absent OK -active_deployment.status.message y->x OK -active_deployment.status.state absent->x OK -active_deployment.status.state absent->y OK -active_deployment.status.state x->absent OK -active_deployment.status.state x->y OK -active_deployment.status.state y->absent OK -active_deployment.status.state y->x OK -active_deployment.update_time absent->x OK -active_deployment.update_time absent->y OK -active_deployment.update_time x->absent OK -active_deployment.update_time x->y OK -active_deployment.update_time y->absent OK -active_deployment.update_time y->x OK -app_status.message absent->x OK -app_status.message absent->y OK -app_status.message x->absent OK -app_status.message x->y OK -app_status.message y->absent OK -app_status.message y->x OK -app_status.running_instances 1->2 OK -app_status.running_instances 1->absent OK -app_status.running_instances 2->1 OK -app_status.running_instances 2->absent OK -app_status.running_instances absent->1 OK -app_status.running_instances absent->2 OK -app_status.state absent->x OK -app_status.state absent->y OK -app_status.state x->absent OK -app_status.state x->y OK -app_status.state y->absent OK -app_status.state y->x OK -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent DRIFT budget_policy_id -budget_policy_id x->y OK -budget_policy_id y->absent DRIFT budget_policy_id -budget_policy_id y->x OK -compute_max_instances 1->2 OK -compute_max_instances 1->absent DRIFT compute_max_instances -compute_max_instances 2->1 OK -compute_max_instances 2->absent DRIFT compute_max_instances -compute_max_instances absent->1 OK -compute_max_instances absent->2 OK -compute_min_instances 1->2 OK -compute_min_instances 1->absent DRIFT compute_min_instances -compute_min_instances 2->1 OK -compute_min_instances 2->absent DRIFT compute_min_instances -compute_min_instances absent->1 OK -compute_min_instances absent->2 OK -compute_size LARGE->MEDIUM OK -compute_size LARGE->absent OK -compute_size MEDIUM->LARGE OK -compute_size MEDIUM->absent OK -compute_size absent->LARGE OK -compute_size absent->MEDIUM SUPPRESSED remote_already_set -compute_status.active_instances 1->2 OK -compute_status.active_instances 1->absent OK -compute_status.active_instances 2->1 OK -compute_status.active_instances 2->absent OK -compute_status.active_instances absent->1 OK -compute_status.active_instances absent->2 OK -compute_status.message absent->x OK -compute_status.message absent->y OK -compute_status.message x->absent OK -compute_status.message x->y OK -compute_status.message y->absent OK -compute_status.message y->x OK -compute_status.state absent->x OK -compute_status.state absent->y OK -compute_status.state x->absent OK -compute_status.state x->y OK -compute_status.state y->absent OK -compute_status.state y->x OK -create_time absent->x OK -create_time absent->y OK -create_time x->absent OK -create_time x->y OK -create_time y->absent OK -create_time y->x OK -creator absent->x OK -creator absent->y OK -creator x->absent OK -creator x->y OK -creator y->absent OK -creator y->x OK -default_git_source.branch absent->x OK -default_git_source.branch absent->y OK -default_git_source.branch x->absent OK -default_git_source.branch x->y OK -default_git_source.branch y->absent OK -default_git_source.branch y->x OK -default_git_source.commit absent->x OK -default_git_source.commit absent->y OK -default_git_source.commit x->absent OK -default_git_source.commit x->y OK -default_git_source.commit y->absent OK -default_git_source.commit y->x OK -default_git_source.git_repository.auto_deploy absent->false OK -default_git_source.git_repository.auto_deploy absent->true OK -default_git_source.git_repository.auto_deploy false->absent OK -default_git_source.git_repository.auto_deploy false->true OK -default_git_source.git_repository.auto_deploy true->absent OK -default_git_source.git_repository.auto_deploy true->false OK -default_git_source.git_repository.caller_credential_id 1->2 OK -default_git_source.git_repository.caller_credential_id 1->absent OK -default_git_source.git_repository.caller_credential_id 2->1 OK -default_git_source.git_repository.caller_credential_id 2->absent OK -default_git_source.git_repository.caller_credential_id absent->1 OK -default_git_source.git_repository.caller_credential_id absent->2 OK -default_git_source.git_repository.provider x->y OK -default_git_source.git_repository.provider y->x OK -default_git_source.git_repository.url x->y OK -default_git_source.git_repository.url y->x OK -default_git_source.resolved_commit absent->x OK -default_git_source.resolved_commit absent->y OK -default_git_source.resolved_commit x->absent OK -default_git_source.resolved_commit x->y OK -default_git_source.resolved_commit y->absent OK -default_git_source.resolved_commit y->x OK -default_git_source.source_code_path absent->x OK -default_git_source.source_code_path absent->y OK -default_git_source.source_code_path x->absent OK -default_git_source.source_code_path x->y OK -default_git_source.source_code_path y->absent OK -default_git_source.source_code_path y->x OK -default_git_source.tag absent->x OK -default_git_source.tag absent->y OK -default_git_source.tag x->absent OK -default_git_source.tag x->y OK -default_git_source.tag y->absent OK -default_git_source.tag y->x OK -default_source_code_path absent->x OK -default_source_code_path absent->y OK -default_source_code_path x->absent OK -default_source_code_path x->y OK -default_source_code_path y->absent OK -default_source_code_path y->x OK -description absent->x OK -description absent->y OK -description x->absent DRIFT description -description x->y OK -description y->absent DRIFT description -description y->x OK -effective_budget_policy_id absent->x OK -effective_budget_policy_id absent->y OK -effective_budget_policy_id x->absent OK -effective_budget_policy_id x->y OK -effective_budget_policy_id y->absent OK -effective_budget_policy_id y->x OK -effective_usage_policy_id absent->x OK -effective_usage_policy_id absent->y OK -effective_usage_policy_id x->absent OK -effective_usage_policy_id x->y OK -effective_usage_policy_id y->absent OK -effective_usage_policy_id y->x OK -forward_user_access_token absent->false OK -forward_user_access_token absent->true OK -forward_user_access_token false->absent SUPPRESSED empty -forward_user_access_token false->true OK -forward_user_access_token true->absent OK -forward_user_access_token true->false OK -git_repository.auto_deploy absent->false OK -git_repository.auto_deploy absent->true OK -git_repository.auto_deploy false->absent SUPPRESSED empty -git_repository.auto_deploy false->true OK -git_repository.auto_deploy true->absent DRIFT git_repository.auto_deploy -git_repository.auto_deploy true->false OK -git_repository.caller_credential_id 1->2 OK -git_repository.caller_credential_id 1->absent DRIFT git_repository.caller_credential_id -git_repository.caller_credential_id 2->1 OK -git_repository.caller_credential_id 2->absent DRIFT git_repository.caller_credential_id -git_repository.caller_credential_id absent->1 OK -git_repository.caller_credential_id absent->2 OK -git_repository.provider x->y OK -git_repository.provider y->x OK -git_repository.url x->y OK -git_repository.url y->x OK -git_source.branch absent->x NOT_OBSERVABLE -git_source.branch absent->y NOT_OBSERVABLE -git_source.branch x->absent NOT_OBSERVABLE -git_source.branch x->y NOT_OBSERVABLE -git_source.branch y->absent NOT_OBSERVABLE -git_source.branch y->x NOT_OBSERVABLE -git_source.commit absent->x NOT_OBSERVABLE -git_source.commit absent->y NOT_OBSERVABLE -git_source.commit x->absent NOT_OBSERVABLE -git_source.commit x->y NOT_OBSERVABLE -git_source.commit y->absent NOT_OBSERVABLE -git_source.commit y->x NOT_OBSERVABLE -git_source.git_repository.auto_deploy absent->false NOT_OBSERVABLE -git_source.git_repository.auto_deploy absent->true NOT_OBSERVABLE -git_source.git_repository.auto_deploy false->absent NOT_OBSERVABLE -git_source.git_repository.auto_deploy false->true NOT_OBSERVABLE -git_source.git_repository.auto_deploy true->absent NOT_OBSERVABLE -git_source.git_repository.auto_deploy true->false NOT_OBSERVABLE -git_source.git_repository.caller_credential_id 1->2 NOT_OBSERVABLE -git_source.git_repository.caller_credential_id 1->absent NOT_OBSERVABLE -git_source.git_repository.caller_credential_id 2->1 NOT_OBSERVABLE -git_source.git_repository.caller_credential_id 2->absent NOT_OBSERVABLE -git_source.git_repository.caller_credential_id absent->1 NOT_OBSERVABLE -git_source.git_repository.caller_credential_id absent->2 NOT_OBSERVABLE -git_source.git_repository.provider x->y NOT_OBSERVABLE -git_source.git_repository.provider y->x NOT_OBSERVABLE -git_source.git_repository.url x->y NOT_OBSERVABLE -git_source.git_repository.url y->x NOT_OBSERVABLE -git_source.resolved_commit absent->x NOT_OBSERVABLE -git_source.resolved_commit absent->y NOT_OBSERVABLE -git_source.resolved_commit x->absent NOT_OBSERVABLE -git_source.resolved_commit x->y NOT_OBSERVABLE -git_source.resolved_commit y->absent NOT_OBSERVABLE -git_source.resolved_commit y->x NOT_OBSERVABLE -git_source.source_code_path absent->x NOT_OBSERVABLE -git_source.source_code_path absent->y NOT_OBSERVABLE -git_source.source_code_path x->absent NOT_OBSERVABLE -git_source.source_code_path x->y NOT_OBSERVABLE -git_source.source_code_path y->absent NOT_OBSERVABLE -git_source.source_code_path y->x NOT_OBSERVABLE -git_source.tag absent->x NOT_OBSERVABLE -git_source.tag absent->y NOT_OBSERVABLE -git_source.tag x->absent NOT_OBSERVABLE -git_source.tag x->y NOT_OBSERVABLE -git_source.tag y->absent NOT_OBSERVABLE -git_source.tag y->x NOT_OBSERVABLE -id absent->x SUPPRESSED spec:output_only -id absent->y SUPPRESSED spec:output_only -id x->absent SUPPRESSED spec:output_only -id x->y SUPPRESSED spec:output_only -id y->absent SUPPRESSED spec:output_only -id y->x SUPPRESSED spec:output_only -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle.started absent->false NOT_OBSERVABLE -lifecycle.started absent->true OK -lifecycle.started false->absent OK -lifecycle.started false->true OK -lifecycle.started true->absent OK -lifecycle.started true->false OK -name - SKIPPED rename waits out an asynchronous delete -oauth2_app_client_id absent->x OK -oauth2_app_client_id absent->y OK -oauth2_app_client_id x->absent OK -oauth2_app_client_id x->y OK -oauth2_app_client_id y->absent OK -oauth2_app_client_id y->x OK -oauth2_app_integration_id absent->x OK -oauth2_app_integration_id absent->y OK -oauth2_app_integration_id x->absent OK -oauth2_app_integration_id x->y OK -oauth2_app_integration_id y->absent OK -oauth2_app_integration_id y->x OK -pending_deployment.create_time absent->x OK -pending_deployment.create_time absent->y OK -pending_deployment.create_time x->absent OK -pending_deployment.create_time x->y OK -pending_deployment.create_time y->absent OK -pending_deployment.create_time y->x OK -pending_deployment.creator absent->x OK -pending_deployment.creator absent->y OK -pending_deployment.creator x->absent OK -pending_deployment.creator x->y OK -pending_deployment.creator y->absent OK -pending_deployment.creator y->x OK -pending_deployment.deployment_artifacts.source_code_path absent->x OK -pending_deployment.deployment_artifacts.source_code_path absent->y OK -pending_deployment.deployment_artifacts.source_code_path x->absent OK -pending_deployment.deployment_artifacts.source_code_path x->y OK -pending_deployment.deployment_artifacts.source_code_path y->absent OK -pending_deployment.deployment_artifacts.source_code_path y->x OK -pending_deployment.deployment_id absent->x OK -pending_deployment.deployment_id absent->y OK -pending_deployment.deployment_id x->absent OK -pending_deployment.deployment_id x->y OK -pending_deployment.deployment_id y->absent OK -pending_deployment.deployment_id y->x OK -pending_deployment.git_source.branch absent->x OK -pending_deployment.git_source.branch absent->y OK -pending_deployment.git_source.branch x->absent OK -pending_deployment.git_source.branch x->y OK -pending_deployment.git_source.branch y->absent OK -pending_deployment.git_source.branch y->x OK -pending_deployment.git_source.commit absent->x OK -pending_deployment.git_source.commit absent->y OK -pending_deployment.git_source.commit x->absent OK -pending_deployment.git_source.commit x->y OK -pending_deployment.git_source.commit y->absent OK -pending_deployment.git_source.commit y->x OK -pending_deployment.git_source.git_repository.auto_deploy absent->false OK -pending_deployment.git_source.git_repository.auto_deploy absent->true OK -pending_deployment.git_source.git_repository.auto_deploy false->absent OK -pending_deployment.git_source.git_repository.auto_deploy false->true OK -pending_deployment.git_source.git_repository.auto_deploy true->absent OK -pending_deployment.git_source.git_repository.auto_deploy true->false OK -pending_deployment.git_source.git_repository.caller_credential_id 1->2 OK -pending_deployment.git_source.git_repository.caller_credential_id 1->absent OK -pending_deployment.git_source.git_repository.caller_credential_id 2->1 OK -pending_deployment.git_source.git_repository.caller_credential_id 2->absent OK -pending_deployment.git_source.git_repository.caller_credential_id absent->1 OK -pending_deployment.git_source.git_repository.caller_credential_id absent->2 OK -pending_deployment.git_source.git_repository.provider x->y OK -pending_deployment.git_source.git_repository.provider y->x OK -pending_deployment.git_source.git_repository.url x->y OK -pending_deployment.git_source.git_repository.url y->x OK -pending_deployment.git_source.resolved_commit absent->x OK -pending_deployment.git_source.resolved_commit absent->y OK -pending_deployment.git_source.resolved_commit x->absent OK -pending_deployment.git_source.resolved_commit x->y OK -pending_deployment.git_source.resolved_commit y->absent OK -pending_deployment.git_source.resolved_commit y->x OK -pending_deployment.git_source.source_code_path absent->x OK -pending_deployment.git_source.source_code_path absent->y OK -pending_deployment.git_source.source_code_path x->absent OK -pending_deployment.git_source.source_code_path x->y OK -pending_deployment.git_source.source_code_path y->absent OK -pending_deployment.git_source.source_code_path y->x OK -pending_deployment.git_source.tag absent->x OK -pending_deployment.git_source.tag absent->y OK -pending_deployment.git_source.tag x->absent OK -pending_deployment.git_source.tag x->y OK -pending_deployment.git_source.tag y->absent OK -pending_deployment.git_source.tag y->x OK -pending_deployment.mode absent->x OK -pending_deployment.mode absent->y OK -pending_deployment.mode x->absent OK -pending_deployment.mode x->y OK -pending_deployment.mode y->absent OK -pending_deployment.mode y->x OK -pending_deployment.source_code_path absent->x OK -pending_deployment.source_code_path absent->y OK -pending_deployment.source_code_path x->absent OK -pending_deployment.source_code_path x->y OK -pending_deployment.source_code_path y->absent OK -pending_deployment.source_code_path y->x OK -pending_deployment.status.message absent->x OK -pending_deployment.status.message absent->y OK -pending_deployment.status.message x->absent OK -pending_deployment.status.message x->y OK -pending_deployment.status.message y->absent OK -pending_deployment.status.message y->x OK -pending_deployment.status.state absent->x OK -pending_deployment.status.state absent->y OK -pending_deployment.status.state x->absent OK -pending_deployment.status.state x->y OK -pending_deployment.status.state y->absent OK -pending_deployment.status.state y->x OK -pending_deployment.update_time absent->x OK -pending_deployment.update_time absent->y OK -pending_deployment.update_time x->absent OK -pending_deployment.update_time x->y OK -pending_deployment.update_time y->absent OK -pending_deployment.update_time y->x OK -service_principal_client_id absent->x OK -service_principal_client_id absent->y OK -service_principal_client_id x->absent OK -service_principal_client_id x->y OK -service_principal_client_id y->absent OK -service_principal_client_id y->x OK -service_principal_id 1->2 OK -service_principal_id 1->absent OK -service_principal_id 2->1 OK -service_principal_id 2->absent OK -service_principal_id absent->1 OK -service_principal_id absent->2 OK -service_principal_name absent->x OK -service_principal_name absent->y OK -service_principal_name x->absent OK -service_principal_name x->y OK -service_principal_name y->absent OK -service_principal_name y->x OK -source_code_path absent->x SUPPRESSED no active deployment -source_code_path absent->y SUPPRESSED no active deployment -source_code_path x->absent SUPPRESSED no active deployment -source_code_path x->y SUPPRESSED no active deployment -source_code_path y->absent SUPPRESSED no active deployment -source_code_path y->x SUPPRESSED no active deployment -space absent->x OK -space absent->y OK -space x->absent OK -space x->y OK -space y->absent OK -space y->x OK -thumbnail_url absent->x OK -thumbnail_url absent->y OK -thumbnail_url x->absent OK -thumbnail_url x->y OK -thumbnail_url y->absent OK -thumbnail_url y->x OK -update_time absent->x OK -update_time absent->y OK -update_time x->absent OK -update_time x->y OK -update_time y->absent OK -update_time y->x OK -updater absent->x OK -updater absent->y OK -updater x->absent OK -updater x->y OK -updater y->absent OK -updater y->x OK -url absent->x SUPPRESSED spec:output_only -url absent->y SUPPRESSED spec:output_only -url x->absent SUPPRESSED spec:output_only -url x->y SUPPRESSED spec:output_only -url y->absent SUPPRESSED spec:output_only -url y->x SUPPRESSED spec:output_only -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent DRIFT usage_policy_id -usage_policy_id x->y OK -usage_policy_id y->absent DRIFT usage_policy_id -usage_policy_id y->x OK - -=== summary -OK 413 -SUPPRESSED 21 -NOT_OBSERVABLE 59 -DRIFT 13 -SKIPPED 1 - -=== not covered: 49 fields under a slice or map -active_deployment.command[*] -active_deployment.env_vars[*].name -active_deployment.env_vars[*].value -active_deployment.env_vars[*].value_from -config.command[*] -config.env[*].name -config.env[*].value -config.env[*].value_from -effective_user_api_scopes[*] -pending_deployment.command[*] -pending_deployment.env_vars[*].name -pending_deployment.env_vars[*].value -pending_deployment.env_vars[*].value_from -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -resources[*].app.name -resources[*].app.permission -resources[*].database.database_name -resources[*].database.instance_name -resources[*].database.permission -resources[*].description -resources[*].experiment.experiment_id -resources[*].experiment.permission -resources[*].genie_space.name -resources[*].genie_space.permission -resources[*].genie_space.space_id -resources[*].job.id -resources[*].job.permission -resources[*].name -resources[*].postgres.branch -resources[*].postgres.database -resources[*].postgres.permission -resources[*].secret.key -resources[*].secret.permission -resources[*].secret.scope -resources[*].serving_endpoint.name -resources[*].serving_endpoint.permission -resources[*].sql_warehouse.id -resources[*].sql_warehouse.permission -resources[*].uc_securable.permission -resources[*].uc_securable.securable_full_name -resources[*].uc_securable.securable_kind -resources[*].uc_securable.securable_type -telemetry_export_destinations[*].unity_catalog.logs_table -telemetry_export_destinations[*].unity_catalog.metrics_table -telemetry_export_destinations[*].unity_catalog.traces_table -user_api_scopes[*] +budget_policy_id x absent POST_DEPLOY_DRIFT budget_policy_id +budget_policy_id y absent POST_DEPLOY_DRIFT budget_policy_id +compute_max_instances 1 absent POST_DEPLOY_DRIFT compute_max_instances +compute_max_instances 2 absent POST_DEPLOY_DRIFT compute_max_instances +compute_min_instances 1 absent POST_DEPLOY_DRIFT compute_min_instances +compute_min_instances 2 absent POST_DEPLOY_DRIFT compute_min_instances +compute_size absent MEDIUM SUPPRESSED remote_already_set +description x absent POST_DEPLOY_DRIFT description +description y absent POST_DEPLOY_DRIFT description +git_repository.auto_deploy true absent POST_DEPLOY_DRIFT git_repository.auto_deploy +git_repository.caller_credential_id 1 absent POST_DEPLOY_DRIFT git_repository.caller_credential_id +git_repository.caller_credential_id 2 absent POST_DEPLOY_DRIFT git_repository.caller_credential_id +id absent x SUPPRESSED spec:output_only +id absent y SUPPRESSED spec:output_only +id x absent SUPPRESSED spec:output_only +id x y SUPPRESSED spec:output_only +id y absent SUPPRESSED spec:output_only +id y x SUPPRESSED spec:output_only +name y x DEPLOY_ERROR cannot recreate resources.apps.foo: timed out: app is deleting, retrying create +source_code_path absent x SUPPRESSED no active deployment +source_code_path absent y SUPPRESSED no active deployment +source_code_path x absent SUPPRESSED no active deployment +source_code_path x y SUPPRESSED no active deployment +source_code_path y absent SUPPRESSED no active deployment +source_code_path y x SUPPRESSED no active deployment +url absent x SUPPRESSED spec:output_only +url absent y SUPPRESSED spec:output_only +url x absent SUPPRESSED spec:output_only +url x y SUPPRESSED spec:output_only +url y absent SUPPRESSED spec:output_only +url y x SUPPRESSED spec:output_only +usage_policy_id x absent POST_DEPLOY_DRIFT usage_policy_id +usage_policy_id y absent POST_DEPLOY_DRIFT usage_policy_id diff --git a/bundle/direct/tests/output/catalogs.full.txt b/bundle/direct/tests/output/catalogs.full.txt new file mode 100644 index 00000000000..0bffe092068 --- /dev/null +++ b/bundle/direct/tests/output/catalogs.full.txt @@ -0,0 +1,155 @@ +=== catalog.yml.tmpl +comment absent x OK +comment absent y OK +comment x absent OK +comment x y OK +comment y absent OK +comment y x OK +connection_name absent x OK_RECREATE +connection_name absent y OK_RECREATE +connection_name x absent OK_RECREATE +connection_name x y OK_RECREATE +connection_name y absent OK_RECREATE +connection_name y x OK_RECREATE +custom_max_retention_hours 1 2 OK +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 1 OK +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours absent 1 OK +custom_max_retention_hours absent 2 OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y x OK +managed_encryption_settings.azure_encryption_settings.azure_tenant_id x y OK +managed_encryption_settings.azure_encryption_settings.azure_tenant_id y x OK +managed_encryption_settings.azure_key_vault_key_id absent x OK +managed_encryption_settings.azure_key_vault_key_id absent y OK +managed_encryption_settings.azure_key_vault_key_id x absent OK +managed_encryption_settings.azure_key_vault_key_id x y OK +managed_encryption_settings.azure_key_vault_key_id y absent OK +managed_encryption_settings.azure_key_vault_key_id y x OK +managed_encryption_settings.customer_managed_key_id absent x OK +managed_encryption_settings.customer_managed_key_id absent y OK +managed_encryption_settings.customer_managed_key_id x absent OK +managed_encryption_settings.customer_managed_key_id x y OK +managed_encryption_settings.customer_managed_key_id y absent OK +managed_encryption_settings.customer_managed_key_id y x OK +name x y OK +name y x OK +provider_name absent x OK_RECREATE +provider_name absent y OK_RECREATE +provider_name x absent OK_RECREATE +provider_name x y OK_RECREATE +provider_name y absent OK_RECREATE +provider_name y x OK_RECREATE +share_name absent x OK_RECREATE +share_name absent y OK_RECREATE +share_name x absent OK_RECREATE +share_name x y OK_RECREATE +share_name y absent OK_RECREATE +share_name y x OK_RECREATE +storage_root absent x OK_RECREATE +storage_root absent y OK_RECREATE +storage_root x absent OK_RECREATE +storage_root x y OK_RECREATE +storage_root y absent OK_RECREATE +storage_root y x OK_RECREATE + +=== catalog_optional_fields.yml.tmpl +comment absent x OK +comment absent y OK +comment x absent OK +comment x y OK +comment y absent OK +comment y x OK +connection_name absent x OK_RECREATE +connection_name absent y OK_RECREATE +connection_name x absent OK_RECREATE +connection_name x y OK_RECREATE +connection_name y absent OK_RECREATE +connection_name y x OK_RECREATE +custom_max_retention_hours 1 2 OK +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 1 OK +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours absent 1 OK +custom_max_retention_hours absent 2 OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent x OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x y OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y absent OK +managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y x OK +managed_encryption_settings.azure_encryption_settings.azure_tenant_id x y OK +managed_encryption_settings.azure_encryption_settings.azure_tenant_id y x OK +managed_encryption_settings.azure_key_vault_key_id absent x OK +managed_encryption_settings.azure_key_vault_key_id absent y OK +managed_encryption_settings.azure_key_vault_key_id x absent OK +managed_encryption_settings.azure_key_vault_key_id x y OK +managed_encryption_settings.azure_key_vault_key_id y absent OK +managed_encryption_settings.azure_key_vault_key_id y x OK +managed_encryption_settings.customer_managed_key_id absent x OK +managed_encryption_settings.customer_managed_key_id absent y OK +managed_encryption_settings.customer_managed_key_id x absent OK +managed_encryption_settings.customer_managed_key_id x y OK +managed_encryption_settings.customer_managed_key_id y absent OK +managed_encryption_settings.customer_managed_key_id y x OK +name x y OK +name y x OK +provider_name absent x OK_RECREATE +provider_name absent y OK_RECREATE +provider_name x absent OK_RECREATE +provider_name x y OK_RECREATE +provider_name y absent OK_RECREATE +provider_name y x OK_RECREATE +share_name absent x OK_RECREATE +share_name absent y OK_RECREATE +share_name x absent OK_RECREATE +share_name x y OK_RECREATE +share_name y absent OK_RECREATE +share_name y x OK_RECREATE +storage_root absent x OK_RECREATE +storage_root absent y OK_RECREATE +storage_root x absent OK_RECREATE +storage_root x y OK_RECREATE +storage_root y absent OK_RECREATE +storage_root y x OK_RECREATE + +=== summary +OK 76 +OK_RECREATE 48 +NOT_OBSERVABLE 12 +POST_DEPLOY_DRIFT 4 + +=== not covered: 4 fields under a slice or map +grants[*].principal +grants[*].privileges[*] +options.* +properties.* diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index e6c54bcc206..6d38140bf79 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -1,155 +1,7 @@ === catalog.yml.tmpl -comment absent->x OK -comment absent->y OK -comment x->absent OK -comment x->y OK -comment y->absent OK -comment y->x OK -connection_name absent->x OK_RECREATE -connection_name absent->y OK_RECREATE -connection_name x->absent OK_RECREATE -connection_name x->y OK_RECREATE -connection_name y->absent OK_RECREATE -connection_name y->x OK_RECREATE -custom_max_retention_hours 1->2 OK -custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours -custom_max_retention_hours 2->1 OK -custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours -custom_max_retention_hours absent->1 OK -custom_max_retention_hours absent->2 OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent->x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent->y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x->absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x->y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y->absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y->x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent->x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent->y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x->absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x->y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y->absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y->x OK -managed_encryption_settings.azure_encryption_settings.azure_tenant_id x->y OK -managed_encryption_settings.azure_encryption_settings.azure_tenant_id y->x OK -managed_encryption_settings.azure_key_vault_key_id absent->x OK -managed_encryption_settings.azure_key_vault_key_id absent->y OK -managed_encryption_settings.azure_key_vault_key_id x->absent OK -managed_encryption_settings.azure_key_vault_key_id x->y OK -managed_encryption_settings.azure_key_vault_key_id y->absent OK -managed_encryption_settings.azure_key_vault_key_id y->x OK -managed_encryption_settings.customer_managed_key_id absent->x OK -managed_encryption_settings.customer_managed_key_id absent->y OK -managed_encryption_settings.customer_managed_key_id x->absent OK -managed_encryption_settings.customer_managed_key_id x->y OK -managed_encryption_settings.customer_managed_key_id y->absent OK -managed_encryption_settings.customer_managed_key_id y->x OK -name x->y OK -name y->x OK -provider_name absent->x OK_RECREATE -provider_name absent->y OK_RECREATE -provider_name x->absent OK_RECREATE -provider_name x->y OK_RECREATE -provider_name y->absent OK_RECREATE -provider_name y->x OK_RECREATE -share_name absent->x OK_RECREATE -share_name absent->y OK_RECREATE -share_name x->absent OK_RECREATE -share_name x->y OK_RECREATE -share_name y->absent OK_RECREATE -share_name y->x OK_RECREATE -storage_root absent->x OK_RECREATE -storage_root absent->y OK_RECREATE -storage_root x->absent OK_RECREATE -storage_root x->y OK_RECREATE -storage_root y->absent OK_RECREATE -storage_root y->x OK_RECREATE +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours === catalog_optional_fields.yml.tmpl -comment absent->x OK -comment absent->y OK -comment x->absent OK -comment x->y OK -comment y->absent OK -comment y->x OK -connection_name absent->x OK_RECREATE -connection_name absent->y OK_RECREATE -connection_name x->absent OK_RECREATE -connection_name x->y OK_RECREATE -connection_name y->absent OK_RECREATE -connection_name y->x OK_RECREATE -custom_max_retention_hours 1->2 OK -custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours -custom_max_retention_hours 2->1 OK -custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours -custom_max_retention_hours absent->1 OK -custom_max_retention_hours absent->2 OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent->x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent->y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x->absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x->y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y->absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y->x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent->x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent->y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x->absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x->y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y->absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y->x OK -managed_encryption_settings.azure_encryption_settings.azure_tenant_id x->y OK -managed_encryption_settings.azure_encryption_settings.azure_tenant_id y->x OK -managed_encryption_settings.azure_key_vault_key_id absent->x OK -managed_encryption_settings.azure_key_vault_key_id absent->y OK -managed_encryption_settings.azure_key_vault_key_id x->absent OK -managed_encryption_settings.azure_key_vault_key_id x->y OK -managed_encryption_settings.azure_key_vault_key_id y->absent OK -managed_encryption_settings.azure_key_vault_key_id y->x OK -managed_encryption_settings.customer_managed_key_id absent->x OK -managed_encryption_settings.customer_managed_key_id absent->y OK -managed_encryption_settings.customer_managed_key_id x->absent OK -managed_encryption_settings.customer_managed_key_id x->y OK -managed_encryption_settings.customer_managed_key_id y->absent OK -managed_encryption_settings.customer_managed_key_id y->x OK -name x->y OK -name y->x OK -provider_name absent->x OK_RECREATE -provider_name absent->y OK_RECREATE -provider_name x->absent OK_RECREATE -provider_name x->y OK_RECREATE -provider_name y->absent OK_RECREATE -provider_name y->x OK_RECREATE -share_name absent->x OK_RECREATE -share_name absent->y OK_RECREATE -share_name x->absent OK_RECREATE -share_name x->y OK_RECREATE -share_name y->absent OK_RECREATE -share_name y->x OK_RECREATE -storage_root absent->x OK_RECREATE -storage_root absent->y OK_RECREATE -storage_root x->absent OK_RECREATE -storage_root x->y OK_RECREATE -storage_root y->absent OK_RECREATE -storage_root y->x OK_RECREATE - -=== summary -OK 76 -OK_RECREATE 48 -NOT_OBSERVABLE 12 -DRIFT 4 - -=== not covered: 4 fields under a slice or map -grants[*].principal -grants[*].privileges[*] -options.* -properties.* +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours diff --git a/bundle/direct/tests/output/cluster_policies.full.txt b/bundle/direct/tests/output/cluster_policies.full.txt new file mode 100644 index 00000000000..eab334d21b9 --- /dev/null +++ b/bundle/direct/tests/output/cluster_policies.full.txt @@ -0,0 +1,64 @@ +=== cluster_policy.yml.tmpl +definition absent x OK +definition absent y OK +definition x absent OK +definition x y OK +definition y absent OK +definition y x OK +description absent x OK +description absent y OK +description x absent OK +description x y OK +description y absent OK +description y x OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +max_clusters_per_user 1 2 OK +max_clusters_per_user 1 absent OK +max_clusters_per_user 2 1 OK +max_clusters_per_user 2 absent OK +max_clusters_per_user absent 1 OK +max_clusters_per_user absent 2 OK +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +policy_family_definition_overrides absent x OK +policy_family_definition_overrides absent y OK +policy_family_definition_overrides x absent OK +policy_family_definition_overrides x y OK +policy_family_definition_overrides y absent OK +policy_family_definition_overrides y x OK +policy_family_id absent x OK +policy_family_id absent y OK +policy_family_id x absent OK +policy_family_id x y OK +policy_family_id y absent OK +policy_family_id y x OK + +=== summary +OK 36 +NOT_OBSERVABLE 6 + +=== not covered: 15 fields under a slice or map +libraries[*].cran.package +libraries[*].cran.repo +libraries[*].egg +libraries[*].jar +libraries[*].maven.coordinates +libraries[*].maven.exclusions[*] +libraries[*].maven.repo +libraries[*].pypi.package +libraries[*].pypi.repo +libraries[*].requirements +libraries[*].whl +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/tests/output/cluster_policies.txt b/bundle/direct/tests/output/cluster_policies.txt index 758917cead1..e69de29bb2d 100644 --- a/bundle/direct/tests/output/cluster_policies.txt +++ b/bundle/direct/tests/output/cluster_policies.txt @@ -1,64 +0,0 @@ -=== cluster_policy.yml.tmpl -definition absent->x OK -definition absent->y OK -definition x->absent OK -definition x->y OK -definition y->absent OK -definition y->x OK -description absent->x OK -description absent->y OK -description x->absent OK -description x->y OK -description y->absent OK -description y->x OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -max_clusters_per_user 1->2 OK -max_clusters_per_user 1->absent OK -max_clusters_per_user 2->1 OK -max_clusters_per_user 2->absent OK -max_clusters_per_user absent->1 OK -max_clusters_per_user absent->2 OK -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -policy_family_definition_overrides absent->x OK -policy_family_definition_overrides absent->y OK -policy_family_definition_overrides x->absent OK -policy_family_definition_overrides x->y OK -policy_family_definition_overrides y->absent OK -policy_family_definition_overrides y->x OK -policy_family_id absent->x OK -policy_family_id absent->y OK -policy_family_id x->absent OK -policy_family_id x->y OK -policy_family_id y->absent OK -policy_family_id y->x OK - -=== summary -OK 36 -NOT_OBSERVABLE 6 - -=== not covered: 15 fields under a slice or map -libraries[*].cran.package -libraries[*].cran.repo -libraries[*].egg -libraries[*].jar -libraries[*].maven.coordinates -libraries[*].maven.exclusions[*] -libraries[*].maven.repo -libraries[*].pypi.package -libraries[*].pypi.repo -libraries[*].requirements -libraries[*].whl -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name diff --git a/bundle/direct/tests/output/clusters.full.txt b/bundle/direct/tests/output/clusters.full.txt new file mode 100644 index 00000000000..b0e7a9e505a --- /dev/null +++ b/bundle/direct/tests/output/clusters.full.txt @@ -0,0 +1,777 @@ +=== cluster.yml.tmpl +apply_policy_default_values absent false SUPPRESSED empty +apply_policy_default_values absent true OK +apply_policy_default_values false absent SUPPRESSED remote_already_set +apply_policy_default_values false true OK +apply_policy_default_values true absent OK +apply_policy_default_values true false OK +autoscale.max_workers 1 2 OK +autoscale.max_workers 1 absent OK +autoscale.max_workers 2 1 OK +autoscale.max_workers 2 absent OK +autoscale.max_workers absent 1 OK +autoscale.max_workers absent 2 OK +autoscale.min_workers 1 2 OK +autoscale.min_workers 1 absent OK +autoscale.min_workers 2 1 OK +autoscale.min_workers 2 absent OK +autoscale.min_workers absent 1 OK +autoscale.min_workers absent 2 OK +autotermination_minutes 1 2 OK +autotermination_minutes 1 absent OK +autotermination_minutes 2 1 OK +autotermination_minutes 2 absent OK +autotermination_minutes absent 1 OK +autotermination_minutes absent 2 OK +aws_attributes.availability absent x OK +aws_attributes.availability absent y OK +aws_attributes.availability x absent OK +aws_attributes.availability x y OK +aws_attributes.availability y absent OK +aws_attributes.availability y x OK +aws_attributes.ebs_volume_count 1 2 OK +aws_attributes.ebs_volume_count 1 absent OK +aws_attributes.ebs_volume_count 2 1 OK +aws_attributes.ebs_volume_count 2 absent OK +aws_attributes.ebs_volume_count absent 1 OK +aws_attributes.ebs_volume_count absent 2 OK +aws_attributes.ebs_volume_iops 1 2 OK +aws_attributes.ebs_volume_iops 1 absent OK +aws_attributes.ebs_volume_iops 2 1 OK +aws_attributes.ebs_volume_iops 2 absent OK +aws_attributes.ebs_volume_iops absent 1 OK +aws_attributes.ebs_volume_iops absent 2 OK +aws_attributes.ebs_volume_size 1 2 OK +aws_attributes.ebs_volume_size 1 absent OK +aws_attributes.ebs_volume_size 2 1 OK +aws_attributes.ebs_volume_size 2 absent OK +aws_attributes.ebs_volume_size absent 1 OK +aws_attributes.ebs_volume_size absent 2 OK +aws_attributes.ebs_volume_throughput 1 2 OK +aws_attributes.ebs_volume_throughput 1 absent OK +aws_attributes.ebs_volume_throughput 2 1 OK +aws_attributes.ebs_volume_throughput 2 absent OK +aws_attributes.ebs_volume_throughput absent 1 OK +aws_attributes.ebs_volume_throughput absent 2 OK +aws_attributes.ebs_volume_type absent x OK +aws_attributes.ebs_volume_type absent y OK +aws_attributes.ebs_volume_type x absent OK +aws_attributes.ebs_volume_type x y OK +aws_attributes.ebs_volume_type y absent OK +aws_attributes.ebs_volume_type y x OK +aws_attributes.first_on_demand 1 2 OK +aws_attributes.first_on_demand 1 absent OK +aws_attributes.first_on_demand 2 1 OK +aws_attributes.first_on_demand 2 absent OK +aws_attributes.first_on_demand absent 1 OK +aws_attributes.first_on_demand absent 2 OK +aws_attributes.instance_profile_arn absent x OK +aws_attributes.instance_profile_arn absent y OK +aws_attributes.instance_profile_arn x absent OK +aws_attributes.instance_profile_arn x y OK +aws_attributes.instance_profile_arn y absent OK +aws_attributes.instance_profile_arn y x OK +aws_attributes.spot_bid_price_percent 1 2 OK +aws_attributes.spot_bid_price_percent 1 absent OK +aws_attributes.spot_bid_price_percent 2 1 OK +aws_attributes.spot_bid_price_percent 2 absent OK +aws_attributes.spot_bid_price_percent absent 1 OK +aws_attributes.spot_bid_price_percent absent 2 OK +aws_attributes.zone_id absent x OK +aws_attributes.zone_id absent y OK +aws_attributes.zone_id x absent OK +aws_attributes.zone_id x y OK +aws_attributes.zone_id y absent OK +aws_attributes.zone_id y x OK +azure_attributes.availability absent x OK +azure_attributes.availability absent y OK +azure_attributes.availability x absent OK +azure_attributes.availability x y OK +azure_attributes.availability y absent OK +azure_attributes.availability y x OK +azure_attributes.capacity_reservation_group absent x OK +azure_attributes.capacity_reservation_group absent y OK +azure_attributes.capacity_reservation_group x absent OK +azure_attributes.capacity_reservation_group x y OK +azure_attributes.capacity_reservation_group y absent OK +azure_attributes.capacity_reservation_group y x OK +azure_attributes.first_on_demand 1 2 OK +azure_attributes.first_on_demand 1 absent OK +azure_attributes.first_on_demand 2 1 OK +azure_attributes.first_on_demand 2 absent OK +azure_attributes.first_on_demand absent 1 OK +azure_attributes.first_on_demand absent 2 OK +azure_attributes.log_analytics_info.log_analytics_primary_key absent x OK +azure_attributes.log_analytics_info.log_analytics_primary_key absent y OK +azure_attributes.log_analytics_info.log_analytics_primary_key x absent OK +azure_attributes.log_analytics_info.log_analytics_primary_key x y OK +azure_attributes.log_analytics_info.log_analytics_primary_key y absent OK +azure_attributes.log_analytics_info.log_analytics_primary_key y x OK +azure_attributes.log_analytics_info.log_analytics_workspace_id absent x OK +azure_attributes.log_analytics_info.log_analytics_workspace_id absent y OK +azure_attributes.log_analytics_info.log_analytics_workspace_id x absent OK +azure_attributes.log_analytics_info.log_analytics_workspace_id x y OK +azure_attributes.log_analytics_info.log_analytics_workspace_id y absent OK +azure_attributes.log_analytics_info.log_analytics_workspace_id y x OK +azure_attributes.spot_bid_max_price 1 2 OK +azure_attributes.spot_bid_max_price 1 absent OK +azure_attributes.spot_bid_max_price 2 1 OK +azure_attributes.spot_bid_max_price 2 absent OK +azure_attributes.spot_bid_max_price absent 1 OK +azure_attributes.spot_bid_max_price absent 2 OK +cluster_log_conf.dbfs.destination x y OK +cluster_log_conf.dbfs.destination y x OK +cluster_log_conf.s3.canned_acl absent x OK +cluster_log_conf.s3.canned_acl absent y OK +cluster_log_conf.s3.canned_acl x absent OK +cluster_log_conf.s3.canned_acl x y OK +cluster_log_conf.s3.canned_acl y absent OK +cluster_log_conf.s3.canned_acl y x OK +cluster_log_conf.s3.destination x y OK +cluster_log_conf.s3.destination y x OK +cluster_log_conf.s3.enable_encryption absent false SUPPRESSED empty +cluster_log_conf.s3.enable_encryption absent true OK +cluster_log_conf.s3.enable_encryption false absent SUPPRESSED empty +cluster_log_conf.s3.enable_encryption false true OK +cluster_log_conf.s3.enable_encryption true absent OK +cluster_log_conf.s3.enable_encryption true false OK +cluster_log_conf.s3.encryption_type absent x OK +cluster_log_conf.s3.encryption_type absent y OK +cluster_log_conf.s3.encryption_type x absent OK +cluster_log_conf.s3.encryption_type x y OK +cluster_log_conf.s3.encryption_type y absent OK +cluster_log_conf.s3.encryption_type y x OK +cluster_log_conf.s3.endpoint absent x OK +cluster_log_conf.s3.endpoint absent y OK +cluster_log_conf.s3.endpoint x absent OK +cluster_log_conf.s3.endpoint x y OK +cluster_log_conf.s3.endpoint y absent OK +cluster_log_conf.s3.endpoint y x OK +cluster_log_conf.s3.kms_key absent x OK +cluster_log_conf.s3.kms_key absent y OK +cluster_log_conf.s3.kms_key x absent OK +cluster_log_conf.s3.kms_key x y OK +cluster_log_conf.s3.kms_key y absent OK +cluster_log_conf.s3.kms_key y x OK +cluster_log_conf.s3.region absent x OK +cluster_log_conf.s3.region absent y OK +cluster_log_conf.s3.region x absent OK +cluster_log_conf.s3.region x y OK +cluster_log_conf.s3.region y absent OK +cluster_log_conf.s3.region y x OK +cluster_log_conf.volumes.destination x y OK +cluster_log_conf.volumes.destination y x OK +cluster_name absent x OK +cluster_name absent y OK +cluster_name x absent OK +cluster_name x y OK +cluster_name y absent OK +cluster_name y x OK +data_security_mode absent x OK +data_security_mode absent y OK +data_security_mode x absent OK +data_security_mode x y OK +data_security_mode y absent OK +data_security_mode y x OK +dependency_mode absent x OK +dependency_mode absent y OK +dependency_mode x absent OK +dependency_mode x y OK +dependency_mode y absent OK +dependency_mode y x OK +docker_image.basic_auth.password absent x OK +docker_image.basic_auth.password absent y OK +docker_image.basic_auth.password x absent OK +docker_image.basic_auth.password x y OK +docker_image.basic_auth.password y absent OK +docker_image.basic_auth.password y x OK +docker_image.basic_auth.username absent x OK +docker_image.basic_auth.username absent y OK +docker_image.basic_auth.username x absent OK +docker_image.basic_auth.username x y OK +docker_image.basic_auth.username y absent OK +docker_image.basic_auth.username y x OK +docker_image.url absent x OK +docker_image.url absent y OK +docker_image.url x absent OK +docker_image.url x y OK +docker_image.url y absent OK +docker_image.url y x OK +driver_instance_pool_id absent x OK +driver_instance_pool_id absent y OK +driver_instance_pool_id x absent OK +driver_instance_pool_id x y OK +driver_instance_pool_id y absent OK +driver_instance_pool_id y x OK +driver_node_type_id absent x OK +driver_node_type_id absent y OK +driver_node_type_id x absent OK +driver_node_type_id x y OK +driver_node_type_id y absent OK +driver_node_type_id y x OK +enable_elastic_disk absent false SUPPRESSED remote_already_set +enable_elastic_disk absent true OK +enable_elastic_disk false absent SUPPRESSED empty +enable_elastic_disk false true OK +enable_elastic_disk true absent OK +enable_elastic_disk true false OK +enable_local_disk_encryption absent false SUPPRESSED empty +enable_local_disk_encryption absent true OK +enable_local_disk_encryption false absent SUPPRESSED empty +enable_local_disk_encryption false true OK +enable_local_disk_encryption true absent OK +enable_local_disk_encryption true false OK +gcp_attributes.availability absent x OK +gcp_attributes.availability absent y OK +gcp_attributes.availability x absent OK +gcp_attributes.availability x y OK +gcp_attributes.availability y absent OK +gcp_attributes.availability y x OK +gcp_attributes.boot_disk_size 1 2 OK +gcp_attributes.boot_disk_size 1 absent OK +gcp_attributes.boot_disk_size 2 1 OK +gcp_attributes.boot_disk_size 2 absent OK +gcp_attributes.boot_disk_size absent 1 OK +gcp_attributes.boot_disk_size absent 2 OK +gcp_attributes.confidential_compute_type absent x OK +gcp_attributes.confidential_compute_type absent y OK +gcp_attributes.confidential_compute_type x absent OK +gcp_attributes.confidential_compute_type x y OK +gcp_attributes.confidential_compute_type y absent OK +gcp_attributes.confidential_compute_type y x OK +gcp_attributes.first_on_demand 1 2 OK +gcp_attributes.first_on_demand 1 absent OK +gcp_attributes.first_on_demand 2 1 OK +gcp_attributes.first_on_demand 2 absent OK +gcp_attributes.first_on_demand absent 1 OK +gcp_attributes.first_on_demand absent 2 OK +gcp_attributes.google_service_account absent x OK +gcp_attributes.google_service_account absent y OK +gcp_attributes.google_service_account x absent OK +gcp_attributes.google_service_account x y OK +gcp_attributes.google_service_account y absent OK +gcp_attributes.google_service_account y x OK +gcp_attributes.local_ssd_count 1 2 OK +gcp_attributes.local_ssd_count 1 absent OK +gcp_attributes.local_ssd_count 2 1 OK +gcp_attributes.local_ssd_count 2 absent OK +gcp_attributes.local_ssd_count absent 1 OK +gcp_attributes.local_ssd_count absent 2 OK +gcp_attributes.use_preemptible_executors absent false SUPPRESSED empty +gcp_attributes.use_preemptible_executors absent true OK +gcp_attributes.use_preemptible_executors false absent SUPPRESSED empty +gcp_attributes.use_preemptible_executors false true OK +gcp_attributes.use_preemptible_executors true absent OK +gcp_attributes.use_preemptible_executors true false OK +gcp_attributes.zone_id absent x OK +gcp_attributes.zone_id absent y OK +gcp_attributes.zone_id x absent OK +gcp_attributes.zone_id x y OK +gcp_attributes.zone_id y absent OK +gcp_attributes.zone_id y x OK +instance_pool_id absent x OK +instance_pool_id absent y OK +instance_pool_id x absent OK +instance_pool_id x y OK +instance_pool_id y absent OK +instance_pool_id y x OK +is_single_node absent false SUPPRESSED empty +is_single_node absent true OK +is_single_node false absent SUPPRESSED empty +is_single_node false true OK +is_single_node true absent OK +is_single_node true false OK +kind absent x OK +kind absent y OK +kind x absent OK +kind x y OK +kind y absent OK +kind y x OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle.started absent false OK +lifecycle.started absent true OK +lifecycle.started false absent OK +lifecycle.started false true OK +lifecycle.started true absent OK +lifecycle.started true false OK +node_type_id absent x OK +node_type_id absent y OK +node_type_id x absent OK +node_type_id x y OK +node_type_id y absent OK +node_type_id y x OK +num_workers 1 2 OK +num_workers 1 absent OK +num_workers 2 1 OK +num_workers 2 absent OK +num_workers absent 1 OK +num_workers absent 2 OK +policy_id absent x OK +policy_id absent y OK +policy_id x absent OK +policy_id x y OK +policy_id y absent OK +policy_id y x OK +remote_disk_throughput 1 2 OK +remote_disk_throughput 1 absent OK +remote_disk_throughput 2 1 OK +remote_disk_throughput 2 absent OK +remote_disk_throughput absent 1 OK +remote_disk_throughput absent 2 OK +runtime_engine absent x OK +runtime_engine absent y OK +runtime_engine x absent OK +runtime_engine x y OK +runtime_engine y absent OK +runtime_engine y x OK +single_user_name absent x OK +single_user_name absent y OK +single_user_name x absent OK +single_user_name x y OK +single_user_name y absent OK +single_user_name y x OK +spark_version absent x OK +spark_version absent y OK +spark_version x absent OK +spark_version x y OK +spark_version y absent OK +spark_version y x OK +total_initial_remote_disk_size 1 2 OK +total_initial_remote_disk_size 1 absent OK +total_initial_remote_disk_size 2 1 OK +total_initial_remote_disk_size 2 absent OK +total_initial_remote_disk_size absent 1 OK +total_initial_remote_disk_size absent 2 OK +use_ml_runtime absent false SUPPRESSED empty +use_ml_runtime absent true OK +use_ml_runtime false absent SUPPRESSED empty +use_ml_runtime false true OK +use_ml_runtime true absent OK +use_ml_runtime true false OK +workload_type.clients.jobs absent false OK +workload_type.clients.jobs absent true OK +workload_type.clients.jobs false absent SUPPRESSED empty +workload_type.clients.jobs false true OK +workload_type.clients.jobs true absent OK +workload_type.clients.jobs true false OK +workload_type.clients.notebooks absent false SUPPRESSED empty +workload_type.clients.notebooks absent true OK +workload_type.clients.notebooks false absent SUPPRESSED empty +workload_type.clients.notebooks false true OK +workload_type.clients.notebooks true absent OK +workload_type.clients.notebooks true false OK + +=== cluster_apply_policy_default_values.yml.tmpl +apply_policy_default_values absent false SUPPRESSED empty +apply_policy_default_values absent true OK +apply_policy_default_values false absent SUPPRESSED remote_already_set +apply_policy_default_values false true OK +apply_policy_default_values true absent OK +apply_policy_default_values true false OK +autoscale.max_workers 1 2 OK +autoscale.max_workers 1 absent OK +autoscale.max_workers 2 1 OK +autoscale.max_workers 2 absent OK +autoscale.max_workers absent 1 OK +autoscale.max_workers absent 2 OK +autoscale.min_workers 1 2 OK +autoscale.min_workers 1 absent OK +autoscale.min_workers 2 1 OK +autoscale.min_workers 2 absent OK +autoscale.min_workers absent 1 OK +autoscale.min_workers absent 2 OK +autotermination_minutes 1 2 OK +autotermination_minutes 1 absent OK +autotermination_minutes 2 1 OK +autotermination_minutes 2 absent OK +autotermination_minutes absent 1 OK +autotermination_minutes absent 2 OK +aws_attributes.availability absent x OK +aws_attributes.availability absent y OK +aws_attributes.availability x absent OK +aws_attributes.availability x y OK +aws_attributes.availability y absent OK +aws_attributes.availability y x OK +aws_attributes.ebs_volume_count 1 2 OK +aws_attributes.ebs_volume_count 1 absent OK +aws_attributes.ebs_volume_count 2 1 OK +aws_attributes.ebs_volume_count 2 absent OK +aws_attributes.ebs_volume_count absent 1 OK +aws_attributes.ebs_volume_count absent 2 OK +aws_attributes.ebs_volume_iops 1 2 OK +aws_attributes.ebs_volume_iops 1 absent OK +aws_attributes.ebs_volume_iops 2 1 OK +aws_attributes.ebs_volume_iops 2 absent OK +aws_attributes.ebs_volume_iops absent 1 OK +aws_attributes.ebs_volume_iops absent 2 OK +aws_attributes.ebs_volume_size 1 2 OK +aws_attributes.ebs_volume_size 1 absent OK +aws_attributes.ebs_volume_size 2 1 OK +aws_attributes.ebs_volume_size 2 absent OK +aws_attributes.ebs_volume_size absent 1 OK +aws_attributes.ebs_volume_size absent 2 OK +aws_attributes.ebs_volume_throughput 1 2 OK +aws_attributes.ebs_volume_throughput 1 absent OK +aws_attributes.ebs_volume_throughput 2 1 OK +aws_attributes.ebs_volume_throughput 2 absent OK +aws_attributes.ebs_volume_throughput absent 1 OK +aws_attributes.ebs_volume_throughput absent 2 OK +aws_attributes.ebs_volume_type absent x OK +aws_attributes.ebs_volume_type absent y OK +aws_attributes.ebs_volume_type x absent OK +aws_attributes.ebs_volume_type x y OK +aws_attributes.ebs_volume_type y absent OK +aws_attributes.ebs_volume_type y x OK +aws_attributes.first_on_demand 1 2 OK +aws_attributes.first_on_demand 1 absent OK +aws_attributes.first_on_demand 2 1 OK +aws_attributes.first_on_demand 2 absent OK +aws_attributes.first_on_demand absent 1 OK +aws_attributes.first_on_demand absent 2 OK +aws_attributes.instance_profile_arn absent x OK +aws_attributes.instance_profile_arn absent y OK +aws_attributes.instance_profile_arn x absent OK +aws_attributes.instance_profile_arn x y OK +aws_attributes.instance_profile_arn y absent OK +aws_attributes.instance_profile_arn y x OK +aws_attributes.spot_bid_price_percent 1 2 OK +aws_attributes.spot_bid_price_percent 1 absent OK +aws_attributes.spot_bid_price_percent 2 1 OK +aws_attributes.spot_bid_price_percent 2 absent OK +aws_attributes.spot_bid_price_percent absent 1 OK +aws_attributes.spot_bid_price_percent absent 2 OK +aws_attributes.zone_id absent x OK +aws_attributes.zone_id absent y OK +aws_attributes.zone_id x absent OK +aws_attributes.zone_id x y OK +aws_attributes.zone_id y absent OK +aws_attributes.zone_id y x OK +azure_attributes.availability absent x OK +azure_attributes.availability absent y OK +azure_attributes.availability x absent OK +azure_attributes.availability x y OK +azure_attributes.availability y absent OK +azure_attributes.availability y x OK +azure_attributes.capacity_reservation_group absent x OK +azure_attributes.capacity_reservation_group absent y OK +azure_attributes.capacity_reservation_group x absent OK +azure_attributes.capacity_reservation_group x y OK +azure_attributes.capacity_reservation_group y absent OK +azure_attributes.capacity_reservation_group y x OK +azure_attributes.first_on_demand 1 2 OK +azure_attributes.first_on_demand 1 absent OK +azure_attributes.first_on_demand 2 1 OK +azure_attributes.first_on_demand 2 absent OK +azure_attributes.first_on_demand absent 1 OK +azure_attributes.first_on_demand absent 2 OK +azure_attributes.log_analytics_info.log_analytics_primary_key absent x OK +azure_attributes.log_analytics_info.log_analytics_primary_key absent y OK +azure_attributes.log_analytics_info.log_analytics_primary_key x absent OK +azure_attributes.log_analytics_info.log_analytics_primary_key x y OK +azure_attributes.log_analytics_info.log_analytics_primary_key y absent OK +azure_attributes.log_analytics_info.log_analytics_primary_key y x OK +azure_attributes.log_analytics_info.log_analytics_workspace_id absent x OK +azure_attributes.log_analytics_info.log_analytics_workspace_id absent y OK +azure_attributes.log_analytics_info.log_analytics_workspace_id x absent OK +azure_attributes.log_analytics_info.log_analytics_workspace_id x y OK +azure_attributes.log_analytics_info.log_analytics_workspace_id y absent OK +azure_attributes.log_analytics_info.log_analytics_workspace_id y x OK +azure_attributes.spot_bid_max_price 1 2 OK +azure_attributes.spot_bid_max_price 1 absent OK +azure_attributes.spot_bid_max_price 2 1 OK +azure_attributes.spot_bid_max_price 2 absent OK +azure_attributes.spot_bid_max_price absent 1 OK +azure_attributes.spot_bid_max_price absent 2 OK +cluster_log_conf.dbfs.destination x y OK +cluster_log_conf.dbfs.destination y x OK +cluster_log_conf.s3.canned_acl absent x OK +cluster_log_conf.s3.canned_acl absent y OK +cluster_log_conf.s3.canned_acl x absent OK +cluster_log_conf.s3.canned_acl x y OK +cluster_log_conf.s3.canned_acl y absent OK +cluster_log_conf.s3.canned_acl y x OK +cluster_log_conf.s3.destination x y OK +cluster_log_conf.s3.destination y x OK +cluster_log_conf.s3.enable_encryption absent false SUPPRESSED empty +cluster_log_conf.s3.enable_encryption absent true OK +cluster_log_conf.s3.enable_encryption false absent SUPPRESSED empty +cluster_log_conf.s3.enable_encryption false true OK +cluster_log_conf.s3.enable_encryption true absent OK +cluster_log_conf.s3.enable_encryption true false OK +cluster_log_conf.s3.encryption_type absent x OK +cluster_log_conf.s3.encryption_type absent y OK +cluster_log_conf.s3.encryption_type x absent OK +cluster_log_conf.s3.encryption_type x y OK +cluster_log_conf.s3.encryption_type y absent OK +cluster_log_conf.s3.encryption_type y x OK +cluster_log_conf.s3.endpoint absent x OK +cluster_log_conf.s3.endpoint absent y OK +cluster_log_conf.s3.endpoint x absent OK +cluster_log_conf.s3.endpoint x y OK +cluster_log_conf.s3.endpoint y absent OK +cluster_log_conf.s3.endpoint y x OK +cluster_log_conf.s3.kms_key absent x OK +cluster_log_conf.s3.kms_key absent y OK +cluster_log_conf.s3.kms_key x absent OK +cluster_log_conf.s3.kms_key x y OK +cluster_log_conf.s3.kms_key y absent OK +cluster_log_conf.s3.kms_key y x OK +cluster_log_conf.s3.region absent x OK +cluster_log_conf.s3.region absent y OK +cluster_log_conf.s3.region x absent OK +cluster_log_conf.s3.region x y OK +cluster_log_conf.s3.region y absent OK +cluster_log_conf.s3.region y x OK +cluster_log_conf.volumes.destination x y OK +cluster_log_conf.volumes.destination y x OK +cluster_name absent x OK +cluster_name absent y OK +cluster_name x absent OK +cluster_name x y OK +cluster_name y absent OK +cluster_name y x OK +data_security_mode absent x OK +data_security_mode absent y OK +data_security_mode x absent OK +data_security_mode x y OK +data_security_mode y absent OK +data_security_mode y x OK +dependency_mode absent x OK +dependency_mode absent y OK +dependency_mode x absent OK +dependency_mode x y OK +dependency_mode y absent OK +dependency_mode y x OK +docker_image.basic_auth.password absent x OK +docker_image.basic_auth.password absent y OK +docker_image.basic_auth.password x absent OK +docker_image.basic_auth.password x y OK +docker_image.basic_auth.password y absent OK +docker_image.basic_auth.password y x OK +docker_image.basic_auth.username absent x OK +docker_image.basic_auth.username absent y OK +docker_image.basic_auth.username x absent OK +docker_image.basic_auth.username x y OK +docker_image.basic_auth.username y absent OK +docker_image.basic_auth.username y x OK +docker_image.url absent x OK +docker_image.url absent y OK +docker_image.url x absent OK +docker_image.url x y OK +docker_image.url y absent OK +docker_image.url y x OK +driver_instance_pool_id absent x OK +driver_instance_pool_id absent y OK +driver_instance_pool_id x absent OK +driver_instance_pool_id x y OK +driver_instance_pool_id y absent OK +driver_instance_pool_id y x OK +driver_node_type_id absent x OK +driver_node_type_id absent y OK +driver_node_type_id x absent OK +driver_node_type_id x y OK +driver_node_type_id y absent OK +driver_node_type_id y x OK +enable_elastic_disk absent false SUPPRESSED remote_already_set +enable_elastic_disk absent true OK +enable_elastic_disk false absent SUPPRESSED empty +enable_elastic_disk false true OK +enable_elastic_disk true absent OK +enable_elastic_disk true false OK +enable_local_disk_encryption absent false SUPPRESSED empty +enable_local_disk_encryption absent true OK +enable_local_disk_encryption false absent SUPPRESSED empty +enable_local_disk_encryption false true OK +enable_local_disk_encryption true absent OK +enable_local_disk_encryption true false OK +gcp_attributes.availability absent x OK +gcp_attributes.availability absent y OK +gcp_attributes.availability x absent OK +gcp_attributes.availability x y OK +gcp_attributes.availability y absent OK +gcp_attributes.availability y x OK +gcp_attributes.boot_disk_size 1 2 OK +gcp_attributes.boot_disk_size 1 absent OK +gcp_attributes.boot_disk_size 2 1 OK +gcp_attributes.boot_disk_size 2 absent OK +gcp_attributes.boot_disk_size absent 1 OK +gcp_attributes.boot_disk_size absent 2 OK +gcp_attributes.confidential_compute_type absent x OK +gcp_attributes.confidential_compute_type absent y OK +gcp_attributes.confidential_compute_type x absent OK +gcp_attributes.confidential_compute_type x y OK +gcp_attributes.confidential_compute_type y absent OK +gcp_attributes.confidential_compute_type y x OK +gcp_attributes.first_on_demand 1 2 OK +gcp_attributes.first_on_demand 1 absent OK +gcp_attributes.first_on_demand 2 1 OK +gcp_attributes.first_on_demand 2 absent OK +gcp_attributes.first_on_demand absent 1 OK +gcp_attributes.first_on_demand absent 2 OK +gcp_attributes.google_service_account absent x OK +gcp_attributes.google_service_account absent y OK +gcp_attributes.google_service_account x absent OK +gcp_attributes.google_service_account x y OK +gcp_attributes.google_service_account y absent OK +gcp_attributes.google_service_account y x OK +gcp_attributes.local_ssd_count 1 2 OK +gcp_attributes.local_ssd_count 1 absent OK +gcp_attributes.local_ssd_count 2 1 OK +gcp_attributes.local_ssd_count 2 absent OK +gcp_attributes.local_ssd_count absent 1 OK +gcp_attributes.local_ssd_count absent 2 OK +gcp_attributes.use_preemptible_executors absent false SUPPRESSED empty +gcp_attributes.use_preemptible_executors absent true OK +gcp_attributes.use_preemptible_executors false absent SUPPRESSED empty +gcp_attributes.use_preemptible_executors false true OK +gcp_attributes.use_preemptible_executors true absent OK +gcp_attributes.use_preemptible_executors true false OK +gcp_attributes.zone_id absent x OK +gcp_attributes.zone_id absent y OK +gcp_attributes.zone_id x absent OK +gcp_attributes.zone_id x y OK +gcp_attributes.zone_id y absent OK +gcp_attributes.zone_id y x OK +instance_pool_id absent x OK +instance_pool_id absent y OK +instance_pool_id x absent OK +instance_pool_id x y OK +instance_pool_id y absent OK +instance_pool_id y x OK +is_single_node absent false SUPPRESSED empty +is_single_node absent true OK +is_single_node false absent SUPPRESSED empty +is_single_node false true OK +is_single_node true absent OK +is_single_node true false OK +kind absent x OK +kind absent y OK +kind x absent OK +kind x y OK +kind y absent OK +kind y x OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle.started absent false OK +lifecycle.started absent true OK +lifecycle.started false absent OK +lifecycle.started false true OK +lifecycle.started true absent OK +lifecycle.started true false OK +node_type_id absent x OK +node_type_id absent y OK +node_type_id x absent OK +node_type_id x y OK +node_type_id y absent OK +node_type_id y x OK +num_workers 1 2 OK +num_workers 1 absent OK +num_workers 2 1 OK +num_workers 2 absent OK +num_workers absent 1 OK +num_workers absent 2 OK +policy_id absent x OK +policy_id absent y OK +policy_id x absent OK +policy_id x y OK +policy_id y absent OK +policy_id y x OK +remote_disk_throughput 1 2 OK +remote_disk_throughput 1 absent OK +remote_disk_throughput 2 1 OK +remote_disk_throughput 2 absent OK +remote_disk_throughput absent 1 OK +remote_disk_throughput absent 2 OK +runtime_engine absent x OK +runtime_engine absent y OK +runtime_engine x absent OK +runtime_engine x y OK +runtime_engine y absent OK +runtime_engine y x OK +single_user_name absent x OK +single_user_name absent y OK +single_user_name x absent OK +single_user_name x y OK +single_user_name y absent OK +single_user_name y x OK +spark_version absent x OK +spark_version absent y OK +spark_version x absent OK +spark_version x y OK +spark_version y absent OK +spark_version y x OK +total_initial_remote_disk_size 1 2 OK +total_initial_remote_disk_size 1 absent OK +total_initial_remote_disk_size 2 1 OK +total_initial_remote_disk_size 2 absent OK +total_initial_remote_disk_size absent 1 OK +total_initial_remote_disk_size absent 2 OK +use_ml_runtime absent false SUPPRESSED empty +use_ml_runtime absent true OK +use_ml_runtime false absent SUPPRESSED empty +use_ml_runtime false true OK +use_ml_runtime true absent OK +use_ml_runtime true false OK +workload_type.clients.jobs absent false OK +workload_type.clients.jobs absent true OK +workload_type.clients.jobs false absent SUPPRESSED empty +workload_type.clients.jobs false true OK +workload_type.clients.jobs true absent OK +workload_type.clients.jobs true false OK +workload_type.clients.notebooks absent false SUPPRESSED empty +workload_type.clients.notebooks absent true OK +workload_type.clients.notebooks false absent SUPPRESSED empty +workload_type.clients.notebooks false true OK +workload_type.clients.notebooks true absent OK +workload_type.clients.notebooks true false OK + +=== summary +OK 686 +SUPPRESSED 34 +NOT_OBSERVABLE 24 + +=== not covered: 23 fields under a slice or map +custom_tags.* +driver_node_type_flexibility.alternate_node_type_ids[*] +init_scripts[*].abfss.destination +init_scripts[*].dbfs.destination +init_scripts[*].file.destination +init_scripts[*].gcs.destination +init_scripts[*].s3.canned_acl +init_scripts[*].s3.destination +init_scripts[*].s3.enable_encryption +init_scripts[*].s3.encryption_type +init_scripts[*].s3.endpoint +init_scripts[*].s3.kms_key +init_scripts[*].s3.region +init_scripts[*].volumes.destination +init_scripts[*].workspace.destination +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +spark_conf.* +spark_env_vars.* +ssh_public_keys[*] +worker_node_type_flexibility.alternate_node_type_ids[*] diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/tests/output/clusters.txt index 2e123e1c86e..6277d40cf4c 100644 --- a/bundle/direct/tests/output/clusters.txt +++ b/bundle/direct/tests/output/clusters.txt @@ -1,777 +1,7 @@ === cluster.yml.tmpl -apply_policy_default_values absent->false SUPPRESSED empty -apply_policy_default_values absent->true OK -apply_policy_default_values false->absent SUPPRESSED remote_already_set -apply_policy_default_values false->true OK -apply_policy_default_values true->absent OK -apply_policy_default_values true->false OK -autoscale.max_workers 1->2 OK -autoscale.max_workers 1->absent OK -autoscale.max_workers 2->1 OK -autoscale.max_workers 2->absent OK -autoscale.max_workers absent->1 OK -autoscale.max_workers absent->2 OK -autoscale.min_workers 1->2 OK -autoscale.min_workers 1->absent OK -autoscale.min_workers 2->1 OK -autoscale.min_workers 2->absent OK -autoscale.min_workers absent->1 OK -autoscale.min_workers absent->2 OK -autotermination_minutes 1->2 OK -autotermination_minutes 1->absent OK -autotermination_minutes 2->1 OK -autotermination_minutes 2->absent OK -autotermination_minutes absent->1 OK -autotermination_minutes absent->2 OK -aws_attributes.availability absent->x OK -aws_attributes.availability absent->y OK -aws_attributes.availability x->absent OK -aws_attributes.availability x->y OK -aws_attributes.availability y->absent OK -aws_attributes.availability y->x OK -aws_attributes.ebs_volume_count 1->2 OK -aws_attributes.ebs_volume_count 1->absent OK -aws_attributes.ebs_volume_count 2->1 OK -aws_attributes.ebs_volume_count 2->absent OK -aws_attributes.ebs_volume_count absent->1 OK -aws_attributes.ebs_volume_count absent->2 OK -aws_attributes.ebs_volume_iops 1->2 OK -aws_attributes.ebs_volume_iops 1->absent OK -aws_attributes.ebs_volume_iops 2->1 OK -aws_attributes.ebs_volume_iops 2->absent OK -aws_attributes.ebs_volume_iops absent->1 OK -aws_attributes.ebs_volume_iops absent->2 OK -aws_attributes.ebs_volume_size 1->2 OK -aws_attributes.ebs_volume_size 1->absent OK -aws_attributes.ebs_volume_size 2->1 OK -aws_attributes.ebs_volume_size 2->absent OK -aws_attributes.ebs_volume_size absent->1 OK -aws_attributes.ebs_volume_size absent->2 OK -aws_attributes.ebs_volume_throughput 1->2 OK -aws_attributes.ebs_volume_throughput 1->absent OK -aws_attributes.ebs_volume_throughput 2->1 OK -aws_attributes.ebs_volume_throughput 2->absent OK -aws_attributes.ebs_volume_throughput absent->1 OK -aws_attributes.ebs_volume_throughput absent->2 OK -aws_attributes.ebs_volume_type absent->x OK -aws_attributes.ebs_volume_type absent->y OK -aws_attributes.ebs_volume_type x->absent OK -aws_attributes.ebs_volume_type x->y OK -aws_attributes.ebs_volume_type y->absent OK -aws_attributes.ebs_volume_type y->x OK -aws_attributes.first_on_demand 1->2 OK -aws_attributes.first_on_demand 1->absent OK -aws_attributes.first_on_demand 2->1 OK -aws_attributes.first_on_demand 2->absent OK -aws_attributes.first_on_demand absent->1 OK -aws_attributes.first_on_demand absent->2 OK -aws_attributes.instance_profile_arn absent->x OK -aws_attributes.instance_profile_arn absent->y OK -aws_attributes.instance_profile_arn x->absent OK -aws_attributes.instance_profile_arn x->y OK -aws_attributes.instance_profile_arn y->absent OK -aws_attributes.instance_profile_arn y->x OK -aws_attributes.spot_bid_price_percent 1->2 OK -aws_attributes.spot_bid_price_percent 1->absent OK -aws_attributes.spot_bid_price_percent 2->1 OK -aws_attributes.spot_bid_price_percent 2->absent OK -aws_attributes.spot_bid_price_percent absent->1 OK -aws_attributes.spot_bid_price_percent absent->2 OK -aws_attributes.zone_id absent->x OK -aws_attributes.zone_id absent->y OK -aws_attributes.zone_id x->absent OK -aws_attributes.zone_id x->y OK -aws_attributes.zone_id y->absent OK -aws_attributes.zone_id y->x OK -azure_attributes.availability absent->x OK -azure_attributes.availability absent->y OK -azure_attributes.availability x->absent OK -azure_attributes.availability x->y OK -azure_attributes.availability y->absent OK -azure_attributes.availability y->x OK -azure_attributes.capacity_reservation_group absent->x OK -azure_attributes.capacity_reservation_group absent->y OK -azure_attributes.capacity_reservation_group x->absent OK -azure_attributes.capacity_reservation_group x->y OK -azure_attributes.capacity_reservation_group y->absent OK -azure_attributes.capacity_reservation_group y->x OK -azure_attributes.first_on_demand 1->2 OK -azure_attributes.first_on_demand 1->absent OK -azure_attributes.first_on_demand 2->1 OK -azure_attributes.first_on_demand 2->absent OK -azure_attributes.first_on_demand absent->1 OK -azure_attributes.first_on_demand absent->2 OK -azure_attributes.log_analytics_info.log_analytics_primary_key absent->x OK -azure_attributes.log_analytics_info.log_analytics_primary_key absent->y OK -azure_attributes.log_analytics_info.log_analytics_primary_key x->absent OK -azure_attributes.log_analytics_info.log_analytics_primary_key x->y OK -azure_attributes.log_analytics_info.log_analytics_primary_key y->absent OK -azure_attributes.log_analytics_info.log_analytics_primary_key y->x OK -azure_attributes.log_analytics_info.log_analytics_workspace_id absent->x OK -azure_attributes.log_analytics_info.log_analytics_workspace_id absent->y OK -azure_attributes.log_analytics_info.log_analytics_workspace_id x->absent OK -azure_attributes.log_analytics_info.log_analytics_workspace_id x->y OK -azure_attributes.log_analytics_info.log_analytics_workspace_id y->absent OK -azure_attributes.log_analytics_info.log_analytics_workspace_id y->x OK -azure_attributes.spot_bid_max_price 1->2 OK -azure_attributes.spot_bid_max_price 1->absent OK -azure_attributes.spot_bid_max_price 2->1 OK -azure_attributes.spot_bid_max_price 2->absent OK -azure_attributes.spot_bid_max_price absent->1 OK -azure_attributes.spot_bid_max_price absent->2 OK -cluster_log_conf.dbfs.destination x->y OK -cluster_log_conf.dbfs.destination y->x OK -cluster_log_conf.s3.canned_acl absent->x OK -cluster_log_conf.s3.canned_acl absent->y OK -cluster_log_conf.s3.canned_acl x->absent OK -cluster_log_conf.s3.canned_acl x->y OK -cluster_log_conf.s3.canned_acl y->absent OK -cluster_log_conf.s3.canned_acl y->x OK -cluster_log_conf.s3.destination x->y OK -cluster_log_conf.s3.destination y->x OK -cluster_log_conf.s3.enable_encryption absent->false SUPPRESSED empty -cluster_log_conf.s3.enable_encryption absent->true OK -cluster_log_conf.s3.enable_encryption false->absent SUPPRESSED empty -cluster_log_conf.s3.enable_encryption false->true OK -cluster_log_conf.s3.enable_encryption true->absent OK -cluster_log_conf.s3.enable_encryption true->false OK -cluster_log_conf.s3.encryption_type absent->x OK -cluster_log_conf.s3.encryption_type absent->y OK -cluster_log_conf.s3.encryption_type x->absent OK -cluster_log_conf.s3.encryption_type x->y OK -cluster_log_conf.s3.encryption_type y->absent OK -cluster_log_conf.s3.encryption_type y->x OK -cluster_log_conf.s3.endpoint absent->x OK -cluster_log_conf.s3.endpoint absent->y OK -cluster_log_conf.s3.endpoint x->absent OK -cluster_log_conf.s3.endpoint x->y OK -cluster_log_conf.s3.endpoint y->absent OK -cluster_log_conf.s3.endpoint y->x OK -cluster_log_conf.s3.kms_key absent->x OK -cluster_log_conf.s3.kms_key absent->y OK -cluster_log_conf.s3.kms_key x->absent OK -cluster_log_conf.s3.kms_key x->y OK -cluster_log_conf.s3.kms_key y->absent OK -cluster_log_conf.s3.kms_key y->x OK -cluster_log_conf.s3.region absent->x OK -cluster_log_conf.s3.region absent->y OK -cluster_log_conf.s3.region x->absent OK -cluster_log_conf.s3.region x->y OK -cluster_log_conf.s3.region y->absent OK -cluster_log_conf.s3.region y->x OK -cluster_log_conf.volumes.destination x->y OK -cluster_log_conf.volumes.destination y->x OK -cluster_name absent->x OK -cluster_name absent->y OK -cluster_name x->absent OK -cluster_name x->y OK -cluster_name y->absent OK -cluster_name y->x OK -data_security_mode absent->x OK -data_security_mode absent->y OK -data_security_mode x->absent OK -data_security_mode x->y OK -data_security_mode y->absent OK -data_security_mode y->x OK -dependency_mode absent->x OK -dependency_mode absent->y OK -dependency_mode x->absent OK -dependency_mode x->y OK -dependency_mode y->absent OK -dependency_mode y->x OK -docker_image.basic_auth.password absent->x OK -docker_image.basic_auth.password absent->y OK -docker_image.basic_auth.password x->absent OK -docker_image.basic_auth.password x->y OK -docker_image.basic_auth.password y->absent OK -docker_image.basic_auth.password y->x OK -docker_image.basic_auth.username absent->x OK -docker_image.basic_auth.username absent->y OK -docker_image.basic_auth.username x->absent OK -docker_image.basic_auth.username x->y OK -docker_image.basic_auth.username y->absent OK -docker_image.basic_auth.username y->x OK -docker_image.url absent->x OK -docker_image.url absent->y OK -docker_image.url x->absent OK -docker_image.url x->y OK -docker_image.url y->absent OK -docker_image.url y->x OK -driver_instance_pool_id absent->x OK -driver_instance_pool_id absent->y OK -driver_instance_pool_id x->absent OK -driver_instance_pool_id x->y OK -driver_instance_pool_id y->absent OK -driver_instance_pool_id y->x OK -driver_node_type_id absent->x OK -driver_node_type_id absent->y OK -driver_node_type_id x->absent OK -driver_node_type_id x->y OK -driver_node_type_id y->absent OK -driver_node_type_id y->x OK -enable_elastic_disk absent->false SUPPRESSED remote_already_set -enable_elastic_disk absent->true OK -enable_elastic_disk false->absent SUPPRESSED empty -enable_elastic_disk false->true OK -enable_elastic_disk true->absent OK -enable_elastic_disk true->false OK -enable_local_disk_encryption absent->false SUPPRESSED empty -enable_local_disk_encryption absent->true OK -enable_local_disk_encryption false->absent SUPPRESSED empty -enable_local_disk_encryption false->true OK -enable_local_disk_encryption true->absent OK -enable_local_disk_encryption true->false OK -gcp_attributes.availability absent->x OK -gcp_attributes.availability absent->y OK -gcp_attributes.availability x->absent OK -gcp_attributes.availability x->y OK -gcp_attributes.availability y->absent OK -gcp_attributes.availability y->x OK -gcp_attributes.boot_disk_size 1->2 OK -gcp_attributes.boot_disk_size 1->absent OK -gcp_attributes.boot_disk_size 2->1 OK -gcp_attributes.boot_disk_size 2->absent OK -gcp_attributes.boot_disk_size absent->1 OK -gcp_attributes.boot_disk_size absent->2 OK -gcp_attributes.confidential_compute_type absent->x OK -gcp_attributes.confidential_compute_type absent->y OK -gcp_attributes.confidential_compute_type x->absent OK -gcp_attributes.confidential_compute_type x->y OK -gcp_attributes.confidential_compute_type y->absent OK -gcp_attributes.confidential_compute_type y->x OK -gcp_attributes.first_on_demand 1->2 OK -gcp_attributes.first_on_demand 1->absent OK -gcp_attributes.first_on_demand 2->1 OK -gcp_attributes.first_on_demand 2->absent OK -gcp_attributes.first_on_demand absent->1 OK -gcp_attributes.first_on_demand absent->2 OK -gcp_attributes.google_service_account absent->x OK -gcp_attributes.google_service_account absent->y OK -gcp_attributes.google_service_account x->absent OK -gcp_attributes.google_service_account x->y OK -gcp_attributes.google_service_account y->absent OK -gcp_attributes.google_service_account y->x OK -gcp_attributes.local_ssd_count 1->2 OK -gcp_attributes.local_ssd_count 1->absent OK -gcp_attributes.local_ssd_count 2->1 OK -gcp_attributes.local_ssd_count 2->absent OK -gcp_attributes.local_ssd_count absent->1 OK -gcp_attributes.local_ssd_count absent->2 OK -gcp_attributes.use_preemptible_executors absent->false SUPPRESSED empty -gcp_attributes.use_preemptible_executors absent->true OK -gcp_attributes.use_preemptible_executors false->absent SUPPRESSED empty -gcp_attributes.use_preemptible_executors false->true OK -gcp_attributes.use_preemptible_executors true->absent OK -gcp_attributes.use_preemptible_executors true->false OK -gcp_attributes.zone_id absent->x OK -gcp_attributes.zone_id absent->y OK -gcp_attributes.zone_id x->absent OK -gcp_attributes.zone_id x->y OK -gcp_attributes.zone_id y->absent OK -gcp_attributes.zone_id y->x OK -instance_pool_id absent->x OK -instance_pool_id absent->y OK -instance_pool_id x->absent OK -instance_pool_id x->y OK -instance_pool_id y->absent OK -instance_pool_id y->x OK -is_single_node absent->false SUPPRESSED empty -is_single_node absent->true OK -is_single_node false->absent SUPPRESSED empty -is_single_node false->true OK -is_single_node true->absent OK -is_single_node true->false OK -kind absent->x OK -kind absent->y OK -kind x->absent OK -kind x->y OK -kind y->absent OK -kind y->x OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle.started absent->false OK -lifecycle.started absent->true OK -lifecycle.started false->absent OK -lifecycle.started false->true OK -lifecycle.started true->absent OK -lifecycle.started true->false OK -node_type_id absent->x OK -node_type_id absent->y OK -node_type_id x->absent OK -node_type_id x->y OK -node_type_id y->absent OK -node_type_id y->x OK -num_workers 1->2 OK -num_workers 1->absent OK -num_workers 2->1 OK -num_workers 2->absent OK -num_workers absent->1 OK -num_workers absent->2 OK -policy_id absent->x OK -policy_id absent->y OK -policy_id x->absent OK -policy_id x->y OK -policy_id y->absent OK -policy_id y->x OK -remote_disk_throughput 1->2 OK -remote_disk_throughput 1->absent OK -remote_disk_throughput 2->1 OK -remote_disk_throughput 2->absent OK -remote_disk_throughput absent->1 OK -remote_disk_throughput absent->2 OK -runtime_engine absent->x OK -runtime_engine absent->y OK -runtime_engine x->absent OK -runtime_engine x->y OK -runtime_engine y->absent OK -runtime_engine y->x OK -single_user_name absent->x OK -single_user_name absent->y OK -single_user_name x->absent OK -single_user_name x->y OK -single_user_name y->absent OK -single_user_name y->x OK -spark_version absent->x OK -spark_version absent->y OK -spark_version x->absent OK -spark_version x->y OK -spark_version y->absent OK -spark_version y->x OK -total_initial_remote_disk_size 1->2 OK -total_initial_remote_disk_size 1->absent OK -total_initial_remote_disk_size 2->1 OK -total_initial_remote_disk_size 2->absent OK -total_initial_remote_disk_size absent->1 OK -total_initial_remote_disk_size absent->2 OK -use_ml_runtime absent->false SUPPRESSED empty -use_ml_runtime absent->true OK -use_ml_runtime false->absent SUPPRESSED empty -use_ml_runtime false->true OK -use_ml_runtime true->absent OK -use_ml_runtime true->false OK -workload_type.clients.jobs absent->false OK -workload_type.clients.jobs absent->true OK -workload_type.clients.jobs false->absent SUPPRESSED empty -workload_type.clients.jobs false->true OK -workload_type.clients.jobs true->absent OK -workload_type.clients.jobs true->false OK -workload_type.clients.notebooks absent->false SUPPRESSED empty -workload_type.clients.notebooks absent->true OK -workload_type.clients.notebooks false->absent SUPPRESSED empty -workload_type.clients.notebooks false->true OK -workload_type.clients.notebooks true->absent OK -workload_type.clients.notebooks true->false OK +apply_policy_default_values false absent SUPPRESSED remote_already_set +enable_elastic_disk absent false SUPPRESSED remote_already_set === cluster_apply_policy_default_values.yml.tmpl -apply_policy_default_values absent->false SUPPRESSED empty -apply_policy_default_values absent->true OK -apply_policy_default_values false->absent SUPPRESSED remote_already_set -apply_policy_default_values false->true OK -apply_policy_default_values true->absent OK -apply_policy_default_values true->false OK -autoscale.max_workers 1->2 OK -autoscale.max_workers 1->absent OK -autoscale.max_workers 2->1 OK -autoscale.max_workers 2->absent OK -autoscale.max_workers absent->1 OK -autoscale.max_workers absent->2 OK -autoscale.min_workers 1->2 OK -autoscale.min_workers 1->absent OK -autoscale.min_workers 2->1 OK -autoscale.min_workers 2->absent OK -autoscale.min_workers absent->1 OK -autoscale.min_workers absent->2 OK -autotermination_minutes 1->2 OK -autotermination_minutes 1->absent OK -autotermination_minutes 2->1 OK -autotermination_minutes 2->absent OK -autotermination_minutes absent->1 OK -autotermination_minutes absent->2 OK -aws_attributes.availability absent->x OK -aws_attributes.availability absent->y OK -aws_attributes.availability x->absent OK -aws_attributes.availability x->y OK -aws_attributes.availability y->absent OK -aws_attributes.availability y->x OK -aws_attributes.ebs_volume_count 1->2 OK -aws_attributes.ebs_volume_count 1->absent OK -aws_attributes.ebs_volume_count 2->1 OK -aws_attributes.ebs_volume_count 2->absent OK -aws_attributes.ebs_volume_count absent->1 OK -aws_attributes.ebs_volume_count absent->2 OK -aws_attributes.ebs_volume_iops 1->2 OK -aws_attributes.ebs_volume_iops 1->absent OK -aws_attributes.ebs_volume_iops 2->1 OK -aws_attributes.ebs_volume_iops 2->absent OK -aws_attributes.ebs_volume_iops absent->1 OK -aws_attributes.ebs_volume_iops absent->2 OK -aws_attributes.ebs_volume_size 1->2 OK -aws_attributes.ebs_volume_size 1->absent OK -aws_attributes.ebs_volume_size 2->1 OK -aws_attributes.ebs_volume_size 2->absent OK -aws_attributes.ebs_volume_size absent->1 OK -aws_attributes.ebs_volume_size absent->2 OK -aws_attributes.ebs_volume_throughput 1->2 OK -aws_attributes.ebs_volume_throughput 1->absent OK -aws_attributes.ebs_volume_throughput 2->1 OK -aws_attributes.ebs_volume_throughput 2->absent OK -aws_attributes.ebs_volume_throughput absent->1 OK -aws_attributes.ebs_volume_throughput absent->2 OK -aws_attributes.ebs_volume_type absent->x OK -aws_attributes.ebs_volume_type absent->y OK -aws_attributes.ebs_volume_type x->absent OK -aws_attributes.ebs_volume_type x->y OK -aws_attributes.ebs_volume_type y->absent OK -aws_attributes.ebs_volume_type y->x OK -aws_attributes.first_on_demand 1->2 OK -aws_attributes.first_on_demand 1->absent OK -aws_attributes.first_on_demand 2->1 OK -aws_attributes.first_on_demand 2->absent OK -aws_attributes.first_on_demand absent->1 OK -aws_attributes.first_on_demand absent->2 OK -aws_attributes.instance_profile_arn absent->x OK -aws_attributes.instance_profile_arn absent->y OK -aws_attributes.instance_profile_arn x->absent OK -aws_attributes.instance_profile_arn x->y OK -aws_attributes.instance_profile_arn y->absent OK -aws_attributes.instance_profile_arn y->x OK -aws_attributes.spot_bid_price_percent 1->2 OK -aws_attributes.spot_bid_price_percent 1->absent OK -aws_attributes.spot_bid_price_percent 2->1 OK -aws_attributes.spot_bid_price_percent 2->absent OK -aws_attributes.spot_bid_price_percent absent->1 OK -aws_attributes.spot_bid_price_percent absent->2 OK -aws_attributes.zone_id absent->x OK -aws_attributes.zone_id absent->y OK -aws_attributes.zone_id x->absent OK -aws_attributes.zone_id x->y OK -aws_attributes.zone_id y->absent OK -aws_attributes.zone_id y->x OK -azure_attributes.availability absent->x OK -azure_attributes.availability absent->y OK -azure_attributes.availability x->absent OK -azure_attributes.availability x->y OK -azure_attributes.availability y->absent OK -azure_attributes.availability y->x OK -azure_attributes.capacity_reservation_group absent->x OK -azure_attributes.capacity_reservation_group absent->y OK -azure_attributes.capacity_reservation_group x->absent OK -azure_attributes.capacity_reservation_group x->y OK -azure_attributes.capacity_reservation_group y->absent OK -azure_attributes.capacity_reservation_group y->x OK -azure_attributes.first_on_demand 1->2 OK -azure_attributes.first_on_demand 1->absent OK -azure_attributes.first_on_demand 2->1 OK -azure_attributes.first_on_demand 2->absent OK -azure_attributes.first_on_demand absent->1 OK -azure_attributes.first_on_demand absent->2 OK -azure_attributes.log_analytics_info.log_analytics_primary_key absent->x OK -azure_attributes.log_analytics_info.log_analytics_primary_key absent->y OK -azure_attributes.log_analytics_info.log_analytics_primary_key x->absent OK -azure_attributes.log_analytics_info.log_analytics_primary_key x->y OK -azure_attributes.log_analytics_info.log_analytics_primary_key y->absent OK -azure_attributes.log_analytics_info.log_analytics_primary_key y->x OK -azure_attributes.log_analytics_info.log_analytics_workspace_id absent->x OK -azure_attributes.log_analytics_info.log_analytics_workspace_id absent->y OK -azure_attributes.log_analytics_info.log_analytics_workspace_id x->absent OK -azure_attributes.log_analytics_info.log_analytics_workspace_id x->y OK -azure_attributes.log_analytics_info.log_analytics_workspace_id y->absent OK -azure_attributes.log_analytics_info.log_analytics_workspace_id y->x OK -azure_attributes.spot_bid_max_price 1->2 OK -azure_attributes.spot_bid_max_price 1->absent OK -azure_attributes.spot_bid_max_price 2->1 OK -azure_attributes.spot_bid_max_price 2->absent OK -azure_attributes.spot_bid_max_price absent->1 OK -azure_attributes.spot_bid_max_price absent->2 OK -cluster_log_conf.dbfs.destination x->y OK -cluster_log_conf.dbfs.destination y->x OK -cluster_log_conf.s3.canned_acl absent->x OK -cluster_log_conf.s3.canned_acl absent->y OK -cluster_log_conf.s3.canned_acl x->absent OK -cluster_log_conf.s3.canned_acl x->y OK -cluster_log_conf.s3.canned_acl y->absent OK -cluster_log_conf.s3.canned_acl y->x OK -cluster_log_conf.s3.destination x->y OK -cluster_log_conf.s3.destination y->x OK -cluster_log_conf.s3.enable_encryption absent->false SUPPRESSED empty -cluster_log_conf.s3.enable_encryption absent->true OK -cluster_log_conf.s3.enable_encryption false->absent SUPPRESSED empty -cluster_log_conf.s3.enable_encryption false->true OK -cluster_log_conf.s3.enable_encryption true->absent OK -cluster_log_conf.s3.enable_encryption true->false OK -cluster_log_conf.s3.encryption_type absent->x OK -cluster_log_conf.s3.encryption_type absent->y OK -cluster_log_conf.s3.encryption_type x->absent OK -cluster_log_conf.s3.encryption_type x->y OK -cluster_log_conf.s3.encryption_type y->absent OK -cluster_log_conf.s3.encryption_type y->x OK -cluster_log_conf.s3.endpoint absent->x OK -cluster_log_conf.s3.endpoint absent->y OK -cluster_log_conf.s3.endpoint x->absent OK -cluster_log_conf.s3.endpoint x->y OK -cluster_log_conf.s3.endpoint y->absent OK -cluster_log_conf.s3.endpoint y->x OK -cluster_log_conf.s3.kms_key absent->x OK -cluster_log_conf.s3.kms_key absent->y OK -cluster_log_conf.s3.kms_key x->absent OK -cluster_log_conf.s3.kms_key x->y OK -cluster_log_conf.s3.kms_key y->absent OK -cluster_log_conf.s3.kms_key y->x OK -cluster_log_conf.s3.region absent->x OK -cluster_log_conf.s3.region absent->y OK -cluster_log_conf.s3.region x->absent OK -cluster_log_conf.s3.region x->y OK -cluster_log_conf.s3.region y->absent OK -cluster_log_conf.s3.region y->x OK -cluster_log_conf.volumes.destination x->y OK -cluster_log_conf.volumes.destination y->x OK -cluster_name absent->x OK -cluster_name absent->y OK -cluster_name x->absent OK -cluster_name x->y OK -cluster_name y->absent OK -cluster_name y->x OK -data_security_mode absent->x OK -data_security_mode absent->y OK -data_security_mode x->absent OK -data_security_mode x->y OK -data_security_mode y->absent OK -data_security_mode y->x OK -dependency_mode absent->x OK -dependency_mode absent->y OK -dependency_mode x->absent OK -dependency_mode x->y OK -dependency_mode y->absent OK -dependency_mode y->x OK -docker_image.basic_auth.password absent->x OK -docker_image.basic_auth.password absent->y OK -docker_image.basic_auth.password x->absent OK -docker_image.basic_auth.password x->y OK -docker_image.basic_auth.password y->absent OK -docker_image.basic_auth.password y->x OK -docker_image.basic_auth.username absent->x OK -docker_image.basic_auth.username absent->y OK -docker_image.basic_auth.username x->absent OK -docker_image.basic_auth.username x->y OK -docker_image.basic_auth.username y->absent OK -docker_image.basic_auth.username y->x OK -docker_image.url absent->x OK -docker_image.url absent->y OK -docker_image.url x->absent OK -docker_image.url x->y OK -docker_image.url y->absent OK -docker_image.url y->x OK -driver_instance_pool_id absent->x OK -driver_instance_pool_id absent->y OK -driver_instance_pool_id x->absent OK -driver_instance_pool_id x->y OK -driver_instance_pool_id y->absent OK -driver_instance_pool_id y->x OK -driver_node_type_id absent->x OK -driver_node_type_id absent->y OK -driver_node_type_id x->absent OK -driver_node_type_id x->y OK -driver_node_type_id y->absent OK -driver_node_type_id y->x OK -enable_elastic_disk absent->false SUPPRESSED remote_already_set -enable_elastic_disk absent->true OK -enable_elastic_disk false->absent SUPPRESSED empty -enable_elastic_disk false->true OK -enable_elastic_disk true->absent OK -enable_elastic_disk true->false OK -enable_local_disk_encryption absent->false SUPPRESSED empty -enable_local_disk_encryption absent->true OK -enable_local_disk_encryption false->absent SUPPRESSED empty -enable_local_disk_encryption false->true OK -enable_local_disk_encryption true->absent OK -enable_local_disk_encryption true->false OK -gcp_attributes.availability absent->x OK -gcp_attributes.availability absent->y OK -gcp_attributes.availability x->absent OK -gcp_attributes.availability x->y OK -gcp_attributes.availability y->absent OK -gcp_attributes.availability y->x OK -gcp_attributes.boot_disk_size 1->2 OK -gcp_attributes.boot_disk_size 1->absent OK -gcp_attributes.boot_disk_size 2->1 OK -gcp_attributes.boot_disk_size 2->absent OK -gcp_attributes.boot_disk_size absent->1 OK -gcp_attributes.boot_disk_size absent->2 OK -gcp_attributes.confidential_compute_type absent->x OK -gcp_attributes.confidential_compute_type absent->y OK -gcp_attributes.confidential_compute_type x->absent OK -gcp_attributes.confidential_compute_type x->y OK -gcp_attributes.confidential_compute_type y->absent OK -gcp_attributes.confidential_compute_type y->x OK -gcp_attributes.first_on_demand 1->2 OK -gcp_attributes.first_on_demand 1->absent OK -gcp_attributes.first_on_demand 2->1 OK -gcp_attributes.first_on_demand 2->absent OK -gcp_attributes.first_on_demand absent->1 OK -gcp_attributes.first_on_demand absent->2 OK -gcp_attributes.google_service_account absent->x OK -gcp_attributes.google_service_account absent->y OK -gcp_attributes.google_service_account x->absent OK -gcp_attributes.google_service_account x->y OK -gcp_attributes.google_service_account y->absent OK -gcp_attributes.google_service_account y->x OK -gcp_attributes.local_ssd_count 1->2 OK -gcp_attributes.local_ssd_count 1->absent OK -gcp_attributes.local_ssd_count 2->1 OK -gcp_attributes.local_ssd_count 2->absent OK -gcp_attributes.local_ssd_count absent->1 OK -gcp_attributes.local_ssd_count absent->2 OK -gcp_attributes.use_preemptible_executors absent->false SUPPRESSED empty -gcp_attributes.use_preemptible_executors absent->true OK -gcp_attributes.use_preemptible_executors false->absent SUPPRESSED empty -gcp_attributes.use_preemptible_executors false->true OK -gcp_attributes.use_preemptible_executors true->absent OK -gcp_attributes.use_preemptible_executors true->false OK -gcp_attributes.zone_id absent->x OK -gcp_attributes.zone_id absent->y OK -gcp_attributes.zone_id x->absent OK -gcp_attributes.zone_id x->y OK -gcp_attributes.zone_id y->absent OK -gcp_attributes.zone_id y->x OK -instance_pool_id absent->x OK -instance_pool_id absent->y OK -instance_pool_id x->absent OK -instance_pool_id x->y OK -instance_pool_id y->absent OK -instance_pool_id y->x OK -is_single_node absent->false SUPPRESSED empty -is_single_node absent->true OK -is_single_node false->absent SUPPRESSED empty -is_single_node false->true OK -is_single_node true->absent OK -is_single_node true->false OK -kind absent->x OK -kind absent->y OK -kind x->absent OK -kind x->y OK -kind y->absent OK -kind y->x OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle.started absent->false OK -lifecycle.started absent->true OK -lifecycle.started false->absent OK -lifecycle.started false->true OK -lifecycle.started true->absent OK -lifecycle.started true->false OK -node_type_id absent->x OK -node_type_id absent->y OK -node_type_id x->absent OK -node_type_id x->y OK -node_type_id y->absent OK -node_type_id y->x OK -num_workers 1->2 OK -num_workers 1->absent OK -num_workers 2->1 OK -num_workers 2->absent OK -num_workers absent->1 OK -num_workers absent->2 OK -policy_id absent->x OK -policy_id absent->y OK -policy_id x->absent OK -policy_id x->y OK -policy_id y->absent OK -policy_id y->x OK -remote_disk_throughput 1->2 OK -remote_disk_throughput 1->absent OK -remote_disk_throughput 2->1 OK -remote_disk_throughput 2->absent OK -remote_disk_throughput absent->1 OK -remote_disk_throughput absent->2 OK -runtime_engine absent->x OK -runtime_engine absent->y OK -runtime_engine x->absent OK -runtime_engine x->y OK -runtime_engine y->absent OK -runtime_engine y->x OK -single_user_name absent->x OK -single_user_name absent->y OK -single_user_name x->absent OK -single_user_name x->y OK -single_user_name y->absent OK -single_user_name y->x OK -spark_version absent->x OK -spark_version absent->y OK -spark_version x->absent OK -spark_version x->y OK -spark_version y->absent OK -spark_version y->x OK -total_initial_remote_disk_size 1->2 OK -total_initial_remote_disk_size 1->absent OK -total_initial_remote_disk_size 2->1 OK -total_initial_remote_disk_size 2->absent OK -total_initial_remote_disk_size absent->1 OK -total_initial_remote_disk_size absent->2 OK -use_ml_runtime absent->false SUPPRESSED empty -use_ml_runtime absent->true OK -use_ml_runtime false->absent SUPPRESSED empty -use_ml_runtime false->true OK -use_ml_runtime true->absent OK -use_ml_runtime true->false OK -workload_type.clients.jobs absent->false OK -workload_type.clients.jobs absent->true OK -workload_type.clients.jobs false->absent SUPPRESSED empty -workload_type.clients.jobs false->true OK -workload_type.clients.jobs true->absent OK -workload_type.clients.jobs true->false OK -workload_type.clients.notebooks absent->false SUPPRESSED empty -workload_type.clients.notebooks absent->true OK -workload_type.clients.notebooks false->absent SUPPRESSED empty -workload_type.clients.notebooks false->true OK -workload_type.clients.notebooks true->absent OK -workload_type.clients.notebooks true->false OK - -=== summary -OK 686 -SUPPRESSED 34 -NOT_OBSERVABLE 24 - -=== not covered: 23 fields under a slice or map -custom_tags.* -driver_node_type_flexibility.alternate_node_type_ids[*] -init_scripts[*].abfss.destination -init_scripts[*].dbfs.destination -init_scripts[*].file.destination -init_scripts[*].gcs.destination -init_scripts[*].s3.canned_acl -init_scripts[*].s3.destination -init_scripts[*].s3.enable_encryption -init_scripts[*].s3.encryption_type -init_scripts[*].s3.endpoint -init_scripts[*].s3.kms_key -init_scripts[*].s3.region -init_scripts[*].volumes.destination -init_scripts[*].workspace.destination -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -spark_conf.* -spark_env_vars.* -ssh_public_keys[*] -worker_node_type_flexibility.alternate_node_type_ids[*] +apply_policy_default_values false absent SUPPRESSED remote_already_set +enable_elastic_disk absent false SUPPRESSED remote_already_set diff --git a/bundle/direct/tests/output/dashboards.full.txt b/bundle/direct/tests/output/dashboards.full.txt new file mode 100644 index 00000000000..932c9014db4 --- /dev/null +++ b/bundle/direct/tests/output/dashboards.full.txt @@ -0,0 +1,97 @@ +=== dashboard.yml.tmpl +create_time absent x OK +create_time absent y OK +create_time x absent OK +create_time x y OK +create_time y absent OK +create_time y x OK +dashboard_id absent x OK +dashboard_id absent y OK +dashboard_id x absent OK +dashboard_id x y OK +dashboard_id y absent OK +dashboard_id y x OK +dataset_catalog absent x OK +dataset_catalog absent y OK +dataset_catalog x absent OK +dataset_catalog x y OK +dataset_catalog y absent OK +dataset_catalog y x OK +dataset_schema absent x OK +dataset_schema absent y OK +dataset_schema x absent OK +dataset_schema x y OK +dataset_schema y absent OK +dataset_schema y x OK +display_name absent x OK +display_name absent y OK +display_name x absent POST_DEPLOY_DRIFT display_name +display_name x y OK +display_name y absent POST_DEPLOY_DRIFT display_name +display_name y x OK +embed_credentials absent false NOT_OBSERVABLE +embed_credentials absent true OK +embed_credentials false absent SUPPRESSED empty +embed_credentials false true OK +embed_credentials true absent OK +embed_credentials true false OK +etag absent x SUPPRESSED custom +etag absent y SUPPRESSED custom +etag x absent SUPPRESSED custom +etag x y SUPPRESSED custom +etag y absent SUPPRESSED custom +etag y x SUPPRESSED custom +file_path absent x NOT_OBSERVABLE +file_path absent y NOT_OBSERVABLE +file_path x absent NOT_OBSERVABLE +file_path x y NOT_OBSERVABLE +file_path y absent NOT_OBSERVABLE +file_path y x NOT_OBSERVABLE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle_state absent x OK +lifecycle_state absent y OK +lifecycle_state x absent OK +lifecycle_state x y OK +lifecycle_state y absent OK +lifecycle_state y x OK +parent_path absent x POST_DEPLOY_DRIFT parent_path +parent_path absent y POST_DEPLOY_DRIFT parent_path +parent_path x absent POST_DEPLOY_DRIFT parent_path +parent_path x y POST_DEPLOY_DRIFT parent_path +parent_path y absent POST_DEPLOY_DRIFT parent_path +parent_path y x POST_DEPLOY_DRIFT parent_path +path absent x OK +path absent y OK +path x absent OK +path x y OK +path y absent OK +path y x OK +update_time absent x OK +update_time absent y OK +update_time x absent OK +update_time x y OK +update_time y absent OK +update_time y x OK +warehouse_id absent x OK +warehouse_id absent y OK +warehouse_id x absent OK +warehouse_id x y OK +warehouse_id y absent OK +warehouse_id y x OK + +=== summary +OK 56 +SUPPRESSED 7 +NOT_OBSERVABLE 13 +POST_DEPLOY_DRIFT 8 + +=== not covered: 4 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index fa73a1d5b1a..ddb28dbcead 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -1,97 +1,15 @@ === dashboard.yml.tmpl -create_time absent->x OK -create_time absent->y OK -create_time x->absent OK -create_time x->y OK -create_time y->absent OK -create_time y->x OK -dashboard_id absent->x OK -dashboard_id absent->y OK -dashboard_id x->absent OK -dashboard_id x->y OK -dashboard_id y->absent OK -dashboard_id y->x OK -dataset_catalog absent->x OK -dataset_catalog absent->y OK -dataset_catalog x->absent OK -dataset_catalog x->y OK -dataset_catalog y->absent OK -dataset_catalog y->x OK -dataset_schema absent->x OK -dataset_schema absent->y OK -dataset_schema x->absent OK -dataset_schema x->y OK -dataset_schema y->absent OK -dataset_schema y->x OK -display_name absent->x OK -display_name absent->y OK -display_name x->absent DRIFT display_name -display_name x->y OK -display_name y->absent DRIFT display_name -display_name y->x OK -embed_credentials absent->false NOT_OBSERVABLE -embed_credentials absent->true OK -embed_credentials false->absent SUPPRESSED empty -embed_credentials false->true OK -embed_credentials true->absent OK -embed_credentials true->false OK -etag absent->x SUPPRESSED custom -etag absent->y SUPPRESSED custom -etag x->absent SUPPRESSED custom -etag x->y SUPPRESSED custom -etag y->absent SUPPRESSED custom -etag y->x SUPPRESSED custom -file_path absent->x NOT_OBSERVABLE -file_path absent->y NOT_OBSERVABLE -file_path x->absent NOT_OBSERVABLE -file_path x->y NOT_OBSERVABLE -file_path y->absent NOT_OBSERVABLE -file_path y->x NOT_OBSERVABLE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle_state absent->x OK -lifecycle_state absent->y OK -lifecycle_state x->absent OK -lifecycle_state x->y OK -lifecycle_state y->absent OK -lifecycle_state y->x OK -parent_path absent->x DRIFT parent_path -parent_path absent->y DRIFT parent_path -parent_path x->absent DRIFT parent_path -parent_path x->y DRIFT parent_path -parent_path y->absent DRIFT parent_path -parent_path y->x DRIFT parent_path -path absent->x OK -path absent->y OK -path x->absent OK -path x->y OK -path y->absent OK -path y->x OK -update_time absent->x OK -update_time absent->y OK -update_time x->absent OK -update_time x->y OK -update_time y->absent OK -update_time y->x OK -warehouse_id absent->x OK -warehouse_id absent->y OK -warehouse_id x->absent OK -warehouse_id x->y OK -warehouse_id y->absent OK -warehouse_id y->x OK - -=== summary -OK 56 -SUPPRESSED 7 -NOT_OBSERVABLE 13 -DRIFT 8 - -=== not covered: 4 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name +display_name x absent POST_DEPLOY_DRIFT display_name +display_name y absent POST_DEPLOY_DRIFT display_name +etag absent x SUPPRESSED custom +etag absent y SUPPRESSED custom +etag x absent SUPPRESSED custom +etag x y SUPPRESSED custom +etag y absent SUPPRESSED custom +etag y x SUPPRESSED custom +parent_path absent x POST_DEPLOY_DRIFT parent_path +parent_path absent y POST_DEPLOY_DRIFT parent_path +parent_path x absent POST_DEPLOY_DRIFT parent_path +parent_path x y POST_DEPLOY_DRIFT parent_path +parent_path y absent POST_DEPLOY_DRIFT parent_path +parent_path y x POST_DEPLOY_DRIFT parent_path diff --git a/bundle/direct/tests/output/database_instances.full.txt b/bundle/direct/tests/output/database_instances.full.txt new file mode 100644 index 00000000000..2832da52b1a --- /dev/null +++ b/bundle/direct/tests/output/database_instances.full.txt @@ -0,0 +1,186 @@ +=== database_instance.yml.tmpl +capacity absent x POST_DEPLOY_DRIFT capacity +capacity absent y POST_DEPLOY_DRIFT capacity +capacity x absent SUPPRESSED remote_already_set +capacity x y POST_DEPLOY_DRIFT capacity +capacity y absent SUPPRESSED remote_already_set +capacity y x POST_DEPLOY_DRIFT capacity +creation_time absent x POST_DEPLOY_DRIFT capacity +creation_time absent y POST_DEPLOY_DRIFT capacity +creation_time x absent POST_DEPLOY_DRIFT capacity +creation_time x y POST_DEPLOY_DRIFT capacity +creation_time y absent POST_DEPLOY_DRIFT capacity +creation_time y x POST_DEPLOY_DRIFT capacity +creator absent x POST_DEPLOY_DRIFT capacity +creator absent y POST_DEPLOY_DRIFT capacity +creator x absent POST_DEPLOY_DRIFT capacity +creator x y POST_DEPLOY_DRIFT capacity +creator y absent POST_DEPLOY_DRIFT capacity +creator y x POST_DEPLOY_DRIFT capacity +effective_capacity absent x POST_DEPLOY_DRIFT capacity +effective_capacity absent y POST_DEPLOY_DRIFT capacity +effective_capacity x absent POST_DEPLOY_DRIFT capacity +effective_capacity x y POST_DEPLOY_DRIFT capacity +effective_capacity y absent POST_DEPLOY_DRIFT capacity +effective_capacity y x POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login absent false SUPPRESSED empty +effective_enable_pg_native_login absent true POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login false absent POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login false true POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login true false POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries absent false SUPPRESSED empty +effective_enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries false absent POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity +effective_node_count 1 2 POST_DEPLOY_DRIFT capacity +effective_node_count 1 absent POST_DEPLOY_DRIFT capacity +effective_node_count 2 1 POST_DEPLOY_DRIFT capacity +effective_node_count 2 absent POST_DEPLOY_DRIFT capacity +effective_node_count absent 1 POST_DEPLOY_DRIFT capacity +effective_node_count absent 2 POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days 1 2 POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days 1 absent POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days 2 1 POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days 2 absent POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days absent 1 POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days absent 2 POST_DEPLOY_DRIFT capacity +effective_stopped absent false SUPPRESSED empty +effective_stopped absent true POST_DEPLOY_DRIFT capacity +effective_stopped false absent POST_DEPLOY_DRIFT capacity +effective_stopped false true POST_DEPLOY_DRIFT capacity +effective_stopped true absent POST_DEPLOY_DRIFT capacity +effective_stopped true false POST_DEPLOY_DRIFT capacity +effective_usage_policy_id absent x POST_DEPLOY_DRIFT capacity +effective_usage_policy_id absent y POST_DEPLOY_DRIFT capacity +effective_usage_policy_id x absent POST_DEPLOY_DRIFT capacity +effective_usage_policy_id x y POST_DEPLOY_DRIFT capacity +effective_usage_policy_id y absent POST_DEPLOY_DRIFT capacity +effective_usage_policy_id y x POST_DEPLOY_DRIFT capacity +enable_pg_native_login absent false SUPPRESSED empty +enable_pg_native_login absent true POST_DEPLOY_DRIFT capacity +enable_pg_native_login false absent POST_DEPLOY_DRIFT capacity +enable_pg_native_login false true POST_DEPLOY_DRIFT capacity +enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity +enable_pg_native_login true false POST_DEPLOY_DRIFT capacity +enable_readable_secondaries absent false SUPPRESSED empty +enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity +enable_readable_secondaries false absent POST_DEPLOY_DRIFT capacity +enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity +enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity +enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK_RECREATE +name y x OK_RECREATE +node_count 1 2 POST_DEPLOY_DRIFT capacity +node_count 1 absent POST_DEPLOY_DRIFT capacity +node_count 2 1 POST_DEPLOY_DRIFT capacity +node_count 2 absent POST_DEPLOY_DRIFT capacity +node_count absent 1 POST_DEPLOY_DRIFT capacity +node_count absent 2 POST_DEPLOY_DRIFT capacity +parent_instance_ref.branch_time absent x OK_RECREATE +parent_instance_ref.branch_time absent y OK_RECREATE +parent_instance_ref.branch_time x absent OK_RECREATE +parent_instance_ref.branch_time x y OK_RECREATE +parent_instance_ref.branch_time y absent OK_RECREATE +parent_instance_ref.branch_time y x OK_RECREATE +parent_instance_ref.effective_lsn absent x OK_RECREATE +parent_instance_ref.effective_lsn absent y OK_RECREATE +parent_instance_ref.effective_lsn x absent OK_RECREATE +parent_instance_ref.effective_lsn x y OK_RECREATE +parent_instance_ref.effective_lsn y absent OK_RECREATE +parent_instance_ref.effective_lsn y x OK_RECREATE +parent_instance_ref.lsn absent x OK_RECREATE +parent_instance_ref.lsn absent y OK_RECREATE +parent_instance_ref.lsn x absent OK_RECREATE +parent_instance_ref.lsn x y OK_RECREATE +parent_instance_ref.lsn y absent OK_RECREATE +parent_instance_ref.lsn y x OK_RECREATE +parent_instance_ref.name absent x OK_RECREATE +parent_instance_ref.name absent y OK_RECREATE +parent_instance_ref.name x absent OK_RECREATE +parent_instance_ref.name x y OK_RECREATE +parent_instance_ref.name y absent OK_RECREATE +parent_instance_ref.name y x OK_RECREATE +parent_instance_ref.uid absent x OK_RECREATE +parent_instance_ref.uid absent y OK_RECREATE +parent_instance_ref.uid x absent OK_RECREATE +parent_instance_ref.uid x y OK_RECREATE +parent_instance_ref.uid y absent OK_RECREATE +parent_instance_ref.uid y x OK_RECREATE +pg_version absent x POST_DEPLOY_DRIFT capacity +pg_version absent y POST_DEPLOY_DRIFT capacity +pg_version x absent POST_DEPLOY_DRIFT capacity +pg_version x y POST_DEPLOY_DRIFT capacity +pg_version y absent POST_DEPLOY_DRIFT capacity +pg_version y x POST_DEPLOY_DRIFT capacity +read_only_dns absent x POST_DEPLOY_DRIFT capacity +read_only_dns absent y POST_DEPLOY_DRIFT capacity +read_only_dns x absent POST_DEPLOY_DRIFT capacity +read_only_dns x y POST_DEPLOY_DRIFT capacity +read_only_dns y absent POST_DEPLOY_DRIFT capacity +read_only_dns y x POST_DEPLOY_DRIFT capacity +read_write_dns absent x POST_DEPLOY_DRIFT capacity +read_write_dns absent y POST_DEPLOY_DRIFT capacity +read_write_dns x absent POST_DEPLOY_DRIFT capacity +read_write_dns x y POST_DEPLOY_DRIFT capacity +read_write_dns y absent POST_DEPLOY_DRIFT capacity +read_write_dns y x POST_DEPLOY_DRIFT capacity +retention_window_in_days 1 2 POST_DEPLOY_DRIFT capacity +retention_window_in_days 1 absent POST_DEPLOY_DRIFT capacity +retention_window_in_days 2 1 POST_DEPLOY_DRIFT capacity +retention_window_in_days 2 absent POST_DEPLOY_DRIFT capacity +retention_window_in_days absent 1 POST_DEPLOY_DRIFT capacity +retention_window_in_days absent 2 POST_DEPLOY_DRIFT capacity +state absent x POST_DEPLOY_DRIFT capacity +state absent y POST_DEPLOY_DRIFT capacity +state x absent POST_DEPLOY_DRIFT capacity +state x y POST_DEPLOY_DRIFT capacity +state y absent POST_DEPLOY_DRIFT capacity +state y x POST_DEPLOY_DRIFT capacity +stopped absent false SUPPRESSED empty +stopped absent true POST_DEPLOY_DRIFT capacity +stopped false absent POST_DEPLOY_DRIFT capacity +stopped false true POST_DEPLOY_DRIFT capacity +stopped true absent POST_DEPLOY_DRIFT capacity +stopped true false POST_DEPLOY_DRIFT capacity +uid absent x POST_DEPLOY_DRIFT capacity +uid absent y POST_DEPLOY_DRIFT capacity +uid x absent POST_DEPLOY_DRIFT capacity +uid x y POST_DEPLOY_DRIFT capacity +uid y absent POST_DEPLOY_DRIFT capacity +uid y x POST_DEPLOY_DRIFT capacity +usage_policy_id absent x POST_DEPLOY_DRIFT capacity +usage_policy_id absent y POST_DEPLOY_DRIFT capacity +usage_policy_id x absent POST_DEPLOY_DRIFT capacity +usage_policy_id x y POST_DEPLOY_DRIFT capacity +usage_policy_id y absent POST_DEPLOY_DRIFT capacity +usage_policy_id y x POST_DEPLOY_DRIFT capacity + +=== summary +OK_RECREATE 32 +SUPPRESSED 8 +NOT_OBSERVABLE 6 +POST_DEPLOY_DRIFT 118 + +=== not covered: 13 fields under a slice or map +child_instance_refs[*].branch_time +child_instance_refs[*].effective_lsn +child_instance_refs[*].lsn +child_instance_refs[*].name +child_instance_refs[*].uid +custom_tags[*].key +custom_tags[*].value +effective_custom_tags[*].key +effective_custom_tags[*].value +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index 55386f507ee..696a98dfa0b 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -1,186 +1,121 @@ === database_instance.yml.tmpl -capacity absent->x DRIFT capacity -capacity absent->y DRIFT capacity -capacity x->absent SUPPRESSED remote_already_set -capacity x->y DRIFT capacity -capacity y->absent SUPPRESSED remote_already_set -capacity y->x DRIFT capacity -creation_time absent->x DRIFT capacity -creation_time absent->y DRIFT capacity -creation_time x->absent DRIFT capacity -creation_time x->y DRIFT capacity -creation_time y->absent DRIFT capacity -creation_time y->x DRIFT capacity -creator absent->x DRIFT capacity -creator absent->y DRIFT capacity -creator x->absent DRIFT capacity -creator x->y DRIFT capacity -creator y->absent DRIFT capacity -creator y->x DRIFT capacity -effective_capacity absent->x DRIFT capacity -effective_capacity absent->y DRIFT capacity -effective_capacity x->absent DRIFT capacity -effective_capacity x->y DRIFT capacity -effective_capacity y->absent DRIFT capacity -effective_capacity y->x DRIFT capacity -effective_enable_pg_native_login absent->false SUPPRESSED empty -effective_enable_pg_native_login absent->true DRIFT capacity -effective_enable_pg_native_login false->absent DRIFT capacity -effective_enable_pg_native_login false->true DRIFT capacity -effective_enable_pg_native_login true->absent DRIFT capacity -effective_enable_pg_native_login true->false DRIFT capacity -effective_enable_readable_secondaries absent->false SUPPRESSED empty -effective_enable_readable_secondaries absent->true DRIFT capacity -effective_enable_readable_secondaries false->absent DRIFT capacity -effective_enable_readable_secondaries false->true DRIFT capacity -effective_enable_readable_secondaries true->absent DRIFT capacity -effective_enable_readable_secondaries true->false DRIFT capacity -effective_node_count 1->2 DRIFT capacity -effective_node_count 1->absent DRIFT capacity -effective_node_count 2->1 DRIFT capacity -effective_node_count 2->absent DRIFT capacity -effective_node_count absent->1 DRIFT capacity -effective_node_count absent->2 DRIFT capacity -effective_retention_window_in_days 1->2 DRIFT capacity -effective_retention_window_in_days 1->absent DRIFT capacity -effective_retention_window_in_days 2->1 DRIFT capacity -effective_retention_window_in_days 2->absent DRIFT capacity -effective_retention_window_in_days absent->1 DRIFT capacity -effective_retention_window_in_days absent->2 DRIFT capacity -effective_stopped absent->false SUPPRESSED empty -effective_stopped absent->true DRIFT capacity -effective_stopped false->absent DRIFT capacity -effective_stopped false->true DRIFT capacity -effective_stopped true->absent DRIFT capacity -effective_stopped true->false DRIFT capacity -effective_usage_policy_id absent->x DRIFT capacity -effective_usage_policy_id absent->y DRIFT capacity -effective_usage_policy_id x->absent DRIFT capacity -effective_usage_policy_id x->y DRIFT capacity -effective_usage_policy_id y->absent DRIFT capacity -effective_usage_policy_id y->x DRIFT capacity -enable_pg_native_login absent->false SUPPRESSED empty -enable_pg_native_login absent->true DRIFT capacity -enable_pg_native_login false->absent DRIFT capacity -enable_pg_native_login false->true DRIFT capacity -enable_pg_native_login true->absent DRIFT capacity -enable_pg_native_login true->false DRIFT capacity -enable_readable_secondaries absent->false SUPPRESSED empty -enable_readable_secondaries absent->true DRIFT capacity -enable_readable_secondaries false->absent DRIFT capacity -enable_readable_secondaries false->true DRIFT capacity -enable_readable_secondaries true->absent DRIFT capacity -enable_readable_secondaries true->false DRIFT capacity -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK_RECREATE -name y->x OK_RECREATE -node_count 1->2 DRIFT capacity -node_count 1->absent DRIFT capacity -node_count 2->1 DRIFT capacity -node_count 2->absent DRIFT capacity -node_count absent->1 DRIFT capacity -node_count absent->2 DRIFT capacity -parent_instance_ref.branch_time absent->x OK_RECREATE -parent_instance_ref.branch_time absent->y OK_RECREATE -parent_instance_ref.branch_time x->absent OK_RECREATE -parent_instance_ref.branch_time x->y OK_RECREATE -parent_instance_ref.branch_time y->absent OK_RECREATE -parent_instance_ref.branch_time y->x OK_RECREATE -parent_instance_ref.effective_lsn absent->x OK_RECREATE -parent_instance_ref.effective_lsn absent->y OK_RECREATE -parent_instance_ref.effective_lsn x->absent OK_RECREATE -parent_instance_ref.effective_lsn x->y OK_RECREATE -parent_instance_ref.effective_lsn y->absent OK_RECREATE -parent_instance_ref.effective_lsn y->x OK_RECREATE -parent_instance_ref.lsn absent->x OK_RECREATE -parent_instance_ref.lsn absent->y OK_RECREATE -parent_instance_ref.lsn x->absent OK_RECREATE -parent_instance_ref.lsn x->y OK_RECREATE -parent_instance_ref.lsn y->absent OK_RECREATE -parent_instance_ref.lsn y->x OK_RECREATE -parent_instance_ref.name absent->x OK_RECREATE -parent_instance_ref.name absent->y OK_RECREATE -parent_instance_ref.name x->absent OK_RECREATE -parent_instance_ref.name x->y OK_RECREATE -parent_instance_ref.name y->absent OK_RECREATE -parent_instance_ref.name y->x OK_RECREATE -parent_instance_ref.uid absent->x OK_RECREATE -parent_instance_ref.uid absent->y OK_RECREATE -parent_instance_ref.uid x->absent OK_RECREATE -parent_instance_ref.uid x->y OK_RECREATE -parent_instance_ref.uid y->absent OK_RECREATE -parent_instance_ref.uid y->x OK_RECREATE -pg_version absent->x DRIFT capacity -pg_version absent->y DRIFT capacity -pg_version x->absent DRIFT capacity -pg_version x->y DRIFT capacity -pg_version y->absent DRIFT capacity -pg_version y->x DRIFT capacity -read_only_dns absent->x DRIFT capacity -read_only_dns absent->y DRIFT capacity -read_only_dns x->absent DRIFT capacity -read_only_dns x->y DRIFT capacity -read_only_dns y->absent DRIFT capacity -read_only_dns y->x DRIFT capacity -read_write_dns absent->x DRIFT capacity -read_write_dns absent->y DRIFT capacity -read_write_dns x->absent DRIFT capacity -read_write_dns x->y DRIFT capacity -read_write_dns y->absent DRIFT capacity -read_write_dns y->x DRIFT capacity -retention_window_in_days 1->2 DRIFT capacity -retention_window_in_days 1->absent DRIFT capacity -retention_window_in_days 2->1 DRIFT capacity -retention_window_in_days 2->absent DRIFT capacity -retention_window_in_days absent->1 DRIFT capacity -retention_window_in_days absent->2 DRIFT capacity -state absent->x DRIFT capacity -state absent->y DRIFT capacity -state x->absent DRIFT capacity -state x->y DRIFT capacity -state y->absent DRIFT capacity -state y->x DRIFT capacity -stopped absent->false SUPPRESSED empty -stopped absent->true DRIFT capacity -stopped false->absent DRIFT capacity -stopped false->true DRIFT capacity -stopped true->absent DRIFT capacity -stopped true->false DRIFT capacity -uid absent->x DRIFT capacity -uid absent->y DRIFT capacity -uid x->absent DRIFT capacity -uid x->y DRIFT capacity -uid y->absent DRIFT capacity -uid y->x DRIFT capacity -usage_policy_id absent->x DRIFT capacity -usage_policy_id absent->y DRIFT capacity -usage_policy_id x->absent DRIFT capacity -usage_policy_id x->y DRIFT capacity -usage_policy_id y->absent DRIFT capacity -usage_policy_id y->x DRIFT capacity - -=== summary -OK_RECREATE 32 -SUPPRESSED 8 -NOT_OBSERVABLE 6 -DRIFT 118 - -=== not covered: 13 fields under a slice or map -child_instance_refs[*].branch_time -child_instance_refs[*].effective_lsn -child_instance_refs[*].lsn -child_instance_refs[*].name -child_instance_refs[*].uid -custom_tags[*].key -custom_tags[*].value -effective_custom_tags[*].key -effective_custom_tags[*].value -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name +capacity absent x POST_DEPLOY_DRIFT capacity +capacity absent y POST_DEPLOY_DRIFT capacity +capacity x absent SUPPRESSED remote_already_set +capacity x y POST_DEPLOY_DRIFT capacity +capacity y absent SUPPRESSED remote_already_set +capacity y x POST_DEPLOY_DRIFT capacity +creation_time absent x POST_DEPLOY_DRIFT capacity +creation_time absent y POST_DEPLOY_DRIFT capacity +creation_time x absent POST_DEPLOY_DRIFT capacity +creation_time x y POST_DEPLOY_DRIFT capacity +creation_time y absent POST_DEPLOY_DRIFT capacity +creation_time y x POST_DEPLOY_DRIFT capacity +creator absent x POST_DEPLOY_DRIFT capacity +creator absent y POST_DEPLOY_DRIFT capacity +creator x absent POST_DEPLOY_DRIFT capacity +creator x y POST_DEPLOY_DRIFT capacity +creator y absent POST_DEPLOY_DRIFT capacity +creator y x POST_DEPLOY_DRIFT capacity +effective_capacity absent x POST_DEPLOY_DRIFT capacity +effective_capacity absent y POST_DEPLOY_DRIFT capacity +effective_capacity x absent POST_DEPLOY_DRIFT capacity +effective_capacity x y POST_DEPLOY_DRIFT capacity +effective_capacity y absent POST_DEPLOY_DRIFT capacity +effective_capacity y x POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login absent true POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login false absent POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login false true POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity +effective_enable_pg_native_login true false POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries false absent POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity +effective_enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity +effective_node_count 1 2 POST_DEPLOY_DRIFT capacity +effective_node_count 1 absent POST_DEPLOY_DRIFT capacity +effective_node_count 2 1 POST_DEPLOY_DRIFT capacity +effective_node_count 2 absent POST_DEPLOY_DRIFT capacity +effective_node_count absent 1 POST_DEPLOY_DRIFT capacity +effective_node_count absent 2 POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days 1 2 POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days 1 absent POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days 2 1 POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days 2 absent POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days absent 1 POST_DEPLOY_DRIFT capacity +effective_retention_window_in_days absent 2 POST_DEPLOY_DRIFT capacity +effective_stopped absent true POST_DEPLOY_DRIFT capacity +effective_stopped false absent POST_DEPLOY_DRIFT capacity +effective_stopped false true POST_DEPLOY_DRIFT capacity +effective_stopped true absent POST_DEPLOY_DRIFT capacity +effective_stopped true false POST_DEPLOY_DRIFT capacity +effective_usage_policy_id absent x POST_DEPLOY_DRIFT capacity +effective_usage_policy_id absent y POST_DEPLOY_DRIFT capacity +effective_usage_policy_id x absent POST_DEPLOY_DRIFT capacity +effective_usage_policy_id x y POST_DEPLOY_DRIFT capacity +effective_usage_policy_id y absent POST_DEPLOY_DRIFT capacity +effective_usage_policy_id y x POST_DEPLOY_DRIFT capacity +enable_pg_native_login absent true POST_DEPLOY_DRIFT capacity +enable_pg_native_login false absent POST_DEPLOY_DRIFT capacity +enable_pg_native_login false true POST_DEPLOY_DRIFT capacity +enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity +enable_pg_native_login true false POST_DEPLOY_DRIFT capacity +enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity +enable_readable_secondaries false absent POST_DEPLOY_DRIFT capacity +enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity +enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity +enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity +node_count 1 2 POST_DEPLOY_DRIFT capacity +node_count 1 absent POST_DEPLOY_DRIFT capacity +node_count 2 1 POST_DEPLOY_DRIFT capacity +node_count 2 absent POST_DEPLOY_DRIFT capacity +node_count absent 1 POST_DEPLOY_DRIFT capacity +node_count absent 2 POST_DEPLOY_DRIFT capacity +pg_version absent x POST_DEPLOY_DRIFT capacity +pg_version absent y POST_DEPLOY_DRIFT capacity +pg_version x absent POST_DEPLOY_DRIFT capacity +pg_version x y POST_DEPLOY_DRIFT capacity +pg_version y absent POST_DEPLOY_DRIFT capacity +pg_version y x POST_DEPLOY_DRIFT capacity +read_only_dns absent x POST_DEPLOY_DRIFT capacity +read_only_dns absent y POST_DEPLOY_DRIFT capacity +read_only_dns x absent POST_DEPLOY_DRIFT capacity +read_only_dns x y POST_DEPLOY_DRIFT capacity +read_only_dns y absent POST_DEPLOY_DRIFT capacity +read_only_dns y x POST_DEPLOY_DRIFT capacity +read_write_dns absent x POST_DEPLOY_DRIFT capacity +read_write_dns absent y POST_DEPLOY_DRIFT capacity +read_write_dns x absent POST_DEPLOY_DRIFT capacity +read_write_dns x y POST_DEPLOY_DRIFT capacity +read_write_dns y absent POST_DEPLOY_DRIFT capacity +read_write_dns y x POST_DEPLOY_DRIFT capacity +retention_window_in_days 1 2 POST_DEPLOY_DRIFT capacity +retention_window_in_days 1 absent POST_DEPLOY_DRIFT capacity +retention_window_in_days 2 1 POST_DEPLOY_DRIFT capacity +retention_window_in_days 2 absent POST_DEPLOY_DRIFT capacity +retention_window_in_days absent 1 POST_DEPLOY_DRIFT capacity +retention_window_in_days absent 2 POST_DEPLOY_DRIFT capacity +state absent x POST_DEPLOY_DRIFT capacity +state absent y POST_DEPLOY_DRIFT capacity +state x absent POST_DEPLOY_DRIFT capacity +state x y POST_DEPLOY_DRIFT capacity +state y absent POST_DEPLOY_DRIFT capacity +state y x POST_DEPLOY_DRIFT capacity +stopped absent true POST_DEPLOY_DRIFT capacity +stopped false absent POST_DEPLOY_DRIFT capacity +stopped false true POST_DEPLOY_DRIFT capacity +stopped true absent POST_DEPLOY_DRIFT capacity +stopped true false POST_DEPLOY_DRIFT capacity +uid absent x POST_DEPLOY_DRIFT capacity +uid absent y POST_DEPLOY_DRIFT capacity +uid x absent POST_DEPLOY_DRIFT capacity +uid x y POST_DEPLOY_DRIFT capacity +uid y absent POST_DEPLOY_DRIFT capacity +uid y x POST_DEPLOY_DRIFT capacity +usage_policy_id absent x POST_DEPLOY_DRIFT capacity +usage_policy_id absent y POST_DEPLOY_DRIFT capacity +usage_policy_id x absent POST_DEPLOY_DRIFT capacity +usage_policy_id x y POST_DEPLOY_DRIFT capacity +usage_policy_id y absent POST_DEPLOY_DRIFT capacity +usage_policy_id y x POST_DEPLOY_DRIFT capacity diff --git a/bundle/direct/tests/output/experiments.full.txt b/bundle/direct/tests/output/experiments.full.txt new file mode 100644 index 00000000000..7ea68aa3b68 --- /dev/null +++ b/bundle/direct/tests/output/experiments.full.txt @@ -0,0 +1,44 @@ +=== experiment.yml.tmpl +artifact_location absent x OK_RECREATE +artifact_location absent y OK_RECREATE +artifact_location x absent OK_RECREATE +artifact_location x y OK_RECREATE +artifact_location y absent OK_RECREATE +artifact_location y x OK_RECREATE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK +name y x OK +trace_location.uc_trace_location.catalog x y OK_RECREATE +trace_location.uc_trace_location.catalog y x OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix absent x OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix absent y OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix x absent OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix x y OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix y absent OK_RECREATE +trace_location.uc_trace_location.effective_table_prefix y x OK_RECREATE +trace_location.uc_trace_location.schema x y OK_RECREATE +trace_location.uc_trace_location.schema y x OK_RECREATE +trace_location.uc_trace_location.table_prefix absent x OK_RECREATE +trace_location.uc_trace_location.table_prefix absent y OK_RECREATE +trace_location.uc_trace_location.table_prefix x absent OK_RECREATE +trace_location.uc_trace_location.table_prefix x y OK_RECREATE +trace_location.uc_trace_location.table_prefix y absent OK_RECREATE +trace_location.uc_trace_location.table_prefix y x OK_RECREATE + +=== summary +OK 2 +OK_RECREATE 22 +NOT_OBSERVABLE 6 + +=== not covered: 6 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +tags[*].key +tags[*].value diff --git a/bundle/direct/tests/output/experiments.txt b/bundle/direct/tests/output/experiments.txt index f0e2026c7ef..e69de29bb2d 100644 --- a/bundle/direct/tests/output/experiments.txt +++ b/bundle/direct/tests/output/experiments.txt @@ -1,44 +0,0 @@ -=== experiment.yml.tmpl -artifact_location absent->x OK_RECREATE -artifact_location absent->y OK_RECREATE -artifact_location x->absent OK_RECREATE -artifact_location x->y OK_RECREATE -artifact_location y->absent OK_RECREATE -artifact_location y->x OK_RECREATE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK -name y->x OK -trace_location.uc_trace_location.catalog x->y OK_RECREATE -trace_location.uc_trace_location.catalog y->x OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix absent->x OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix absent->y OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix x->absent OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix x->y OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix y->absent OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix y->x OK_RECREATE -trace_location.uc_trace_location.schema x->y OK_RECREATE -trace_location.uc_trace_location.schema y->x OK_RECREATE -trace_location.uc_trace_location.table_prefix absent->x OK_RECREATE -trace_location.uc_trace_location.table_prefix absent->y OK_RECREATE -trace_location.uc_trace_location.table_prefix x->absent OK_RECREATE -trace_location.uc_trace_location.table_prefix x->y OK_RECREATE -trace_location.uc_trace_location.table_prefix y->absent OK_RECREATE -trace_location.uc_trace_location.table_prefix y->x OK_RECREATE - -=== summary -OK 2 -OK_RECREATE 22 -NOT_OBSERVABLE 6 - -=== not covered: 6 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -tags[*].key -tags[*].value diff --git a/bundle/direct/tests/output/external_locations.full.txt b/bundle/direct/tests/output/external_locations.full.txt new file mode 100644 index 00000000000..e2302246bd5 --- /dev/null +++ b/bundle/direct/tests/output/external_locations.full.txt @@ -0,0 +1,263 @@ +=== external_location.yml.tmpl +comment absent x OK +comment absent y OK +comment x absent OK +comment x y OK +comment y absent OK +comment y x OK +credential_name x y OK_RECREATE +credential_name y x OK_RECREATE +effective_enable_file_events absent false SUPPRESSED empty +effective_enable_file_events absent true OK +effective_enable_file_events false absent SUPPRESSED empty +effective_enable_file_events false true OK +effective_enable_file_events true absent OK +effective_enable_file_events true false OK +effective_file_event_queue.managed_aqs.managed_resource_id absent x OK +effective_file_event_queue.managed_aqs.managed_resource_id absent y OK +effective_file_event_queue.managed_aqs.managed_resource_id x absent OK +effective_file_event_queue.managed_aqs.managed_resource_id x y OK +effective_file_event_queue.managed_aqs.managed_resource_id y absent OK +effective_file_event_queue.managed_aqs.managed_resource_id y x OK +effective_file_event_queue.managed_aqs.queue_url absent x OK +effective_file_event_queue.managed_aqs.queue_url absent y OK +effective_file_event_queue.managed_aqs.queue_url x absent OK +effective_file_event_queue.managed_aqs.queue_url x y OK +effective_file_event_queue.managed_aqs.queue_url y absent OK +effective_file_event_queue.managed_aqs.queue_url y x OK +effective_file_event_queue.managed_aqs.resource_group absent x OK +effective_file_event_queue.managed_aqs.resource_group absent y OK +effective_file_event_queue.managed_aqs.resource_group x absent OK +effective_file_event_queue.managed_aqs.resource_group x y OK +effective_file_event_queue.managed_aqs.resource_group y absent OK +effective_file_event_queue.managed_aqs.resource_group y x OK +effective_file_event_queue.managed_aqs.subscription_id absent x OK +effective_file_event_queue.managed_aqs.subscription_id absent y OK +effective_file_event_queue.managed_aqs.subscription_id x absent OK +effective_file_event_queue.managed_aqs.subscription_id x y OK +effective_file_event_queue.managed_aqs.subscription_id y absent OK +effective_file_event_queue.managed_aqs.subscription_id y x OK +effective_file_event_queue.managed_pubsub.managed_resource_id absent x OK +effective_file_event_queue.managed_pubsub.managed_resource_id absent y OK +effective_file_event_queue.managed_pubsub.managed_resource_id x absent OK +effective_file_event_queue.managed_pubsub.managed_resource_id x y OK +effective_file_event_queue.managed_pubsub.managed_resource_id y absent OK +effective_file_event_queue.managed_pubsub.managed_resource_id y x OK +effective_file_event_queue.managed_pubsub.subscription_name absent x OK +effective_file_event_queue.managed_pubsub.subscription_name absent y OK +effective_file_event_queue.managed_pubsub.subscription_name x absent OK +effective_file_event_queue.managed_pubsub.subscription_name x y OK +effective_file_event_queue.managed_pubsub.subscription_name y absent OK +effective_file_event_queue.managed_pubsub.subscription_name y x OK +effective_file_event_queue.managed_sqs.managed_resource_id absent x OK +effective_file_event_queue.managed_sqs.managed_resource_id absent y OK +effective_file_event_queue.managed_sqs.managed_resource_id x absent OK +effective_file_event_queue.managed_sqs.managed_resource_id x y OK +effective_file_event_queue.managed_sqs.managed_resource_id y absent OK +effective_file_event_queue.managed_sqs.managed_resource_id y x OK +effective_file_event_queue.managed_sqs.queue_url absent x OK +effective_file_event_queue.managed_sqs.queue_url absent y OK +effective_file_event_queue.managed_sqs.queue_url x absent OK +effective_file_event_queue.managed_sqs.queue_url x y OK +effective_file_event_queue.managed_sqs.queue_url y absent OK +effective_file_event_queue.managed_sqs.queue_url y x OK +effective_file_event_queue.provided_aqs.managed_resource_id absent x OK +effective_file_event_queue.provided_aqs.managed_resource_id absent y OK +effective_file_event_queue.provided_aqs.managed_resource_id x absent OK +effective_file_event_queue.provided_aqs.managed_resource_id x y OK +effective_file_event_queue.provided_aqs.managed_resource_id y absent OK +effective_file_event_queue.provided_aqs.managed_resource_id y x OK +effective_file_event_queue.provided_aqs.queue_url absent x OK +effective_file_event_queue.provided_aqs.queue_url absent y OK +effective_file_event_queue.provided_aqs.queue_url x absent OK +effective_file_event_queue.provided_aqs.queue_url x y OK +effective_file_event_queue.provided_aqs.queue_url y absent OK +effective_file_event_queue.provided_aqs.queue_url y x OK +effective_file_event_queue.provided_aqs.resource_group absent x OK +effective_file_event_queue.provided_aqs.resource_group absent y OK +effective_file_event_queue.provided_aqs.resource_group x absent OK +effective_file_event_queue.provided_aqs.resource_group x y OK +effective_file_event_queue.provided_aqs.resource_group y absent OK +effective_file_event_queue.provided_aqs.resource_group y x OK +effective_file_event_queue.provided_aqs.subscription_id absent x OK +effective_file_event_queue.provided_aqs.subscription_id absent y OK +effective_file_event_queue.provided_aqs.subscription_id x absent OK +effective_file_event_queue.provided_aqs.subscription_id x y OK +effective_file_event_queue.provided_aqs.subscription_id y absent OK +effective_file_event_queue.provided_aqs.subscription_id y x OK +effective_file_event_queue.provided_pubsub.managed_resource_id absent x OK +effective_file_event_queue.provided_pubsub.managed_resource_id absent y OK +effective_file_event_queue.provided_pubsub.managed_resource_id x absent OK +effective_file_event_queue.provided_pubsub.managed_resource_id x y OK +effective_file_event_queue.provided_pubsub.managed_resource_id y absent OK +effective_file_event_queue.provided_pubsub.managed_resource_id y x OK +effective_file_event_queue.provided_pubsub.subscription_name absent x OK +effective_file_event_queue.provided_pubsub.subscription_name absent y OK +effective_file_event_queue.provided_pubsub.subscription_name x absent OK +effective_file_event_queue.provided_pubsub.subscription_name x y OK +effective_file_event_queue.provided_pubsub.subscription_name y absent OK +effective_file_event_queue.provided_pubsub.subscription_name y x OK +effective_file_event_queue.provided_sqs.managed_resource_id absent x OK +effective_file_event_queue.provided_sqs.managed_resource_id absent y OK +effective_file_event_queue.provided_sqs.managed_resource_id x absent OK +effective_file_event_queue.provided_sqs.managed_resource_id x y OK +effective_file_event_queue.provided_sqs.managed_resource_id y absent OK +effective_file_event_queue.provided_sqs.managed_resource_id y x OK +effective_file_event_queue.provided_sqs.queue_url absent x OK +effective_file_event_queue.provided_sqs.queue_url absent y OK +effective_file_event_queue.provided_sqs.queue_url x absent OK +effective_file_event_queue.provided_sqs.queue_url x y OK +effective_file_event_queue.provided_sqs.queue_url y absent OK +effective_file_event_queue.provided_sqs.queue_url y x OK +enable_file_events absent false SUPPRESSED empty +enable_file_events absent true OK +enable_file_events false absent SUPPRESSED empty +enable_file_events false true OK +enable_file_events true absent OK +enable_file_events true false OK +encryption_details.sse_encryption_details.algorithm absent x OK_RECREATE +encryption_details.sse_encryption_details.algorithm absent y OK_RECREATE +encryption_details.sse_encryption_details.algorithm x absent OK_RECREATE +encryption_details.sse_encryption_details.algorithm x y OK_RECREATE +encryption_details.sse_encryption_details.algorithm y absent OK_RECREATE +encryption_details.sse_encryption_details.algorithm y x OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn absent x OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn absent y OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn x absent OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn x y OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn y absent OK_RECREATE +encryption_details.sse_encryption_details.aws_kms_key_arn y x OK_RECREATE +fallback absent false SUPPRESSED empty +fallback absent true OK +fallback false absent SUPPRESSED empty +fallback false true OK +fallback true absent OK +fallback true false OK +file_event_queue.managed_aqs.managed_resource_id absent x OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id absent y OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id x absent OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id x y OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id y absent OK_RECREATE +file_event_queue.managed_aqs.managed_resource_id y x OK_RECREATE +file_event_queue.managed_aqs.queue_url absent x OK_RECREATE +file_event_queue.managed_aqs.queue_url absent y OK_RECREATE +file_event_queue.managed_aqs.queue_url x absent OK_RECREATE +file_event_queue.managed_aqs.queue_url x y OK_RECREATE +file_event_queue.managed_aqs.queue_url y absent OK_RECREATE +file_event_queue.managed_aqs.queue_url y x OK_RECREATE +file_event_queue.managed_aqs.resource_group absent x OK_RECREATE +file_event_queue.managed_aqs.resource_group absent y OK_RECREATE +file_event_queue.managed_aqs.resource_group x absent OK_RECREATE +file_event_queue.managed_aqs.resource_group x y OK_RECREATE +file_event_queue.managed_aqs.resource_group y absent OK_RECREATE +file_event_queue.managed_aqs.resource_group y x OK_RECREATE +file_event_queue.managed_aqs.subscription_id absent x OK_RECREATE +file_event_queue.managed_aqs.subscription_id absent y OK_RECREATE +file_event_queue.managed_aqs.subscription_id x absent OK_RECREATE +file_event_queue.managed_aqs.subscription_id x y OK_RECREATE +file_event_queue.managed_aqs.subscription_id y absent OK_RECREATE +file_event_queue.managed_aqs.subscription_id y x OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id absent x OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id absent y OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id x absent OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id x y OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id y absent OK_RECREATE +file_event_queue.managed_pubsub.managed_resource_id y x OK_RECREATE +file_event_queue.managed_pubsub.subscription_name absent x OK_RECREATE +file_event_queue.managed_pubsub.subscription_name absent y OK_RECREATE +file_event_queue.managed_pubsub.subscription_name x absent OK_RECREATE +file_event_queue.managed_pubsub.subscription_name x y OK_RECREATE +file_event_queue.managed_pubsub.subscription_name y absent OK_RECREATE +file_event_queue.managed_pubsub.subscription_name y x OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id absent x OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id absent y OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id x absent OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id x y OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id y absent OK_RECREATE +file_event_queue.managed_sqs.managed_resource_id y x OK_RECREATE +file_event_queue.managed_sqs.queue_url absent x OK_RECREATE +file_event_queue.managed_sqs.queue_url absent y OK_RECREATE +file_event_queue.managed_sqs.queue_url x absent OK_RECREATE +file_event_queue.managed_sqs.queue_url x y OK_RECREATE +file_event_queue.managed_sqs.queue_url y absent OK_RECREATE +file_event_queue.managed_sqs.queue_url y x OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id absent x OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id absent y OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id x absent OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id x y OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id y absent OK_RECREATE +file_event_queue.provided_aqs.managed_resource_id y x OK_RECREATE +file_event_queue.provided_aqs.queue_url absent x OK_RECREATE +file_event_queue.provided_aqs.queue_url absent y OK_RECREATE +file_event_queue.provided_aqs.queue_url x absent OK_RECREATE +file_event_queue.provided_aqs.queue_url x y OK_RECREATE +file_event_queue.provided_aqs.queue_url y absent OK_RECREATE +file_event_queue.provided_aqs.queue_url y x OK_RECREATE +file_event_queue.provided_aqs.resource_group absent x OK_RECREATE +file_event_queue.provided_aqs.resource_group absent y OK_RECREATE +file_event_queue.provided_aqs.resource_group x absent OK_RECREATE +file_event_queue.provided_aqs.resource_group x y OK_RECREATE +file_event_queue.provided_aqs.resource_group y absent OK_RECREATE +file_event_queue.provided_aqs.resource_group y x OK_RECREATE +file_event_queue.provided_aqs.subscription_id absent x OK_RECREATE +file_event_queue.provided_aqs.subscription_id absent y OK_RECREATE +file_event_queue.provided_aqs.subscription_id x absent OK_RECREATE +file_event_queue.provided_aqs.subscription_id x y OK_RECREATE +file_event_queue.provided_aqs.subscription_id y absent OK_RECREATE +file_event_queue.provided_aqs.subscription_id y x OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id absent x OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id absent y OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id x absent OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id x y OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id y absent OK_RECREATE +file_event_queue.provided_pubsub.managed_resource_id y x OK_RECREATE +file_event_queue.provided_pubsub.subscription_name absent x OK_RECREATE +file_event_queue.provided_pubsub.subscription_name absent y OK_RECREATE +file_event_queue.provided_pubsub.subscription_name x absent OK_RECREATE +file_event_queue.provided_pubsub.subscription_name x y OK_RECREATE +file_event_queue.provided_pubsub.subscription_name y absent OK_RECREATE +file_event_queue.provided_pubsub.subscription_name y x OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id absent x OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id absent y OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id x absent OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id x y OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id y absent OK_RECREATE +file_event_queue.provided_sqs.managed_resource_id y x OK_RECREATE +file_event_queue.provided_sqs.queue_url absent x OK_RECREATE +file_event_queue.provided_sqs.queue_url absent y OK_RECREATE +file_event_queue.provided_sqs.queue_url x absent OK_RECREATE +file_event_queue.provided_sqs.queue_url x y OK_RECREATE +file_event_queue.provided_sqs.queue_url y absent OK_RECREATE +file_event_queue.provided_sqs.queue_url y x OK_RECREATE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK +name y x OK +read_only absent false SUPPRESSED empty +read_only absent true OK +read_only false absent SUPPRESSED empty +read_only false true OK +read_only true absent OK +read_only true false OK +skip_validation absent false SUPPRESSED empty +skip_validation absent true OK +skip_validation false absent SUPPRESSED empty +skip_validation false true OK +skip_validation true absent OK +skip_validation true false OK +url x y OK +url y x OK + +=== summary +OK 126 +OK_RECREATE 110 +SUPPRESSED 10 +NOT_OBSERVABLE 6 + +=== not covered: 2 fields under a slice or map +grants[*].principal +grants[*].privileges[*] diff --git a/bundle/direct/tests/output/external_locations.txt b/bundle/direct/tests/output/external_locations.txt index ec72fc0eb1e..e69de29bb2d 100644 --- a/bundle/direct/tests/output/external_locations.txt +++ b/bundle/direct/tests/output/external_locations.txt @@ -1,263 +0,0 @@ -=== external_location.yml.tmpl -comment absent->x OK -comment absent->y OK -comment x->absent OK -comment x->y OK -comment y->absent OK -comment y->x OK -credential_name x->y OK_RECREATE -credential_name y->x OK_RECREATE -effective_enable_file_events absent->false SUPPRESSED empty -effective_enable_file_events absent->true OK -effective_enable_file_events false->absent SUPPRESSED empty -effective_enable_file_events false->true OK -effective_enable_file_events true->absent OK -effective_enable_file_events true->false OK -effective_file_event_queue.managed_aqs.managed_resource_id absent->x OK -effective_file_event_queue.managed_aqs.managed_resource_id absent->y OK -effective_file_event_queue.managed_aqs.managed_resource_id x->absent OK -effective_file_event_queue.managed_aqs.managed_resource_id x->y OK -effective_file_event_queue.managed_aqs.managed_resource_id y->absent OK -effective_file_event_queue.managed_aqs.managed_resource_id y->x OK -effective_file_event_queue.managed_aqs.queue_url absent->x OK -effective_file_event_queue.managed_aqs.queue_url absent->y OK -effective_file_event_queue.managed_aqs.queue_url x->absent OK -effective_file_event_queue.managed_aqs.queue_url x->y OK -effective_file_event_queue.managed_aqs.queue_url y->absent OK -effective_file_event_queue.managed_aqs.queue_url y->x OK -effective_file_event_queue.managed_aqs.resource_group absent->x OK -effective_file_event_queue.managed_aqs.resource_group absent->y OK -effective_file_event_queue.managed_aqs.resource_group x->absent OK -effective_file_event_queue.managed_aqs.resource_group x->y OK -effective_file_event_queue.managed_aqs.resource_group y->absent OK -effective_file_event_queue.managed_aqs.resource_group y->x OK -effective_file_event_queue.managed_aqs.subscription_id absent->x OK -effective_file_event_queue.managed_aqs.subscription_id absent->y OK -effective_file_event_queue.managed_aqs.subscription_id x->absent OK -effective_file_event_queue.managed_aqs.subscription_id x->y OK -effective_file_event_queue.managed_aqs.subscription_id y->absent OK -effective_file_event_queue.managed_aqs.subscription_id y->x OK -effective_file_event_queue.managed_pubsub.managed_resource_id absent->x OK -effective_file_event_queue.managed_pubsub.managed_resource_id absent->y OK -effective_file_event_queue.managed_pubsub.managed_resource_id x->absent OK -effective_file_event_queue.managed_pubsub.managed_resource_id x->y OK -effective_file_event_queue.managed_pubsub.managed_resource_id y->absent OK -effective_file_event_queue.managed_pubsub.managed_resource_id y->x OK -effective_file_event_queue.managed_pubsub.subscription_name absent->x OK -effective_file_event_queue.managed_pubsub.subscription_name absent->y OK -effective_file_event_queue.managed_pubsub.subscription_name x->absent OK -effective_file_event_queue.managed_pubsub.subscription_name x->y OK -effective_file_event_queue.managed_pubsub.subscription_name y->absent OK -effective_file_event_queue.managed_pubsub.subscription_name y->x OK -effective_file_event_queue.managed_sqs.managed_resource_id absent->x OK -effective_file_event_queue.managed_sqs.managed_resource_id absent->y OK -effective_file_event_queue.managed_sqs.managed_resource_id x->absent OK -effective_file_event_queue.managed_sqs.managed_resource_id x->y OK -effective_file_event_queue.managed_sqs.managed_resource_id y->absent OK -effective_file_event_queue.managed_sqs.managed_resource_id y->x OK -effective_file_event_queue.managed_sqs.queue_url absent->x OK -effective_file_event_queue.managed_sqs.queue_url absent->y OK -effective_file_event_queue.managed_sqs.queue_url x->absent OK -effective_file_event_queue.managed_sqs.queue_url x->y OK -effective_file_event_queue.managed_sqs.queue_url y->absent OK -effective_file_event_queue.managed_sqs.queue_url y->x OK -effective_file_event_queue.provided_aqs.managed_resource_id absent->x OK -effective_file_event_queue.provided_aqs.managed_resource_id absent->y OK -effective_file_event_queue.provided_aqs.managed_resource_id x->absent OK -effective_file_event_queue.provided_aqs.managed_resource_id x->y OK -effective_file_event_queue.provided_aqs.managed_resource_id y->absent OK -effective_file_event_queue.provided_aqs.managed_resource_id y->x OK -effective_file_event_queue.provided_aqs.queue_url absent->x OK -effective_file_event_queue.provided_aqs.queue_url absent->y OK -effective_file_event_queue.provided_aqs.queue_url x->absent OK -effective_file_event_queue.provided_aqs.queue_url x->y OK -effective_file_event_queue.provided_aqs.queue_url y->absent OK -effective_file_event_queue.provided_aqs.queue_url y->x OK -effective_file_event_queue.provided_aqs.resource_group absent->x OK -effective_file_event_queue.provided_aqs.resource_group absent->y OK -effective_file_event_queue.provided_aqs.resource_group x->absent OK -effective_file_event_queue.provided_aqs.resource_group x->y OK -effective_file_event_queue.provided_aqs.resource_group y->absent OK -effective_file_event_queue.provided_aqs.resource_group y->x OK -effective_file_event_queue.provided_aqs.subscription_id absent->x OK -effective_file_event_queue.provided_aqs.subscription_id absent->y OK -effective_file_event_queue.provided_aqs.subscription_id x->absent OK -effective_file_event_queue.provided_aqs.subscription_id x->y OK -effective_file_event_queue.provided_aqs.subscription_id y->absent OK -effective_file_event_queue.provided_aqs.subscription_id y->x OK -effective_file_event_queue.provided_pubsub.managed_resource_id absent->x OK -effective_file_event_queue.provided_pubsub.managed_resource_id absent->y OK -effective_file_event_queue.provided_pubsub.managed_resource_id x->absent OK -effective_file_event_queue.provided_pubsub.managed_resource_id x->y OK -effective_file_event_queue.provided_pubsub.managed_resource_id y->absent OK -effective_file_event_queue.provided_pubsub.managed_resource_id y->x OK -effective_file_event_queue.provided_pubsub.subscription_name absent->x OK -effective_file_event_queue.provided_pubsub.subscription_name absent->y OK -effective_file_event_queue.provided_pubsub.subscription_name x->absent OK -effective_file_event_queue.provided_pubsub.subscription_name x->y OK -effective_file_event_queue.provided_pubsub.subscription_name y->absent OK -effective_file_event_queue.provided_pubsub.subscription_name y->x OK -effective_file_event_queue.provided_sqs.managed_resource_id absent->x OK -effective_file_event_queue.provided_sqs.managed_resource_id absent->y OK -effective_file_event_queue.provided_sqs.managed_resource_id x->absent OK -effective_file_event_queue.provided_sqs.managed_resource_id x->y OK -effective_file_event_queue.provided_sqs.managed_resource_id y->absent OK -effective_file_event_queue.provided_sqs.managed_resource_id y->x OK -effective_file_event_queue.provided_sqs.queue_url absent->x OK -effective_file_event_queue.provided_sqs.queue_url absent->y OK -effective_file_event_queue.provided_sqs.queue_url x->absent OK -effective_file_event_queue.provided_sqs.queue_url x->y OK -effective_file_event_queue.provided_sqs.queue_url y->absent OK -effective_file_event_queue.provided_sqs.queue_url y->x OK -enable_file_events absent->false SUPPRESSED empty -enable_file_events absent->true OK -enable_file_events false->absent SUPPRESSED empty -enable_file_events false->true OK -enable_file_events true->absent OK -enable_file_events true->false OK -encryption_details.sse_encryption_details.algorithm absent->x OK_RECREATE -encryption_details.sse_encryption_details.algorithm absent->y OK_RECREATE -encryption_details.sse_encryption_details.algorithm x->absent OK_RECREATE -encryption_details.sse_encryption_details.algorithm x->y OK_RECREATE -encryption_details.sse_encryption_details.algorithm y->absent OK_RECREATE -encryption_details.sse_encryption_details.algorithm y->x OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn absent->x OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn absent->y OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn x->absent OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn x->y OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn y->absent OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn y->x OK_RECREATE -fallback absent->false SUPPRESSED empty -fallback absent->true OK -fallback false->absent SUPPRESSED empty -fallback false->true OK -fallback true->absent OK -fallback true->false OK -file_event_queue.managed_aqs.managed_resource_id absent->x OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id absent->y OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id x->absent OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id x->y OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id y->absent OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id y->x OK_RECREATE -file_event_queue.managed_aqs.queue_url absent->x OK_RECREATE -file_event_queue.managed_aqs.queue_url absent->y OK_RECREATE -file_event_queue.managed_aqs.queue_url x->absent OK_RECREATE -file_event_queue.managed_aqs.queue_url x->y OK_RECREATE -file_event_queue.managed_aqs.queue_url y->absent OK_RECREATE -file_event_queue.managed_aqs.queue_url y->x OK_RECREATE -file_event_queue.managed_aqs.resource_group absent->x OK_RECREATE -file_event_queue.managed_aqs.resource_group absent->y OK_RECREATE -file_event_queue.managed_aqs.resource_group x->absent OK_RECREATE -file_event_queue.managed_aqs.resource_group x->y OK_RECREATE -file_event_queue.managed_aqs.resource_group y->absent OK_RECREATE -file_event_queue.managed_aqs.resource_group y->x OK_RECREATE -file_event_queue.managed_aqs.subscription_id absent->x OK_RECREATE -file_event_queue.managed_aqs.subscription_id absent->y OK_RECREATE -file_event_queue.managed_aqs.subscription_id x->absent OK_RECREATE -file_event_queue.managed_aqs.subscription_id x->y OK_RECREATE -file_event_queue.managed_aqs.subscription_id y->absent OK_RECREATE -file_event_queue.managed_aqs.subscription_id y->x OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id absent->x OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id absent->y OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id x->absent OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id x->y OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id y->absent OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id y->x OK_RECREATE -file_event_queue.managed_pubsub.subscription_name absent->x OK_RECREATE -file_event_queue.managed_pubsub.subscription_name absent->y OK_RECREATE -file_event_queue.managed_pubsub.subscription_name x->absent OK_RECREATE -file_event_queue.managed_pubsub.subscription_name x->y OK_RECREATE -file_event_queue.managed_pubsub.subscription_name y->absent OK_RECREATE -file_event_queue.managed_pubsub.subscription_name y->x OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id absent->x OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id absent->y OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id x->absent OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id x->y OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id y->absent OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id y->x OK_RECREATE -file_event_queue.managed_sqs.queue_url absent->x OK_RECREATE -file_event_queue.managed_sqs.queue_url absent->y OK_RECREATE -file_event_queue.managed_sqs.queue_url x->absent OK_RECREATE -file_event_queue.managed_sqs.queue_url x->y OK_RECREATE -file_event_queue.managed_sqs.queue_url y->absent OK_RECREATE -file_event_queue.managed_sqs.queue_url y->x OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id absent->x OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id absent->y OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id x->absent OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id x->y OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id y->absent OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id y->x OK_RECREATE -file_event_queue.provided_aqs.queue_url absent->x OK_RECREATE -file_event_queue.provided_aqs.queue_url absent->y OK_RECREATE -file_event_queue.provided_aqs.queue_url x->absent OK_RECREATE -file_event_queue.provided_aqs.queue_url x->y OK_RECREATE -file_event_queue.provided_aqs.queue_url y->absent OK_RECREATE -file_event_queue.provided_aqs.queue_url y->x OK_RECREATE -file_event_queue.provided_aqs.resource_group absent->x OK_RECREATE -file_event_queue.provided_aqs.resource_group absent->y OK_RECREATE -file_event_queue.provided_aqs.resource_group x->absent OK_RECREATE -file_event_queue.provided_aqs.resource_group x->y OK_RECREATE -file_event_queue.provided_aqs.resource_group y->absent OK_RECREATE -file_event_queue.provided_aqs.resource_group y->x OK_RECREATE -file_event_queue.provided_aqs.subscription_id absent->x OK_RECREATE -file_event_queue.provided_aqs.subscription_id absent->y OK_RECREATE -file_event_queue.provided_aqs.subscription_id x->absent OK_RECREATE -file_event_queue.provided_aqs.subscription_id x->y OK_RECREATE -file_event_queue.provided_aqs.subscription_id y->absent OK_RECREATE -file_event_queue.provided_aqs.subscription_id y->x OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id absent->x OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id absent->y OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id x->absent OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id x->y OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id y->absent OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id y->x OK_RECREATE -file_event_queue.provided_pubsub.subscription_name absent->x OK_RECREATE -file_event_queue.provided_pubsub.subscription_name absent->y OK_RECREATE -file_event_queue.provided_pubsub.subscription_name x->absent OK_RECREATE -file_event_queue.provided_pubsub.subscription_name x->y OK_RECREATE -file_event_queue.provided_pubsub.subscription_name y->absent OK_RECREATE -file_event_queue.provided_pubsub.subscription_name y->x OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id absent->x OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id absent->y OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id x->absent OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id x->y OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id y->absent OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id y->x OK_RECREATE -file_event_queue.provided_sqs.queue_url absent->x OK_RECREATE -file_event_queue.provided_sqs.queue_url absent->y OK_RECREATE -file_event_queue.provided_sqs.queue_url x->absent OK_RECREATE -file_event_queue.provided_sqs.queue_url x->y OK_RECREATE -file_event_queue.provided_sqs.queue_url y->absent OK_RECREATE -file_event_queue.provided_sqs.queue_url y->x OK_RECREATE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK -name y->x OK -read_only absent->false SUPPRESSED empty -read_only absent->true OK -read_only false->absent SUPPRESSED empty -read_only false->true OK -read_only true->absent OK -read_only true->false OK -skip_validation absent->false SUPPRESSED empty -skip_validation absent->true OK -skip_validation false->absent SUPPRESSED empty -skip_validation false->true OK -skip_validation true->absent OK -skip_validation true->false OK -url x->y OK -url y->x OK - -=== summary -OK 126 -OK_RECREATE 110 -SUPPRESSED 10 -NOT_OBSERVABLE 6 - -=== not covered: 2 fields under a slice or map -grants[*].principal -grants[*].privileges[*] diff --git a/bundle/direct/tests/output/genie_spaces.full.txt b/bundle/direct/tests/output/genie_spaces.full.txt new file mode 100644 index 00000000000..39ff81224f2 --- /dev/null +++ b/bundle/direct/tests/output/genie_spaces.full.txt @@ -0,0 +1,55 @@ +=== genie_space.yml.tmpl +description absent x OK +description absent y OK +description x absent POST_DEPLOY_DRIFT description +description x y OK +description y absent POST_DEPLOY_DRIFT description +description y x OK +etag absent x SUPPRESSED custom +etag absent y SUPPRESSED custom +etag x absent SUPPRESSED custom +etag x y SUPPRESSED custom +etag y absent SUPPRESSED custom +etag y x SUPPRESSED custom +file_path absent x NOT_OBSERVABLE +file_path absent y NOT_OBSERVABLE +file_path x absent NOT_OBSERVABLE +file_path x y NOT_OBSERVABLE +file_path y absent NOT_OBSERVABLE +file_path y x NOT_OBSERVABLE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +parent_path absent x POST_DEPLOY_DRIFT parent_path +parent_path absent y POST_DEPLOY_DRIFT parent_path +parent_path x absent POST_DEPLOY_DRIFT parent_path +parent_path x y POST_DEPLOY_DRIFT parent_path +parent_path y absent POST_DEPLOY_DRIFT parent_path +parent_path y x POST_DEPLOY_DRIFT parent_path +title absent x OK +title absent y OK +title x absent POST_DEPLOY_DRIFT title +title x y OK +title y absent POST_DEPLOY_DRIFT title +title y x OK +warehouse_id absent x OK +warehouse_id absent y OK +warehouse_id x absent POST_DEPLOY_DRIFT warehouse_id +warehouse_id x y OK +warehouse_id y absent POST_DEPLOY_DRIFT warehouse_id +warehouse_id y x OK + +=== summary +OK 12 +SUPPRESSED 6 +NOT_OBSERVABLE 12 +POST_DEPLOY_DRIFT 12 + +=== not covered: 4 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index 2f92d9755d1..ddf8efdc1c4 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,55 +1,19 @@ === genie_space.yml.tmpl -description absent->x OK -description absent->y OK -description x->absent DRIFT description -description x->y OK -description y->absent DRIFT description -description y->x OK -etag absent->x SUPPRESSED custom -etag absent->y SUPPRESSED custom -etag x->absent SUPPRESSED custom -etag x->y SUPPRESSED custom -etag y->absent SUPPRESSED custom -etag y->x SUPPRESSED custom -file_path absent->x NOT_OBSERVABLE -file_path absent->y NOT_OBSERVABLE -file_path x->absent NOT_OBSERVABLE -file_path x->y NOT_OBSERVABLE -file_path y->absent NOT_OBSERVABLE -file_path y->x NOT_OBSERVABLE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -parent_path absent->x DRIFT parent_path -parent_path absent->y DRIFT parent_path -parent_path x->absent DRIFT parent_path -parent_path x->y DRIFT parent_path -parent_path y->absent DRIFT parent_path -parent_path y->x DRIFT parent_path -title absent->x OK -title absent->y OK -title x->absent DRIFT title -title x->y OK -title y->absent DRIFT title -title y->x OK -warehouse_id absent->x OK -warehouse_id absent->y OK -warehouse_id x->absent DRIFT warehouse_id -warehouse_id x->y OK -warehouse_id y->absent DRIFT warehouse_id -warehouse_id y->x OK - -=== summary -OK 12 -SUPPRESSED 6 -NOT_OBSERVABLE 12 -DRIFT 12 - -=== not covered: 4 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name +description x absent POST_DEPLOY_DRIFT description +description y absent POST_DEPLOY_DRIFT description +etag absent x SUPPRESSED custom +etag absent y SUPPRESSED custom +etag x absent SUPPRESSED custom +etag x y SUPPRESSED custom +etag y absent SUPPRESSED custom +etag y x SUPPRESSED custom +parent_path absent x POST_DEPLOY_DRIFT parent_path +parent_path absent y POST_DEPLOY_DRIFT parent_path +parent_path x absent POST_DEPLOY_DRIFT parent_path +parent_path x y POST_DEPLOY_DRIFT parent_path +parent_path y absent POST_DEPLOY_DRIFT parent_path +parent_path y x POST_DEPLOY_DRIFT parent_path +title x absent POST_DEPLOY_DRIFT title +title y absent POST_DEPLOY_DRIFT title +warehouse_id x absent POST_DEPLOY_DRIFT warehouse_id +warehouse_id y absent POST_DEPLOY_DRIFT warehouse_id diff --git a/bundle/direct/tests/output/instance_pools.full.txt b/bundle/direct/tests/output/instance_pools.full.txt new file mode 100644 index 00000000000..e3621fca80f --- /dev/null +++ b/bundle/direct/tests/output/instance_pools.full.txt @@ -0,0 +1,162 @@ +=== instance_pool.yml.tmpl +aws_attributes.availability absent x OK +aws_attributes.availability absent y OK +aws_attributes.availability x absent OK +aws_attributes.availability x y OK +aws_attributes.availability y absent OK +aws_attributes.availability y x OK +aws_attributes.instance_profile_arn absent x OK +aws_attributes.instance_profile_arn absent y OK +aws_attributes.instance_profile_arn x absent OK +aws_attributes.instance_profile_arn x y OK +aws_attributes.instance_profile_arn y absent OK +aws_attributes.instance_profile_arn y x OK +aws_attributes.spot_bid_price_percent 1 2 OK +aws_attributes.spot_bid_price_percent 1 absent OK +aws_attributes.spot_bid_price_percent 2 1 OK +aws_attributes.spot_bid_price_percent 2 absent OK +aws_attributes.spot_bid_price_percent absent 1 OK +aws_attributes.spot_bid_price_percent absent 2 OK +aws_attributes.zone_id absent x OK +aws_attributes.zone_id absent y OK +aws_attributes.zone_id x absent OK +aws_attributes.zone_id x y OK +aws_attributes.zone_id y absent OK +aws_attributes.zone_id y x OK +azure_attributes.availability absent x OK +azure_attributes.availability absent y OK +azure_attributes.availability x absent OK +azure_attributes.availability x y OK +azure_attributes.availability y absent OK +azure_attributes.availability y x OK +azure_attributes.capacity_reservation_group absent x OK +azure_attributes.capacity_reservation_group absent y OK +azure_attributes.capacity_reservation_group x absent OK +azure_attributes.capacity_reservation_group x y OK +azure_attributes.capacity_reservation_group y absent OK +azure_attributes.capacity_reservation_group y x OK +azure_attributes.spot_bid_max_price 1 2 OK +azure_attributes.spot_bid_max_price 1 absent OK +azure_attributes.spot_bid_max_price 2 1 OK +azure_attributes.spot_bid_max_price 2 absent OK +azure_attributes.spot_bid_max_price absent 1 OK +azure_attributes.spot_bid_max_price absent 2 OK +disk_spec.disk_count 1 2 OK_RECREATE +disk_spec.disk_count 1 absent OK_RECREATE +disk_spec.disk_count 2 1 OK_RECREATE +disk_spec.disk_count 2 absent OK_RECREATE +disk_spec.disk_count absent 1 OK_RECREATE +disk_spec.disk_count absent 2 OK_RECREATE +disk_spec.disk_iops 1 2 OK_RECREATE +disk_spec.disk_iops 1 absent OK_RECREATE +disk_spec.disk_iops 2 1 OK_RECREATE +disk_spec.disk_iops 2 absent OK_RECREATE +disk_spec.disk_iops absent 1 OK_RECREATE +disk_spec.disk_iops absent 2 OK_RECREATE +disk_spec.disk_size 1 2 OK_RECREATE +disk_spec.disk_size 1 absent OK_RECREATE +disk_spec.disk_size 2 1 OK_RECREATE +disk_spec.disk_size 2 absent OK_RECREATE +disk_spec.disk_size absent 1 OK_RECREATE +disk_spec.disk_size absent 2 OK_RECREATE +disk_spec.disk_throughput 1 2 OK_RECREATE +disk_spec.disk_throughput 1 absent OK_RECREATE +disk_spec.disk_throughput 2 1 OK_RECREATE +disk_spec.disk_throughput 2 absent OK_RECREATE +disk_spec.disk_throughput absent 1 OK_RECREATE +disk_spec.disk_throughput absent 2 OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type absent x OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type absent y OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type x absent OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type x y OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type y absent OK_RECREATE +disk_spec.disk_type.azure_disk_volume_type y x OK_RECREATE +disk_spec.disk_type.ebs_volume_type absent x OK_RECREATE +disk_spec.disk_type.ebs_volume_type absent y OK_RECREATE +disk_spec.disk_type.ebs_volume_type x absent OK_RECREATE +disk_spec.disk_type.ebs_volume_type x y OK_RECREATE +disk_spec.disk_type.ebs_volume_type y absent OK_RECREATE +disk_spec.disk_type.ebs_volume_type y x OK_RECREATE +enable_elastic_disk absent false SUPPRESSED empty +enable_elastic_disk absent true POST_DEPLOY_DRIFT enable_elastic_disk +enable_elastic_disk false absent SUPPRESSED remote_already_set +enable_elastic_disk false true POST_DEPLOY_DRIFT enable_elastic_disk +enable_elastic_disk true absent SUPPRESSED remote_already_set +enable_elastic_disk true false OK +gcp_attributes.gcp_availability absent x OK +gcp_attributes.gcp_availability absent y OK +gcp_attributes.gcp_availability x absent OK +gcp_attributes.gcp_availability x y OK +gcp_attributes.gcp_availability y absent OK +gcp_attributes.gcp_availability y x OK +gcp_attributes.local_ssd_count 1 2 OK +gcp_attributes.local_ssd_count 1 absent OK +gcp_attributes.local_ssd_count 2 1 OK +gcp_attributes.local_ssd_count 2 absent OK +gcp_attributes.local_ssd_count absent 1 OK +gcp_attributes.local_ssd_count absent 2 OK +gcp_attributes.zone_id absent x OK +gcp_attributes.zone_id absent y OK +gcp_attributes.zone_id x absent OK +gcp_attributes.zone_id x y OK +gcp_attributes.zone_id y absent OK +gcp_attributes.zone_id y x OK +idle_instance_autotermination_minutes 1 2 OK +idle_instance_autotermination_minutes 1 absent OK +idle_instance_autotermination_minutes 2 1 OK +idle_instance_autotermination_minutes 2 absent OK +idle_instance_autotermination_minutes absent 1 OK +idle_instance_autotermination_minutes absent 2 OK +instance_pool_name x y OK +instance_pool_name y x OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +max_capacity 1 2 OK +max_capacity 1 absent OK +max_capacity 2 1 OK +max_capacity 2 absent OK +max_capacity absent 1 OK +max_capacity absent 2 OK +min_idle_instances 1 2 OK +min_idle_instances 1 absent OK +min_idle_instances 2 1 OK +min_idle_instances 2 absent OK +min_idle_instances absent 1 OK +min_idle_instances absent 2 OK +node_type_id x y OK +node_type_id y x OK +remote_disk_throughput 1 2 OK +remote_disk_throughput 1 absent OK +remote_disk_throughput 2 1 OK +remote_disk_throughput 2 absent OK +remote_disk_throughput absent 1 OK +remote_disk_throughput absent 2 OK +total_initial_remote_disk_size 1 2 OK +total_initial_remote_disk_size 1 absent OK +total_initial_remote_disk_size 2 1 OK +total_initial_remote_disk_size 2 absent OK +total_initial_remote_disk_size absent 1 OK +total_initial_remote_disk_size absent 2 OK + +=== summary +OK 95 +OK_RECREATE 36 +SUPPRESSED 3 +NOT_OBSERVABLE 6 +POST_DEPLOY_DRIFT 2 + +=== not covered: 10 fields under a slice or map +custom_tags.* +node_type_flexibility.alternate_node_type_ids[*] +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +preloaded_docker_images[*].basic_auth.password +preloaded_docker_images[*].basic_auth.username +preloaded_docker_images[*].url +preloaded_spark_versions[*] diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt index 1d0e705a1c9..d763d7b1bc7 100644 --- a/bundle/direct/tests/output/instance_pools.txt +++ b/bundle/direct/tests/output/instance_pools.txt @@ -1,162 +1,5 @@ === instance_pool.yml.tmpl -aws_attributes.availability absent->x OK -aws_attributes.availability absent->y OK -aws_attributes.availability x->absent OK -aws_attributes.availability x->y OK -aws_attributes.availability y->absent OK -aws_attributes.availability y->x OK -aws_attributes.instance_profile_arn absent->x OK -aws_attributes.instance_profile_arn absent->y OK -aws_attributes.instance_profile_arn x->absent OK -aws_attributes.instance_profile_arn x->y OK -aws_attributes.instance_profile_arn y->absent OK -aws_attributes.instance_profile_arn y->x OK -aws_attributes.spot_bid_price_percent 1->2 OK -aws_attributes.spot_bid_price_percent 1->absent OK -aws_attributes.spot_bid_price_percent 2->1 OK -aws_attributes.spot_bid_price_percent 2->absent OK -aws_attributes.spot_bid_price_percent absent->1 OK -aws_attributes.spot_bid_price_percent absent->2 OK -aws_attributes.zone_id absent->x OK -aws_attributes.zone_id absent->y OK -aws_attributes.zone_id x->absent OK -aws_attributes.zone_id x->y OK -aws_attributes.zone_id y->absent OK -aws_attributes.zone_id y->x OK -azure_attributes.availability absent->x OK -azure_attributes.availability absent->y OK -azure_attributes.availability x->absent OK -azure_attributes.availability x->y OK -azure_attributes.availability y->absent OK -azure_attributes.availability y->x OK -azure_attributes.capacity_reservation_group absent->x OK -azure_attributes.capacity_reservation_group absent->y OK -azure_attributes.capacity_reservation_group x->absent OK -azure_attributes.capacity_reservation_group x->y OK -azure_attributes.capacity_reservation_group y->absent OK -azure_attributes.capacity_reservation_group y->x OK -azure_attributes.spot_bid_max_price 1->2 OK -azure_attributes.spot_bid_max_price 1->absent OK -azure_attributes.spot_bid_max_price 2->1 OK -azure_attributes.spot_bid_max_price 2->absent OK -azure_attributes.spot_bid_max_price absent->1 OK -azure_attributes.spot_bid_max_price absent->2 OK -disk_spec.disk_count 1->2 OK_RECREATE -disk_spec.disk_count 1->absent OK_RECREATE -disk_spec.disk_count 2->1 OK_RECREATE -disk_spec.disk_count 2->absent OK_RECREATE -disk_spec.disk_count absent->1 OK_RECREATE -disk_spec.disk_count absent->2 OK_RECREATE -disk_spec.disk_iops 1->2 OK_RECREATE -disk_spec.disk_iops 1->absent OK_RECREATE -disk_spec.disk_iops 2->1 OK_RECREATE -disk_spec.disk_iops 2->absent OK_RECREATE -disk_spec.disk_iops absent->1 OK_RECREATE -disk_spec.disk_iops absent->2 OK_RECREATE -disk_spec.disk_size 1->2 OK_RECREATE -disk_spec.disk_size 1->absent OK_RECREATE -disk_spec.disk_size 2->1 OK_RECREATE -disk_spec.disk_size 2->absent OK_RECREATE -disk_spec.disk_size absent->1 OK_RECREATE -disk_spec.disk_size absent->2 OK_RECREATE -disk_spec.disk_throughput 1->2 OK_RECREATE -disk_spec.disk_throughput 1->absent OK_RECREATE -disk_spec.disk_throughput 2->1 OK_RECREATE -disk_spec.disk_throughput 2->absent OK_RECREATE -disk_spec.disk_throughput absent->1 OK_RECREATE -disk_spec.disk_throughput absent->2 OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type absent->x OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type absent->y OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type x->absent OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type x->y OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type y->absent OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type y->x OK_RECREATE -disk_spec.disk_type.ebs_volume_type absent->x OK_RECREATE -disk_spec.disk_type.ebs_volume_type absent->y OK_RECREATE -disk_spec.disk_type.ebs_volume_type x->absent OK_RECREATE -disk_spec.disk_type.ebs_volume_type x->y OK_RECREATE -disk_spec.disk_type.ebs_volume_type y->absent OK_RECREATE -disk_spec.disk_type.ebs_volume_type y->x OK_RECREATE -enable_elastic_disk absent->false SUPPRESSED empty -enable_elastic_disk absent->true DRIFT enable_elastic_disk -enable_elastic_disk false->absent SUPPRESSED remote_already_set -enable_elastic_disk false->true DRIFT enable_elastic_disk -enable_elastic_disk true->absent SUPPRESSED remote_already_set -enable_elastic_disk true->false OK -gcp_attributes.gcp_availability absent->x OK -gcp_attributes.gcp_availability absent->y OK -gcp_attributes.gcp_availability x->absent OK -gcp_attributes.gcp_availability x->y OK -gcp_attributes.gcp_availability y->absent OK -gcp_attributes.gcp_availability y->x OK -gcp_attributes.local_ssd_count 1->2 OK -gcp_attributes.local_ssd_count 1->absent OK -gcp_attributes.local_ssd_count 2->1 OK -gcp_attributes.local_ssd_count 2->absent OK -gcp_attributes.local_ssd_count absent->1 OK -gcp_attributes.local_ssd_count absent->2 OK -gcp_attributes.zone_id absent->x OK -gcp_attributes.zone_id absent->y OK -gcp_attributes.zone_id x->absent OK -gcp_attributes.zone_id x->y OK -gcp_attributes.zone_id y->absent OK -gcp_attributes.zone_id y->x OK -idle_instance_autotermination_minutes 1->2 OK -idle_instance_autotermination_minutes 1->absent OK -idle_instance_autotermination_minutes 2->1 OK -idle_instance_autotermination_minutes 2->absent OK -idle_instance_autotermination_minutes absent->1 OK -idle_instance_autotermination_minutes absent->2 OK -instance_pool_name x->y OK -instance_pool_name y->x OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -max_capacity 1->2 OK -max_capacity 1->absent OK -max_capacity 2->1 OK -max_capacity 2->absent OK -max_capacity absent->1 OK -max_capacity absent->2 OK -min_idle_instances 1->2 OK -min_idle_instances 1->absent OK -min_idle_instances 2->1 OK -min_idle_instances 2->absent OK -min_idle_instances absent->1 OK -min_idle_instances absent->2 OK -node_type_id x->y OK -node_type_id y->x OK -remote_disk_throughput 1->2 OK -remote_disk_throughput 1->absent OK -remote_disk_throughput 2->1 OK -remote_disk_throughput 2->absent OK -remote_disk_throughput absent->1 OK -remote_disk_throughput absent->2 OK -total_initial_remote_disk_size 1->2 OK -total_initial_remote_disk_size 1->absent OK -total_initial_remote_disk_size 2->1 OK -total_initial_remote_disk_size 2->absent OK -total_initial_remote_disk_size absent->1 OK -total_initial_remote_disk_size absent->2 OK - -=== summary -OK 95 -OK_RECREATE 36 -SUPPRESSED 3 -NOT_OBSERVABLE 6 -DRIFT 2 - -=== not covered: 10 fields under a slice or map -custom_tags.* -node_type_flexibility.alternate_node_type_ids[*] -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -preloaded_docker_images[*].basic_auth.password -preloaded_docker_images[*].basic_auth.username -preloaded_docker_images[*].url -preloaded_spark_versions[*] +enable_elastic_disk absent true POST_DEPLOY_DRIFT enable_elastic_disk +enable_elastic_disk false absent SUPPRESSED remote_already_set +enable_elastic_disk false true POST_DEPLOY_DRIFT enable_elastic_disk +enable_elastic_disk true absent SUPPRESSED remote_already_set diff --git a/bundle/direct/tests/output/jobs.full.txt b/bundle/direct/tests/output/jobs.full.txt new file mode 100644 index 00000000000..9974341023d --- /dev/null +++ b/bundle/direct/tests/output/jobs.full.txt @@ -0,0 +1,2055 @@ +=== job.yml.tmpl +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent OK +budget_policy_id x y OK +budget_policy_id y absent OK +budget_policy_id y x OK +continuous.pause_status absent x OK +continuous.pause_status absent y OK +continuous.pause_status x absent OK +continuous.pause_status x y OK +continuous.pause_status y absent OK +continuous.pause_status y x OK +continuous.task_retry_mode absent x OK +continuous.task_retry_mode absent y OK +continuous.task_retry_mode x absent OK +continuous.task_retry_mode x y OK +continuous.task_retry_mode y absent OK +continuous.task_retry_mode y x OK +deployment.deployment_id absent x OK +deployment.deployment_id absent y OK +deployment.deployment_id x absent OK +deployment.deployment_id x y OK +deployment.deployment_id y absent OK +deployment.deployment_id y x OK +deployment.kind x y OK +deployment.kind y x OK +deployment.metadata_file_path absent x OK +deployment.metadata_file_path absent y OK +deployment.metadata_file_path x absent OK +deployment.metadata_file_path x y OK +deployment.metadata_file_path y absent OK +deployment.metadata_file_path y x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x absent NOT_OBSERVABLE +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y absent NOT_OBSERVABLE +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +description absent x OK +description absent y OK +description x absent OK +description x y OK +description y absent OK +description y x OK +edit_mode absent x OK +edit_mode absent y OK +edit_mode x absent OK +edit_mode x y OK +edit_mode y absent OK +edit_mode y x OK +email_notifications.no_alert_for_skipped_runs absent false OK +email_notifications.no_alert_for_skipped_runs absent true OK +email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false true OK +email_notifications.no_alert_for_skipped_runs true absent OK +email_notifications.no_alert_for_skipped_runs true false OK +format MULTI_TASK SINGLE_TASK OK +format MULTI_TASK absent OK +format SINGLE_TASK MULTI_TASK OK +format SINGLE_TASK absent OK +format absent MULTI_TASK OK +format absent SINGLE_TASK OK +git_source.git_branch SKIPPED needs a coherent git_source block +git_source.git_commit SKIPPED needs a coherent git_source block +git_source.git_provider SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block +git_source.git_tag SKIPPED needs a coherent git_source block +git_source.git_url SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +max_concurrent_runs 1 5 OK +max_concurrent_runs 1 absent OK +max_concurrent_runs 5 1 OK +max_concurrent_runs 5 absent OK +max_concurrent_runs absent 1 OK +max_concurrent_runs absent 5 OK +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +notification_settings.no_alert_for_canceled_runs absent false OK +notification_settings.no_alert_for_canceled_runs absent true OK +notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false true OK +notification_settings.no_alert_for_canceled_runs true absent OK +notification_settings.no_alert_for_canceled_runs true false OK +notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent true OK +notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false true OK +notification_settings.no_alert_for_skipped_runs true absent OK +notification_settings.no_alert_for_skipped_runs true false OK +parent_path absent x OK +parent_path absent y OK +parent_path x absent OK +parent_path x y OK +parent_path y absent OK +parent_path y x OK +performance_target PERFORMANCE_OPTIMIZED STANDARD OK +performance_target PERFORMANCE_OPTIMIZED absent OK +performance_target STANDARD PERFORMANCE_OPTIMIZED OK +performance_target STANDARD absent OK +performance_target absent PERFORMANCE_OPTIMIZED OK +performance_target absent STANDARD OK +queue.enabled false true OK +queue.enabled true false OK +run_as.group_name absent x OK +run_as.group_name absent y OK +run_as.group_name x absent OK +run_as.group_name x y OK +run_as.group_name y absent OK +run_as.group_name y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +schedule.pause_status absent x OK +schedule.pause_status absent y OK +schedule.pause_status x absent OK +schedule.pause_status x y OK +schedule.pause_status y absent OK +schedule.pause_status y x OK +schedule.quartz_cron_expression x y OK +schedule.quartz_cron_expression y x OK +schedule.sql_condition.sql_query_id x y OK +schedule.sql_condition.sql_query_id y x OK +schedule.sql_condition.trigger_mode absent x OK +schedule.sql_condition.trigger_mode absent y OK +schedule.sql_condition.trigger_mode x absent OK +schedule.sql_condition.trigger_mode x y OK +schedule.sql_condition.trigger_mode y absent OK +schedule.sql_condition.trigger_mode y x OK +schedule.sql_condition.warehouse_id x y OK +schedule.sql_condition.warehouse_id y x OK +schedule.timezone_id x y OK +schedule.timezone_id y x OK +timeout_seconds 1200 600 OK +timeout_seconds 1200 absent OK +timeout_seconds 600 1200 OK +timeout_seconds 600 absent OK +timeout_seconds absent 1200 OK +timeout_seconds absent 600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK +trigger.file_arrival.url x y OK +trigger.file_arrival.url y x OK +trigger.file_arrival.wait_after_last_change_seconds 1 2 OK +trigger.file_arrival.wait_after_last_change_seconds 1 absent OK +trigger.file_arrival.wait_after_last_change_seconds 2 1 OK +trigger.file_arrival.wait_after_last_change_seconds 2 absent OK +trigger.file_arrival.wait_after_last_change_seconds absent 1 OK +trigger.file_arrival.wait_after_last_change_seconds absent 2 OK +trigger.model.condition x y OK +trigger.model.condition y x OK +trigger.model.min_time_between_triggers_seconds 1 2 OK +trigger.model.min_time_between_triggers_seconds 1 absent OK +trigger.model.min_time_between_triggers_seconds 2 1 OK +trigger.model.min_time_between_triggers_seconds 2 absent OK +trigger.model.min_time_between_triggers_seconds absent 1 OK +trigger.model.min_time_between_triggers_seconds absent 2 OK +trigger.model.securable_name absent x OK +trigger.model.securable_name absent y OK +trigger.model.securable_name x absent OK +trigger.model.securable_name x y OK +trigger.model.securable_name y absent OK +trigger.model.securable_name y x OK +trigger.model.wait_after_last_change_seconds 1 2 OK +trigger.model.wait_after_last_change_seconds 1 absent OK +trigger.model.wait_after_last_change_seconds 2 1 OK +trigger.model.wait_after_last_change_seconds 2 absent OK +trigger.model.wait_after_last_change_seconds absent 1 OK +trigger.model.wait_after_last_change_seconds absent 2 OK +trigger.pause_status absent x OK +trigger.pause_status absent y OK +trigger.pause_status x absent OK +trigger.pause_status x y OK +trigger.pause_status y absent OK +trigger.pause_status y x OK +trigger.periodic.interval 1 2 OK +trigger.periodic.interval 2 1 OK +trigger.periodic.unit x y OK +trigger.periodic.unit y x OK +trigger.sql_condition.sql_query_id x y OK +trigger.sql_condition.sql_query_id y x OK +trigger.sql_condition.trigger_mode absent x OK +trigger.sql_condition.trigger_mode absent y OK +trigger.sql_condition.trigger_mode x absent OK +trigger.sql_condition.trigger_mode x y OK +trigger.sql_condition.trigger_mode y absent OK +trigger.sql_condition.trigger_mode y x OK +trigger.sql_condition.warehouse_id x y OK +trigger.sql_condition.warehouse_id y x OK +trigger.table_update.condition absent x OK +trigger.table_update.condition absent y OK +trigger.table_update.condition x absent OK +trigger.table_update.condition x y OK +trigger.table_update.condition y absent OK +trigger.table_update.condition y x OK +trigger.table_update.min_time_between_triggers_seconds 1 2 OK +trigger.table_update.min_time_between_triggers_seconds 1 absent OK +trigger.table_update.min_time_between_triggers_seconds 2 1 OK +trigger.table_update.min_time_between_triggers_seconds 2 absent OK +trigger.table_update.min_time_between_triggers_seconds absent 1 OK +trigger.table_update.min_time_between_triggers_seconds absent 2 OK +trigger.table_update.wait_after_last_change_seconds 1 2 OK +trigger.table_update.wait_after_last_change_seconds 1 absent OK +trigger.table_update.wait_after_last_change_seconds 2 1 OK +trigger.table_update.wait_after_last_change_seconds 2 absent OK +trigger.table_update.wait_after_last_change_seconds absent 1 OK +trigger.table_update.wait_after_last_change_seconds absent 2 OK +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent OK +usage_policy_id x y OK +usage_policy_id y absent OK +usage_policy_id y x OK + +=== job_apply_policy_default_values_for_each_task.yml.tmpl +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent OK +budget_policy_id x y OK +budget_policy_id y absent OK +budget_policy_id y x OK +continuous.pause_status absent x OK +continuous.pause_status absent y OK +continuous.pause_status x absent OK +continuous.pause_status x y OK +continuous.pause_status y absent OK +continuous.pause_status y x OK +continuous.task_retry_mode absent x OK +continuous.task_retry_mode absent y OK +continuous.task_retry_mode x absent OK +continuous.task_retry_mode x y OK +continuous.task_retry_mode y absent OK +continuous.task_retry_mode y x OK +deployment.deployment_id absent x OK +deployment.deployment_id absent y OK +deployment.deployment_id x absent OK +deployment.deployment_id x y OK +deployment.deployment_id y absent OK +deployment.deployment_id y x OK +deployment.kind x y OK +deployment.kind y x OK +deployment.metadata_file_path absent x OK +deployment.metadata_file_path absent y OK +deployment.metadata_file_path x absent OK +deployment.metadata_file_path x y OK +deployment.metadata_file_path y absent OK +deployment.metadata_file_path y x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x absent NOT_OBSERVABLE +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y absent NOT_OBSERVABLE +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +description absent x OK +description absent y OK +description x absent OK +description x y OK +description y absent OK +description y x OK +edit_mode absent x OK +edit_mode absent y OK +edit_mode x absent OK +edit_mode x y OK +edit_mode y absent OK +edit_mode y x OK +email_notifications.no_alert_for_skipped_runs absent false OK +email_notifications.no_alert_for_skipped_runs absent true OK +email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false true OK +email_notifications.no_alert_for_skipped_runs true absent OK +email_notifications.no_alert_for_skipped_runs true false OK +format MULTI_TASK SINGLE_TASK OK +format MULTI_TASK absent OK +format SINGLE_TASK MULTI_TASK OK +format SINGLE_TASK absent OK +format absent MULTI_TASK OK +format absent SINGLE_TASK OK +git_source.git_branch SKIPPED needs a coherent git_source block +git_source.git_commit SKIPPED needs a coherent git_source block +git_source.git_provider SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block +git_source.git_tag SKIPPED needs a coherent git_source block +git_source.git_url SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +max_concurrent_runs 1 5 OK +max_concurrent_runs 1 absent OK +max_concurrent_runs 5 1 OK +max_concurrent_runs 5 absent OK +max_concurrent_runs absent 1 OK +max_concurrent_runs absent 5 OK +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +notification_settings.no_alert_for_canceled_runs absent false OK +notification_settings.no_alert_for_canceled_runs absent true OK +notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false true OK +notification_settings.no_alert_for_canceled_runs true absent OK +notification_settings.no_alert_for_canceled_runs true false OK +notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent true OK +notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false true OK +notification_settings.no_alert_for_skipped_runs true absent OK +notification_settings.no_alert_for_skipped_runs true false OK +parent_path absent x OK +parent_path absent y OK +parent_path x absent OK +parent_path x y OK +parent_path y absent OK +parent_path y x OK +performance_target PERFORMANCE_OPTIMIZED STANDARD OK +performance_target PERFORMANCE_OPTIMIZED absent OK +performance_target STANDARD PERFORMANCE_OPTIMIZED OK +performance_target STANDARD absent OK +performance_target absent PERFORMANCE_OPTIMIZED OK +performance_target absent STANDARD OK +queue.enabled false true OK +queue.enabled true false OK +run_as.group_name absent x OK +run_as.group_name absent y OK +run_as.group_name x absent OK +run_as.group_name x y OK +run_as.group_name y absent OK +run_as.group_name y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +schedule.pause_status absent x OK +schedule.pause_status absent y OK +schedule.pause_status x absent OK +schedule.pause_status x y OK +schedule.pause_status y absent OK +schedule.pause_status y x OK +schedule.quartz_cron_expression x y OK +schedule.quartz_cron_expression y x OK +schedule.sql_condition.sql_query_id x y OK +schedule.sql_condition.sql_query_id y x OK +schedule.sql_condition.trigger_mode absent x OK +schedule.sql_condition.trigger_mode absent y OK +schedule.sql_condition.trigger_mode x absent OK +schedule.sql_condition.trigger_mode x y OK +schedule.sql_condition.trigger_mode y absent OK +schedule.sql_condition.trigger_mode y x OK +schedule.sql_condition.warehouse_id x y OK +schedule.sql_condition.warehouse_id y x OK +schedule.timezone_id x y OK +schedule.timezone_id y x OK +timeout_seconds 1200 600 OK +timeout_seconds 1200 absent OK +timeout_seconds 600 1200 OK +timeout_seconds 600 absent OK +timeout_seconds absent 1200 OK +timeout_seconds absent 600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK +trigger.file_arrival.url x y OK +trigger.file_arrival.url y x OK +trigger.file_arrival.wait_after_last_change_seconds 1 2 OK +trigger.file_arrival.wait_after_last_change_seconds 1 absent OK +trigger.file_arrival.wait_after_last_change_seconds 2 1 OK +trigger.file_arrival.wait_after_last_change_seconds 2 absent OK +trigger.file_arrival.wait_after_last_change_seconds absent 1 OK +trigger.file_arrival.wait_after_last_change_seconds absent 2 OK +trigger.model.condition x y OK +trigger.model.condition y x OK +trigger.model.min_time_between_triggers_seconds 1 2 OK +trigger.model.min_time_between_triggers_seconds 1 absent OK +trigger.model.min_time_between_triggers_seconds 2 1 OK +trigger.model.min_time_between_triggers_seconds 2 absent OK +trigger.model.min_time_between_triggers_seconds absent 1 OK +trigger.model.min_time_between_triggers_seconds absent 2 OK +trigger.model.securable_name absent x OK +trigger.model.securable_name absent y OK +trigger.model.securable_name x absent OK +trigger.model.securable_name x y OK +trigger.model.securable_name y absent OK +trigger.model.securable_name y x OK +trigger.model.wait_after_last_change_seconds 1 2 OK +trigger.model.wait_after_last_change_seconds 1 absent OK +trigger.model.wait_after_last_change_seconds 2 1 OK +trigger.model.wait_after_last_change_seconds 2 absent OK +trigger.model.wait_after_last_change_seconds absent 1 OK +trigger.model.wait_after_last_change_seconds absent 2 OK +trigger.pause_status absent x OK +trigger.pause_status absent y OK +trigger.pause_status x absent OK +trigger.pause_status x y OK +trigger.pause_status y absent OK +trigger.pause_status y x OK +trigger.periodic.interval 1 2 OK +trigger.periodic.interval 2 1 OK +trigger.periodic.unit x y OK +trigger.periodic.unit y x OK +trigger.sql_condition.sql_query_id x y OK +trigger.sql_condition.sql_query_id y x OK +trigger.sql_condition.trigger_mode absent x OK +trigger.sql_condition.trigger_mode absent y OK +trigger.sql_condition.trigger_mode x absent OK +trigger.sql_condition.trigger_mode x y OK +trigger.sql_condition.trigger_mode y absent OK +trigger.sql_condition.trigger_mode y x OK +trigger.sql_condition.warehouse_id x y OK +trigger.sql_condition.warehouse_id y x OK +trigger.table_update.condition absent x OK +trigger.table_update.condition absent y OK +trigger.table_update.condition x absent OK +trigger.table_update.condition x y OK +trigger.table_update.condition y absent OK +trigger.table_update.condition y x OK +trigger.table_update.min_time_between_triggers_seconds 1 2 OK +trigger.table_update.min_time_between_triggers_seconds 1 absent OK +trigger.table_update.min_time_between_triggers_seconds 2 1 OK +trigger.table_update.min_time_between_triggers_seconds 2 absent OK +trigger.table_update.min_time_between_triggers_seconds absent 1 OK +trigger.table_update.min_time_between_triggers_seconds absent 2 OK +trigger.table_update.wait_after_last_change_seconds 1 2 OK +trigger.table_update.wait_after_last_change_seconds 1 absent OK +trigger.table_update.wait_after_last_change_seconds 2 1 OK +trigger.table_update.wait_after_last_change_seconds 2 absent OK +trigger.table_update.wait_after_last_change_seconds absent 1 OK +trigger.table_update.wait_after_last_change_seconds absent 2 OK +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent OK +usage_policy_id x y OK +usage_policy_id y absent OK +usage_policy_id y x OK + +=== job_apply_policy_default_values_job_cluster.yml.tmpl +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent OK +budget_policy_id x y OK +budget_policy_id y absent OK +budget_policy_id y x OK +continuous.pause_status absent x OK +continuous.pause_status absent y OK +continuous.pause_status x absent OK +continuous.pause_status x y OK +continuous.pause_status y absent OK +continuous.pause_status y x OK +continuous.task_retry_mode absent x OK +continuous.task_retry_mode absent y OK +continuous.task_retry_mode x absent OK +continuous.task_retry_mode x y OK +continuous.task_retry_mode y absent OK +continuous.task_retry_mode y x OK +deployment.deployment_id absent x OK +deployment.deployment_id absent y OK +deployment.deployment_id x absent OK +deployment.deployment_id x y OK +deployment.deployment_id y absent OK +deployment.deployment_id y x OK +deployment.kind x y OK +deployment.kind y x OK +deployment.metadata_file_path absent x OK +deployment.metadata_file_path absent y OK +deployment.metadata_file_path x absent OK +deployment.metadata_file_path x y OK +deployment.metadata_file_path y absent OK +deployment.metadata_file_path y x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x absent NOT_OBSERVABLE +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y absent NOT_OBSERVABLE +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +description absent x OK +description absent y OK +description x absent OK +description x y OK +description y absent OK +description y x OK +edit_mode absent x OK +edit_mode absent y OK +edit_mode x absent OK +edit_mode x y OK +edit_mode y absent OK +edit_mode y x OK +email_notifications.no_alert_for_skipped_runs absent false OK +email_notifications.no_alert_for_skipped_runs absent true OK +email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false true OK +email_notifications.no_alert_for_skipped_runs true absent OK +email_notifications.no_alert_for_skipped_runs true false OK +format MULTI_TASK SINGLE_TASK OK +format MULTI_TASK absent OK +format SINGLE_TASK MULTI_TASK OK +format SINGLE_TASK absent OK +format absent MULTI_TASK OK +format absent SINGLE_TASK OK +git_source.git_branch SKIPPED needs a coherent git_source block +git_source.git_commit SKIPPED needs a coherent git_source block +git_source.git_provider SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block +git_source.git_tag SKIPPED needs a coherent git_source block +git_source.git_url SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +max_concurrent_runs 1 5 OK +max_concurrent_runs 1 absent OK +max_concurrent_runs 5 1 OK +max_concurrent_runs 5 absent OK +max_concurrent_runs absent 1 OK +max_concurrent_runs absent 5 OK +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +notification_settings.no_alert_for_canceled_runs absent false OK +notification_settings.no_alert_for_canceled_runs absent true OK +notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false true OK +notification_settings.no_alert_for_canceled_runs true absent OK +notification_settings.no_alert_for_canceled_runs true false OK +notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent true OK +notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false true OK +notification_settings.no_alert_for_skipped_runs true absent OK +notification_settings.no_alert_for_skipped_runs true false OK +parent_path absent x OK +parent_path absent y OK +parent_path x absent OK +parent_path x y OK +parent_path y absent OK +parent_path y x OK +performance_target PERFORMANCE_OPTIMIZED STANDARD OK +performance_target PERFORMANCE_OPTIMIZED absent OK +performance_target STANDARD PERFORMANCE_OPTIMIZED OK +performance_target STANDARD absent OK +performance_target absent PERFORMANCE_OPTIMIZED OK +performance_target absent STANDARD OK +queue.enabled false true OK +queue.enabled true false OK +run_as.group_name absent x OK +run_as.group_name absent y OK +run_as.group_name x absent OK +run_as.group_name x y OK +run_as.group_name y absent OK +run_as.group_name y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +schedule.pause_status absent x OK +schedule.pause_status absent y OK +schedule.pause_status x absent OK +schedule.pause_status x y OK +schedule.pause_status y absent OK +schedule.pause_status y x OK +schedule.quartz_cron_expression x y OK +schedule.quartz_cron_expression y x OK +schedule.sql_condition.sql_query_id x y OK +schedule.sql_condition.sql_query_id y x OK +schedule.sql_condition.trigger_mode absent x OK +schedule.sql_condition.trigger_mode absent y OK +schedule.sql_condition.trigger_mode x absent OK +schedule.sql_condition.trigger_mode x y OK +schedule.sql_condition.trigger_mode y absent OK +schedule.sql_condition.trigger_mode y x OK +schedule.sql_condition.warehouse_id x y OK +schedule.sql_condition.warehouse_id y x OK +schedule.timezone_id x y OK +schedule.timezone_id y x OK +timeout_seconds 1200 600 OK +timeout_seconds 1200 absent OK +timeout_seconds 600 1200 OK +timeout_seconds 600 absent OK +timeout_seconds absent 1200 OK +timeout_seconds absent 600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK +trigger.file_arrival.url x y OK +trigger.file_arrival.url y x OK +trigger.file_arrival.wait_after_last_change_seconds 1 2 OK +trigger.file_arrival.wait_after_last_change_seconds 1 absent OK +trigger.file_arrival.wait_after_last_change_seconds 2 1 OK +trigger.file_arrival.wait_after_last_change_seconds 2 absent OK +trigger.file_arrival.wait_after_last_change_seconds absent 1 OK +trigger.file_arrival.wait_after_last_change_seconds absent 2 OK +trigger.model.condition x y OK +trigger.model.condition y x OK +trigger.model.min_time_between_triggers_seconds 1 2 OK +trigger.model.min_time_between_triggers_seconds 1 absent OK +trigger.model.min_time_between_triggers_seconds 2 1 OK +trigger.model.min_time_between_triggers_seconds 2 absent OK +trigger.model.min_time_between_triggers_seconds absent 1 OK +trigger.model.min_time_between_triggers_seconds absent 2 OK +trigger.model.securable_name absent x OK +trigger.model.securable_name absent y OK +trigger.model.securable_name x absent OK +trigger.model.securable_name x y OK +trigger.model.securable_name y absent OK +trigger.model.securable_name y x OK +trigger.model.wait_after_last_change_seconds 1 2 OK +trigger.model.wait_after_last_change_seconds 1 absent OK +trigger.model.wait_after_last_change_seconds 2 1 OK +trigger.model.wait_after_last_change_seconds 2 absent OK +trigger.model.wait_after_last_change_seconds absent 1 OK +trigger.model.wait_after_last_change_seconds absent 2 OK +trigger.pause_status absent x OK +trigger.pause_status absent y OK +trigger.pause_status x absent OK +trigger.pause_status x y OK +trigger.pause_status y absent OK +trigger.pause_status y x OK +trigger.periodic.interval 1 2 OK +trigger.periodic.interval 2 1 OK +trigger.periodic.unit x y OK +trigger.periodic.unit y x OK +trigger.sql_condition.sql_query_id x y OK +trigger.sql_condition.sql_query_id y x OK +trigger.sql_condition.trigger_mode absent x OK +trigger.sql_condition.trigger_mode absent y OK +trigger.sql_condition.trigger_mode x absent OK +trigger.sql_condition.trigger_mode x y OK +trigger.sql_condition.trigger_mode y absent OK +trigger.sql_condition.trigger_mode y x OK +trigger.sql_condition.warehouse_id x y OK +trigger.sql_condition.warehouse_id y x OK +trigger.table_update.condition absent x OK +trigger.table_update.condition absent y OK +trigger.table_update.condition x absent OK +trigger.table_update.condition x y OK +trigger.table_update.condition y absent OK +trigger.table_update.condition y x OK +trigger.table_update.min_time_between_triggers_seconds 1 2 OK +trigger.table_update.min_time_between_triggers_seconds 1 absent OK +trigger.table_update.min_time_between_triggers_seconds 2 1 OK +trigger.table_update.min_time_between_triggers_seconds 2 absent OK +trigger.table_update.min_time_between_triggers_seconds absent 1 OK +trigger.table_update.min_time_between_triggers_seconds absent 2 OK +trigger.table_update.wait_after_last_change_seconds 1 2 OK +trigger.table_update.wait_after_last_change_seconds 1 absent OK +trigger.table_update.wait_after_last_change_seconds 2 1 OK +trigger.table_update.wait_after_last_change_seconds 2 absent OK +trigger.table_update.wait_after_last_change_seconds absent 1 OK +trigger.table_update.wait_after_last_change_seconds absent 2 OK +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent OK +usage_policy_id x y OK +usage_policy_id y absent OK +usage_policy_id y x OK + +=== job_apply_policy_default_values_task_cluster.yml.tmpl +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent OK +budget_policy_id x y OK +budget_policy_id y absent OK +budget_policy_id y x OK +continuous.pause_status absent x OK +continuous.pause_status absent y OK +continuous.pause_status x absent OK +continuous.pause_status x y OK +continuous.pause_status y absent OK +continuous.pause_status y x OK +continuous.task_retry_mode absent x OK +continuous.task_retry_mode absent y OK +continuous.task_retry_mode x absent OK +continuous.task_retry_mode x y OK +continuous.task_retry_mode y absent OK +continuous.task_retry_mode y x OK +deployment.deployment_id absent x OK +deployment.deployment_id absent y OK +deployment.deployment_id x absent OK +deployment.deployment_id x y OK +deployment.deployment_id y absent OK +deployment.deployment_id y x OK +deployment.kind x y OK +deployment.kind y x OK +deployment.metadata_file_path absent x OK +deployment.metadata_file_path absent y OK +deployment.metadata_file_path x absent OK +deployment.metadata_file_path x y OK +deployment.metadata_file_path y absent OK +deployment.metadata_file_path y x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x absent NOT_OBSERVABLE +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y absent NOT_OBSERVABLE +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +description absent x OK +description absent y OK +description x absent OK +description x y OK +description y absent OK +description y x OK +edit_mode absent x OK +edit_mode absent y OK +edit_mode x absent OK +edit_mode x y OK +edit_mode y absent OK +edit_mode y x OK +email_notifications.no_alert_for_skipped_runs absent false OK +email_notifications.no_alert_for_skipped_runs absent true OK +email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false true OK +email_notifications.no_alert_for_skipped_runs true absent OK +email_notifications.no_alert_for_skipped_runs true false OK +format MULTI_TASK SINGLE_TASK OK +format MULTI_TASK absent OK +format SINGLE_TASK MULTI_TASK OK +format SINGLE_TASK absent OK +format absent MULTI_TASK OK +format absent SINGLE_TASK OK +git_source.git_branch SKIPPED needs a coherent git_source block +git_source.git_commit SKIPPED needs a coherent git_source block +git_source.git_provider SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block +git_source.git_tag SKIPPED needs a coherent git_source block +git_source.git_url SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +max_concurrent_runs 1 5 OK +max_concurrent_runs 1 absent OK +max_concurrent_runs 5 1 OK +max_concurrent_runs 5 absent OK +max_concurrent_runs absent 1 OK +max_concurrent_runs absent 5 OK +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +notification_settings.no_alert_for_canceled_runs absent false OK +notification_settings.no_alert_for_canceled_runs absent true OK +notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false true OK +notification_settings.no_alert_for_canceled_runs true absent OK +notification_settings.no_alert_for_canceled_runs true false OK +notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent true OK +notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false true OK +notification_settings.no_alert_for_skipped_runs true absent OK +notification_settings.no_alert_for_skipped_runs true false OK +parent_path absent x OK +parent_path absent y OK +parent_path x absent OK +parent_path x y OK +parent_path y absent OK +parent_path y x OK +performance_target PERFORMANCE_OPTIMIZED STANDARD OK +performance_target PERFORMANCE_OPTIMIZED absent OK +performance_target STANDARD PERFORMANCE_OPTIMIZED OK +performance_target STANDARD absent OK +performance_target absent PERFORMANCE_OPTIMIZED OK +performance_target absent STANDARD OK +queue.enabled false true OK +queue.enabled true false OK +run_as.group_name absent x OK +run_as.group_name absent y OK +run_as.group_name x absent OK +run_as.group_name x y OK +run_as.group_name y absent OK +run_as.group_name y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +schedule.pause_status absent x OK +schedule.pause_status absent y OK +schedule.pause_status x absent OK +schedule.pause_status x y OK +schedule.pause_status y absent OK +schedule.pause_status y x OK +schedule.quartz_cron_expression x y OK +schedule.quartz_cron_expression y x OK +schedule.sql_condition.sql_query_id x y OK +schedule.sql_condition.sql_query_id y x OK +schedule.sql_condition.trigger_mode absent x OK +schedule.sql_condition.trigger_mode absent y OK +schedule.sql_condition.trigger_mode x absent OK +schedule.sql_condition.trigger_mode x y OK +schedule.sql_condition.trigger_mode y absent OK +schedule.sql_condition.trigger_mode y x OK +schedule.sql_condition.warehouse_id x y OK +schedule.sql_condition.warehouse_id y x OK +schedule.timezone_id x y OK +schedule.timezone_id y x OK +timeout_seconds 1200 600 OK +timeout_seconds 1200 absent OK +timeout_seconds 600 1200 OK +timeout_seconds 600 absent OK +timeout_seconds absent 1200 OK +timeout_seconds absent 600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK +trigger.file_arrival.url x y OK +trigger.file_arrival.url y x OK +trigger.file_arrival.wait_after_last_change_seconds 1 2 OK +trigger.file_arrival.wait_after_last_change_seconds 1 absent OK +trigger.file_arrival.wait_after_last_change_seconds 2 1 OK +trigger.file_arrival.wait_after_last_change_seconds 2 absent OK +trigger.file_arrival.wait_after_last_change_seconds absent 1 OK +trigger.file_arrival.wait_after_last_change_seconds absent 2 OK +trigger.model.condition x y OK +trigger.model.condition y x OK +trigger.model.min_time_between_triggers_seconds 1 2 OK +trigger.model.min_time_between_triggers_seconds 1 absent OK +trigger.model.min_time_between_triggers_seconds 2 1 OK +trigger.model.min_time_between_triggers_seconds 2 absent OK +trigger.model.min_time_between_triggers_seconds absent 1 OK +trigger.model.min_time_between_triggers_seconds absent 2 OK +trigger.model.securable_name absent x OK +trigger.model.securable_name absent y OK +trigger.model.securable_name x absent OK +trigger.model.securable_name x y OK +trigger.model.securable_name y absent OK +trigger.model.securable_name y x OK +trigger.model.wait_after_last_change_seconds 1 2 OK +trigger.model.wait_after_last_change_seconds 1 absent OK +trigger.model.wait_after_last_change_seconds 2 1 OK +trigger.model.wait_after_last_change_seconds 2 absent OK +trigger.model.wait_after_last_change_seconds absent 1 OK +trigger.model.wait_after_last_change_seconds absent 2 OK +trigger.pause_status absent x OK +trigger.pause_status absent y OK +trigger.pause_status x absent OK +trigger.pause_status x y OK +trigger.pause_status y absent OK +trigger.pause_status y x OK +trigger.periodic.interval 1 2 OK +trigger.periodic.interval 2 1 OK +trigger.periodic.unit x y OK +trigger.periodic.unit y x OK +trigger.sql_condition.sql_query_id x y OK +trigger.sql_condition.sql_query_id y x OK +trigger.sql_condition.trigger_mode absent x OK +trigger.sql_condition.trigger_mode absent y OK +trigger.sql_condition.trigger_mode x absent OK +trigger.sql_condition.trigger_mode x y OK +trigger.sql_condition.trigger_mode y absent OK +trigger.sql_condition.trigger_mode y x OK +trigger.sql_condition.warehouse_id x y OK +trigger.sql_condition.warehouse_id y x OK +trigger.table_update.condition absent x OK +trigger.table_update.condition absent y OK +trigger.table_update.condition x absent OK +trigger.table_update.condition x y OK +trigger.table_update.condition y absent OK +trigger.table_update.condition y x OK +trigger.table_update.min_time_between_triggers_seconds 1 2 OK +trigger.table_update.min_time_between_triggers_seconds 1 absent OK +trigger.table_update.min_time_between_triggers_seconds 2 1 OK +trigger.table_update.min_time_between_triggers_seconds 2 absent OK +trigger.table_update.min_time_between_triggers_seconds absent 1 OK +trigger.table_update.min_time_between_triggers_seconds absent 2 OK +trigger.table_update.wait_after_last_change_seconds 1 2 OK +trigger.table_update.wait_after_last_change_seconds 1 absent OK +trigger.table_update.wait_after_last_change_seconds 2 1 OK +trigger.table_update.wait_after_last_change_seconds 2 absent OK +trigger.table_update.wait_after_last_change_seconds absent 1 OK +trigger.table_update.wait_after_last_change_seconds absent 2 OK +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent OK +usage_policy_id x y OK +usage_policy_id y absent OK +usage_policy_id y x OK + +=== job_with_depends_on.yml.tmpl +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent OK +budget_policy_id x y OK +budget_policy_id y absent OK +budget_policy_id y x OK +continuous.pause_status absent x OK +continuous.pause_status absent y OK +continuous.pause_status x absent OK +continuous.pause_status x y OK +continuous.pause_status y absent OK +continuous.pause_status y x OK +continuous.task_retry_mode absent x OK +continuous.task_retry_mode absent y OK +continuous.task_retry_mode x absent OK +continuous.task_retry_mode x y OK +continuous.task_retry_mode y absent OK +continuous.task_retry_mode y x OK +deployment.deployment_id absent x OK +deployment.deployment_id absent y OK +deployment.deployment_id x absent OK +deployment.deployment_id x y OK +deployment.deployment_id y absent OK +deployment.deployment_id y x OK +deployment.kind x y OK +deployment.kind y x OK +deployment.metadata_file_path absent x OK +deployment.metadata_file_path absent y OK +deployment.metadata_file_path x absent OK +deployment.metadata_file_path x y OK +deployment.metadata_file_path y absent OK +deployment.metadata_file_path y x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x absent NOT_OBSERVABLE +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y absent NOT_OBSERVABLE +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +description absent x OK +description absent y OK +description x absent OK +description x y OK +description y absent OK +description y x OK +edit_mode absent x OK +edit_mode absent y OK +edit_mode x absent OK +edit_mode x y OK +edit_mode y absent OK +edit_mode y x OK +email_notifications.no_alert_for_skipped_runs absent false OK +email_notifications.no_alert_for_skipped_runs absent true OK +email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false true OK +email_notifications.no_alert_for_skipped_runs true absent OK +email_notifications.no_alert_for_skipped_runs true false OK +format MULTI_TASK SINGLE_TASK OK +format MULTI_TASK absent OK +format SINGLE_TASK MULTI_TASK OK +format SINGLE_TASK absent OK +format absent MULTI_TASK OK +format absent SINGLE_TASK OK +git_source.git_branch SKIPPED needs a coherent git_source block +git_source.git_commit SKIPPED needs a coherent git_source block +git_source.git_provider SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block +git_source.git_tag SKIPPED needs a coherent git_source block +git_source.git_url SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +max_concurrent_runs 1 5 OK +max_concurrent_runs 1 absent OK +max_concurrent_runs 5 1 OK +max_concurrent_runs 5 absent OK +max_concurrent_runs absent 1 OK +max_concurrent_runs absent 5 OK +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +notification_settings.no_alert_for_canceled_runs absent false OK +notification_settings.no_alert_for_canceled_runs absent true OK +notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false true OK +notification_settings.no_alert_for_canceled_runs true absent OK +notification_settings.no_alert_for_canceled_runs true false OK +notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent true OK +notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false true OK +notification_settings.no_alert_for_skipped_runs true absent OK +notification_settings.no_alert_for_skipped_runs true false OK +parent_path absent x OK +parent_path absent y OK +parent_path x absent OK +parent_path x y OK +parent_path y absent OK +parent_path y x OK +performance_target PERFORMANCE_OPTIMIZED STANDARD OK +performance_target PERFORMANCE_OPTIMIZED absent OK +performance_target STANDARD PERFORMANCE_OPTIMIZED OK +performance_target STANDARD absent OK +performance_target absent PERFORMANCE_OPTIMIZED OK +performance_target absent STANDARD OK +queue.enabled false true OK +queue.enabled true false OK +run_as.group_name absent x OK +run_as.group_name absent y OK +run_as.group_name x absent OK +run_as.group_name x y OK +run_as.group_name y absent OK +run_as.group_name y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +schedule.pause_status absent x OK +schedule.pause_status absent y OK +schedule.pause_status x absent OK +schedule.pause_status x y OK +schedule.pause_status y absent OK +schedule.pause_status y x OK +schedule.quartz_cron_expression x y OK +schedule.quartz_cron_expression y x OK +schedule.sql_condition.sql_query_id x y OK +schedule.sql_condition.sql_query_id y x OK +schedule.sql_condition.trigger_mode absent x OK +schedule.sql_condition.trigger_mode absent y OK +schedule.sql_condition.trigger_mode x absent OK +schedule.sql_condition.trigger_mode x y OK +schedule.sql_condition.trigger_mode y absent OK +schedule.sql_condition.trigger_mode y x OK +schedule.sql_condition.warehouse_id x y OK +schedule.sql_condition.warehouse_id y x OK +schedule.timezone_id x y OK +schedule.timezone_id y x OK +timeout_seconds 1200 600 OK +timeout_seconds 1200 absent OK +timeout_seconds 600 1200 OK +timeout_seconds 600 absent OK +timeout_seconds absent 1200 OK +timeout_seconds absent 600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK +trigger.file_arrival.url x y OK +trigger.file_arrival.url y x OK +trigger.file_arrival.wait_after_last_change_seconds 1 2 OK +trigger.file_arrival.wait_after_last_change_seconds 1 absent OK +trigger.file_arrival.wait_after_last_change_seconds 2 1 OK +trigger.file_arrival.wait_after_last_change_seconds 2 absent OK +trigger.file_arrival.wait_after_last_change_seconds absent 1 OK +trigger.file_arrival.wait_after_last_change_seconds absent 2 OK +trigger.model.condition x y OK +trigger.model.condition y x OK +trigger.model.min_time_between_triggers_seconds 1 2 OK +trigger.model.min_time_between_triggers_seconds 1 absent OK +trigger.model.min_time_between_triggers_seconds 2 1 OK +trigger.model.min_time_between_triggers_seconds 2 absent OK +trigger.model.min_time_between_triggers_seconds absent 1 OK +trigger.model.min_time_between_triggers_seconds absent 2 OK +trigger.model.securable_name absent x OK +trigger.model.securable_name absent y OK +trigger.model.securable_name x absent OK +trigger.model.securable_name x y OK +trigger.model.securable_name y absent OK +trigger.model.securable_name y x OK +trigger.model.wait_after_last_change_seconds 1 2 OK +trigger.model.wait_after_last_change_seconds 1 absent OK +trigger.model.wait_after_last_change_seconds 2 1 OK +trigger.model.wait_after_last_change_seconds 2 absent OK +trigger.model.wait_after_last_change_seconds absent 1 OK +trigger.model.wait_after_last_change_seconds absent 2 OK +trigger.pause_status absent x OK +trigger.pause_status absent y OK +trigger.pause_status x absent OK +trigger.pause_status x y OK +trigger.pause_status y absent OK +trigger.pause_status y x OK +trigger.periodic.interval 1 2 OK +trigger.periodic.interval 2 1 OK +trigger.periodic.unit x y OK +trigger.periodic.unit y x OK +trigger.sql_condition.sql_query_id x y OK +trigger.sql_condition.sql_query_id y x OK +trigger.sql_condition.trigger_mode absent x OK +trigger.sql_condition.trigger_mode absent y OK +trigger.sql_condition.trigger_mode x absent OK +trigger.sql_condition.trigger_mode x y OK +trigger.sql_condition.trigger_mode y absent OK +trigger.sql_condition.trigger_mode y x OK +trigger.sql_condition.warehouse_id x y OK +trigger.sql_condition.warehouse_id y x OK +trigger.table_update.condition absent x OK +trigger.table_update.condition absent y OK +trigger.table_update.condition x absent OK +trigger.table_update.condition x y OK +trigger.table_update.condition y absent OK +trigger.table_update.condition y x OK +trigger.table_update.min_time_between_triggers_seconds 1 2 OK +trigger.table_update.min_time_between_triggers_seconds 1 absent OK +trigger.table_update.min_time_between_triggers_seconds 2 1 OK +trigger.table_update.min_time_between_triggers_seconds 2 absent OK +trigger.table_update.min_time_between_triggers_seconds absent 1 OK +trigger.table_update.min_time_between_triggers_seconds absent 2 OK +trigger.table_update.wait_after_last_change_seconds 1 2 OK +trigger.table_update.wait_after_last_change_seconds 1 absent OK +trigger.table_update.wait_after_last_change_seconds 2 1 OK +trigger.table_update.wait_after_last_change_seconds 2 absent OK +trigger.table_update.wait_after_last_change_seconds absent 1 OK +trigger.table_update.wait_after_last_change_seconds absent 2 OK +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent OK +usage_policy_id x y OK +usage_policy_id y absent OK +usage_policy_id y x OK + +=== job_with_task.yml.tmpl +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent OK +budget_policy_id x y OK +budget_policy_id y absent OK +budget_policy_id y x OK +continuous.pause_status absent x OK +continuous.pause_status absent y OK +continuous.pause_status x absent OK +continuous.pause_status x y OK +continuous.pause_status y absent OK +continuous.pause_status y x OK +continuous.task_retry_mode absent x OK +continuous.task_retry_mode absent y OK +continuous.task_retry_mode x absent OK +continuous.task_retry_mode x y OK +continuous.task_retry_mode y absent OK +continuous.task_retry_mode y x OK +deployment.deployment_id absent x OK +deployment.deployment_id absent y OK +deployment.deployment_id x absent OK +deployment.deployment_id x y OK +deployment.deployment_id y absent OK +deployment.deployment_id y x OK +deployment.kind x y OK +deployment.kind y x OK +deployment.metadata_file_path absent x OK +deployment.metadata_file_path absent y OK +deployment.metadata_file_path x absent OK +deployment.metadata_file_path x y OK +deployment.metadata_file_path y absent OK +deployment.metadata_file_path y x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x absent NOT_OBSERVABLE +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y absent NOT_OBSERVABLE +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +description absent x OK +description absent y OK +description x absent OK +description x y OK +description y absent OK +description y x OK +edit_mode absent x OK +edit_mode absent y OK +edit_mode x absent OK +edit_mode x y OK +edit_mode y absent OK +edit_mode y x OK +email_notifications.no_alert_for_skipped_runs absent false OK +email_notifications.no_alert_for_skipped_runs absent true OK +email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty +email_notifications.no_alert_for_skipped_runs false true OK +email_notifications.no_alert_for_skipped_runs true absent OK +email_notifications.no_alert_for_skipped_runs true false OK +format MULTI_TASK SINGLE_TASK OK +format MULTI_TASK absent OK +format SINGLE_TASK MULTI_TASK OK +format SINGLE_TASK absent OK +format absent MULTI_TASK OK +format absent SINGLE_TASK OK +git_source.git_branch SKIPPED needs a coherent git_source block +git_source.git_commit SKIPPED needs a coherent git_source block +git_source.git_provider SKIPPED needs a coherent git_source block +git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block +git_source.git_tag SKIPPED needs a coherent git_source block +git_source.git_url SKIPPED needs a coherent git_source block +git_source.job_source.dirty_state SKIPPED needs a coherent git_source block +git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block +git_source.job_source.job_config_path SKIPPED needs a coherent git_source block +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +max_concurrent_runs 1 5 OK +max_concurrent_runs 1 absent OK +max_concurrent_runs 5 1 OK +max_concurrent_runs 5 absent OK +max_concurrent_runs absent 1 OK +max_concurrent_runs absent 5 OK +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +notification_settings.no_alert_for_canceled_runs absent false OK +notification_settings.no_alert_for_canceled_runs absent true OK +notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_canceled_runs false true OK +notification_settings.no_alert_for_canceled_runs true absent OK +notification_settings.no_alert_for_canceled_runs true false OK +notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs absent true OK +notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty +notification_settings.no_alert_for_skipped_runs false true OK +notification_settings.no_alert_for_skipped_runs true absent OK +notification_settings.no_alert_for_skipped_runs true false OK +parent_path absent x OK +parent_path absent y OK +parent_path x absent OK +parent_path x y OK +parent_path y absent OK +parent_path y x OK +performance_target PERFORMANCE_OPTIMIZED STANDARD OK +performance_target PERFORMANCE_OPTIMIZED absent OK +performance_target STANDARD PERFORMANCE_OPTIMIZED OK +performance_target STANDARD absent OK +performance_target absent PERFORMANCE_OPTIMIZED OK +performance_target absent STANDARD OK +queue.enabled false true OK +queue.enabled true false OK +run_as.group_name absent x OK +run_as.group_name absent y OK +run_as.group_name x absent OK +run_as.group_name x y OK +run_as.group_name y absent OK +run_as.group_name y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +schedule.pause_status absent x OK +schedule.pause_status absent y OK +schedule.pause_status x absent OK +schedule.pause_status x y OK +schedule.pause_status y absent OK +schedule.pause_status y x OK +schedule.quartz_cron_expression x y OK +schedule.quartz_cron_expression y x OK +schedule.sql_condition.sql_query_id x y OK +schedule.sql_condition.sql_query_id y x OK +schedule.sql_condition.trigger_mode absent x OK +schedule.sql_condition.trigger_mode absent y OK +schedule.sql_condition.trigger_mode x absent OK +schedule.sql_condition.trigger_mode x y OK +schedule.sql_condition.trigger_mode y absent OK +schedule.sql_condition.trigger_mode y x OK +schedule.sql_condition.warehouse_id x y OK +schedule.sql_condition.warehouse_id y x OK +schedule.timezone_id x y OK +schedule.timezone_id y x OK +timeout_seconds 1200 600 OK +timeout_seconds 1200 absent OK +timeout_seconds 600 1200 OK +timeout_seconds 600 absent OK +timeout_seconds absent 1200 OK +timeout_seconds absent 600 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK +trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK +trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK +trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK +trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK +trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK +trigger.file_arrival.url x y OK +trigger.file_arrival.url y x OK +trigger.file_arrival.wait_after_last_change_seconds 1 2 OK +trigger.file_arrival.wait_after_last_change_seconds 1 absent OK +trigger.file_arrival.wait_after_last_change_seconds 2 1 OK +trigger.file_arrival.wait_after_last_change_seconds 2 absent OK +trigger.file_arrival.wait_after_last_change_seconds absent 1 OK +trigger.file_arrival.wait_after_last_change_seconds absent 2 OK +trigger.model.condition x y OK +trigger.model.condition y x OK +trigger.model.min_time_between_triggers_seconds 1 2 OK +trigger.model.min_time_between_triggers_seconds 1 absent OK +trigger.model.min_time_between_triggers_seconds 2 1 OK +trigger.model.min_time_between_triggers_seconds 2 absent OK +trigger.model.min_time_between_triggers_seconds absent 1 OK +trigger.model.min_time_between_triggers_seconds absent 2 OK +trigger.model.securable_name absent x OK +trigger.model.securable_name absent y OK +trigger.model.securable_name x absent OK +trigger.model.securable_name x y OK +trigger.model.securable_name y absent OK +trigger.model.securable_name y x OK +trigger.model.wait_after_last_change_seconds 1 2 OK +trigger.model.wait_after_last_change_seconds 1 absent OK +trigger.model.wait_after_last_change_seconds 2 1 OK +trigger.model.wait_after_last_change_seconds 2 absent OK +trigger.model.wait_after_last_change_seconds absent 1 OK +trigger.model.wait_after_last_change_seconds absent 2 OK +trigger.pause_status absent x OK +trigger.pause_status absent y OK +trigger.pause_status x absent OK +trigger.pause_status x y OK +trigger.pause_status y absent OK +trigger.pause_status y x OK +trigger.periodic.interval 1 2 OK +trigger.periodic.interval 2 1 OK +trigger.periodic.unit x y OK +trigger.periodic.unit y x OK +trigger.sql_condition.sql_query_id x y OK +trigger.sql_condition.sql_query_id y x OK +trigger.sql_condition.trigger_mode absent x OK +trigger.sql_condition.trigger_mode absent y OK +trigger.sql_condition.trigger_mode x absent OK +trigger.sql_condition.trigger_mode x y OK +trigger.sql_condition.trigger_mode y absent OK +trigger.sql_condition.trigger_mode y x OK +trigger.sql_condition.warehouse_id x y OK +trigger.sql_condition.warehouse_id y x OK +trigger.table_update.condition absent x OK +trigger.table_update.condition absent y OK +trigger.table_update.condition x absent OK +trigger.table_update.condition x y OK +trigger.table_update.condition y absent OK +trigger.table_update.condition y x OK +trigger.table_update.min_time_between_triggers_seconds 1 2 OK +trigger.table_update.min_time_between_triggers_seconds 1 absent OK +trigger.table_update.min_time_between_triggers_seconds 2 1 OK +trigger.table_update.min_time_between_triggers_seconds 2 absent OK +trigger.table_update.min_time_between_triggers_seconds absent 1 OK +trigger.table_update.min_time_between_triggers_seconds absent 2 OK +trigger.table_update.wait_after_last_change_seconds 1 2 OK +trigger.table_update.wait_after_last_change_seconds 1 absent OK +trigger.table_update.wait_after_last_change_seconds 2 1 OK +trigger.table_update.wait_after_last_change_seconds 2 absent OK +trigger.table_update.wait_after_last_change_seconds absent 1 OK +trigger.table_update.wait_after_last_change_seconds absent 2 OK +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent OK +usage_policy_id x y OK +usage_policy_id y absent OK +usage_policy_id y x OK + +=== summary +OK 1272 +SUPPRESSED 48 +NOT_OBSERVABLE 48 +SKIPPED 54 + +=== not covered: 614 fields under a slice or map +email_notifications.on_duration_warning_threshold_exceeded[*] +email_notifications.on_failure[*] +email_notifications.on_start[*] +email_notifications.on_streaming_backlog_exceeded[*] +email_notifications.on_success[*] +environments[*].environment_key +environments[*].spec.base_environment +environments[*].spec.client +environments[*].spec.dependencies[*] +environments[*].spec.environment_version +environments[*].spec.java_dependencies[*] +git_source.sparse_checkout.patterns[*] +health.rules[*].metric +health.rules[*].op +health.rules[*].value +job_clusters[*].job_cluster_key +job_clusters[*].new_cluster.apply_policy_default_values +job_clusters[*].new_cluster.autoscale.max_workers +job_clusters[*].new_cluster.autoscale.min_workers +job_clusters[*].new_cluster.autotermination_minutes +job_clusters[*].new_cluster.aws_attributes.availability +job_clusters[*].new_cluster.aws_attributes.ebs_volume_count +job_clusters[*].new_cluster.aws_attributes.ebs_volume_iops +job_clusters[*].new_cluster.aws_attributes.ebs_volume_size +job_clusters[*].new_cluster.aws_attributes.ebs_volume_throughput +job_clusters[*].new_cluster.aws_attributes.ebs_volume_type +job_clusters[*].new_cluster.aws_attributes.first_on_demand +job_clusters[*].new_cluster.aws_attributes.instance_profile_arn +job_clusters[*].new_cluster.aws_attributes.spot_bid_price_percent +job_clusters[*].new_cluster.aws_attributes.zone_id +job_clusters[*].new_cluster.azure_attributes.availability +job_clusters[*].new_cluster.azure_attributes.capacity_reservation_group +job_clusters[*].new_cluster.azure_attributes.first_on_demand +job_clusters[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key +job_clusters[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id +job_clusters[*].new_cluster.azure_attributes.spot_bid_max_price +job_clusters[*].new_cluster.cluster_log_conf.dbfs.destination +job_clusters[*].new_cluster.cluster_log_conf.s3.canned_acl +job_clusters[*].new_cluster.cluster_log_conf.s3.destination +job_clusters[*].new_cluster.cluster_log_conf.s3.enable_encryption +job_clusters[*].new_cluster.cluster_log_conf.s3.encryption_type +job_clusters[*].new_cluster.cluster_log_conf.s3.endpoint +job_clusters[*].new_cluster.cluster_log_conf.s3.kms_key +job_clusters[*].new_cluster.cluster_log_conf.s3.region +job_clusters[*].new_cluster.cluster_log_conf.volumes.destination +job_clusters[*].new_cluster.cluster_name +job_clusters[*].new_cluster.custom_tags.* +job_clusters[*].new_cluster.data_security_mode +job_clusters[*].new_cluster.dependency_mode +job_clusters[*].new_cluster.docker_image.basic_auth.password +job_clusters[*].new_cluster.docker_image.basic_auth.username +job_clusters[*].new_cluster.docker_image.url +job_clusters[*].new_cluster.driver_instance_pool_id +job_clusters[*].new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] +job_clusters[*].new_cluster.driver_node_type_id +job_clusters[*].new_cluster.enable_elastic_disk +job_clusters[*].new_cluster.enable_local_disk_encryption +job_clusters[*].new_cluster.gcp_attributes.availability +job_clusters[*].new_cluster.gcp_attributes.boot_disk_size +job_clusters[*].new_cluster.gcp_attributes.confidential_compute_type +job_clusters[*].new_cluster.gcp_attributes.first_on_demand +job_clusters[*].new_cluster.gcp_attributes.google_service_account +job_clusters[*].new_cluster.gcp_attributes.local_ssd_count +job_clusters[*].new_cluster.gcp_attributes.use_preemptible_executors +job_clusters[*].new_cluster.gcp_attributes.zone_id +job_clusters[*].new_cluster.init_scripts[*].abfss.destination +job_clusters[*].new_cluster.init_scripts[*].dbfs.destination +job_clusters[*].new_cluster.init_scripts[*].file.destination +job_clusters[*].new_cluster.init_scripts[*].gcs.destination +job_clusters[*].new_cluster.init_scripts[*].s3.canned_acl +job_clusters[*].new_cluster.init_scripts[*].s3.destination +job_clusters[*].new_cluster.init_scripts[*].s3.enable_encryption +job_clusters[*].new_cluster.init_scripts[*].s3.encryption_type +job_clusters[*].new_cluster.init_scripts[*].s3.endpoint +job_clusters[*].new_cluster.init_scripts[*].s3.kms_key +job_clusters[*].new_cluster.init_scripts[*].s3.region +job_clusters[*].new_cluster.init_scripts[*].volumes.destination +job_clusters[*].new_cluster.init_scripts[*].workspace.destination +job_clusters[*].new_cluster.instance_pool_id +job_clusters[*].new_cluster.is_single_node +job_clusters[*].new_cluster.kind +job_clusters[*].new_cluster.node_type_id +job_clusters[*].new_cluster.num_workers +job_clusters[*].new_cluster.policy_id +job_clusters[*].new_cluster.remote_disk_throughput +job_clusters[*].new_cluster.runtime_engine +job_clusters[*].new_cluster.single_user_name +job_clusters[*].new_cluster.spark_conf.* +job_clusters[*].new_cluster.spark_env_vars.* +job_clusters[*].new_cluster.spark_version +job_clusters[*].new_cluster.ssh_public_keys[*] +job_clusters[*].new_cluster.total_initial_remote_disk_size +job_clusters[*].new_cluster.use_ml_runtime +job_clusters[*].new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] +job_clusters[*].new_cluster.workload_type.clients.jobs +job_clusters[*].new_cluster.workload_type.clients.notebooks +job_clusters[*].serverless_compute_id +parameters[*].default +parameters[*].name +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +tags.* +tasks[*].ai_runtime_task.code_source_path +tasks[*].ai_runtime_task.deployments[*].command_path +tasks[*].ai_runtime_task.deployments[*].compute.accelerator_count +tasks[*].ai_runtime_task.deployments[*].compute.accelerator_type +tasks[*].ai_runtime_task.deployments[*].name +tasks[*].ai_runtime_task.docker_image_url +tasks[*].ai_runtime_task.experiment +tasks[*].ai_runtime_task.mlflow_artifact_location +tasks[*].ai_runtime_task.mlflow_experiment_directory +tasks[*].ai_runtime_task.mlflow_run +tasks[*].alert_task.alert_id +tasks[*].alert_task.subscribers[*].destination_id +tasks[*].alert_task.subscribers[*].user_name +tasks[*].alert_task.warehouse_id +tasks[*].alert_task.workspace_path +tasks[*].clean_rooms_notebook_task.clean_room_name +tasks[*].clean_rooms_notebook_task.etag +tasks[*].clean_rooms_notebook_task.notebook_base_parameters.* +tasks[*].clean_rooms_notebook_task.notebook_name +tasks[*].compute.hardware_accelerator +tasks[*].condition_task.left +tasks[*].condition_task.op +tasks[*].condition_task.right +tasks[*].dashboard_task.dashboard_id +tasks[*].dashboard_task.filters.* +tasks[*].dashboard_task.subscription.custom_subject +tasks[*].dashboard_task.subscription.paused +tasks[*].dashboard_task.subscription.subscribers[*].destination_id +tasks[*].dashboard_task.subscription.subscribers[*].user_name +tasks[*].dashboard_task.warehouse_id +tasks[*].dbt_cloud_task.connection_resource_name +tasks[*].dbt_cloud_task.dbt_cloud_job_id +tasks[*].dbt_platform_task.connection_resource_name +tasks[*].dbt_platform_task.dbt_platform_job_id +tasks[*].dbt_task.catalog +tasks[*].dbt_task.commands[*] +tasks[*].dbt_task.profiles_directory +tasks[*].dbt_task.project_directory +tasks[*].dbt_task.schema +tasks[*].dbt_task.source +tasks[*].dbt_task.warehouse_id +tasks[*].depends_on[*].outcome +tasks[*].depends_on[*].task_key +tasks[*].description +tasks[*].disable_auto_optimization +tasks[*].disabled +tasks[*].email_notifications.no_alert_for_skipped_runs +tasks[*].email_notifications.on_duration_warning_threshold_exceeded[*] +tasks[*].email_notifications.on_failure[*] +tasks[*].email_notifications.on_start[*] +tasks[*].email_notifications.on_streaming_backlog_exceeded[*] +tasks[*].email_notifications.on_success[*] +tasks[*].environment_key +tasks[*].existing_cluster_id +tasks[*].for_each_task.concurrency +tasks[*].for_each_task.inputs +tasks[*].for_each_task.task.ai_runtime_task.code_source_path +tasks[*].for_each_task.task.ai_runtime_task.deployments[*].command_path +tasks[*].for_each_task.task.ai_runtime_task.deployments[*].compute.accelerator_count +tasks[*].for_each_task.task.ai_runtime_task.deployments[*].compute.accelerator_type +tasks[*].for_each_task.task.ai_runtime_task.deployments[*].name +tasks[*].for_each_task.task.ai_runtime_task.docker_image_url +tasks[*].for_each_task.task.ai_runtime_task.experiment +tasks[*].for_each_task.task.ai_runtime_task.mlflow_artifact_location +tasks[*].for_each_task.task.ai_runtime_task.mlflow_experiment_directory +tasks[*].for_each_task.task.ai_runtime_task.mlflow_run +tasks[*].for_each_task.task.alert_task.alert_id +tasks[*].for_each_task.task.alert_task.subscribers[*].destination_id +tasks[*].for_each_task.task.alert_task.subscribers[*].user_name +tasks[*].for_each_task.task.alert_task.warehouse_id +tasks[*].for_each_task.task.alert_task.workspace_path +tasks[*].for_each_task.task.clean_rooms_notebook_task.clean_room_name +tasks[*].for_each_task.task.clean_rooms_notebook_task.etag +tasks[*].for_each_task.task.clean_rooms_notebook_task.notebook_base_parameters.* +tasks[*].for_each_task.task.clean_rooms_notebook_task.notebook_name +tasks[*].for_each_task.task.compute.hardware_accelerator +tasks[*].for_each_task.task.condition_task.left +tasks[*].for_each_task.task.condition_task.op +tasks[*].for_each_task.task.condition_task.right +tasks[*].for_each_task.task.dashboard_task.dashboard_id +tasks[*].for_each_task.task.dashboard_task.filters.* +tasks[*].for_each_task.task.dashboard_task.subscription.custom_subject +tasks[*].for_each_task.task.dashboard_task.subscription.paused +tasks[*].for_each_task.task.dashboard_task.subscription.subscribers[*].destination_id +tasks[*].for_each_task.task.dashboard_task.subscription.subscribers[*].user_name +tasks[*].for_each_task.task.dashboard_task.warehouse_id +tasks[*].for_each_task.task.dbt_cloud_task.connection_resource_name +tasks[*].for_each_task.task.dbt_cloud_task.dbt_cloud_job_id +tasks[*].for_each_task.task.dbt_platform_task.connection_resource_name +tasks[*].for_each_task.task.dbt_platform_task.dbt_platform_job_id +tasks[*].for_each_task.task.dbt_task.catalog +tasks[*].for_each_task.task.dbt_task.commands[*] +tasks[*].for_each_task.task.dbt_task.profiles_directory +tasks[*].for_each_task.task.dbt_task.project_directory +tasks[*].for_each_task.task.dbt_task.schema +tasks[*].for_each_task.task.dbt_task.source +tasks[*].for_each_task.task.dbt_task.warehouse_id +tasks[*].for_each_task.task.depends_on[*].outcome +tasks[*].for_each_task.task.depends_on[*].task_key +tasks[*].for_each_task.task.description +tasks[*].for_each_task.task.disable_auto_optimization +tasks[*].for_each_task.task.disabled +tasks[*].for_each_task.task.email_notifications.no_alert_for_skipped_runs +tasks[*].for_each_task.task.email_notifications.on_duration_warning_threshold_exceeded[*] +tasks[*].for_each_task.task.email_notifications.on_failure[*] +tasks[*].for_each_task.task.email_notifications.on_start[*] +tasks[*].for_each_task.task.email_notifications.on_streaming_backlog_exceeded[*] +tasks[*].for_each_task.task.email_notifications.on_success[*] +tasks[*].for_each_task.task.environment_key +tasks[*].for_each_task.task.existing_cluster_id +tasks[*].for_each_task.task.for_each_task.concurrency +tasks[*].for_each_task.task.for_each_task.inputs +tasks[*].for_each_task.task.gen_ai_compute_task.command +tasks[*].for_each_task.task.gen_ai_compute_task.compute.gpu_node_pool_id +tasks[*].for_each_task.task.gen_ai_compute_task.compute.gpu_type +tasks[*].for_each_task.task.gen_ai_compute_task.compute.num_gpus +tasks[*].for_each_task.task.gen_ai_compute_task.dl_runtime_image +tasks[*].for_each_task.task.gen_ai_compute_task.mlflow_experiment_name +tasks[*].for_each_task.task.gen_ai_compute_task.source +tasks[*].for_each_task.task.gen_ai_compute_task.training_script_path +tasks[*].for_each_task.task.gen_ai_compute_task.yaml_parameters +tasks[*].for_each_task.task.gen_ai_compute_task.yaml_parameters_file_path +tasks[*].for_each_task.task.health.rules[*].metric +tasks[*].for_each_task.task.health.rules[*].op +tasks[*].for_each_task.task.health.rules[*].value +tasks[*].for_each_task.task.job_cluster_key +tasks[*].for_each_task.task.libraries[*].cran.package +tasks[*].for_each_task.task.libraries[*].cran.repo +tasks[*].for_each_task.task.libraries[*].egg +tasks[*].for_each_task.task.libraries[*].jar +tasks[*].for_each_task.task.libraries[*].maven.coordinates +tasks[*].for_each_task.task.libraries[*].maven.exclusions[*] +tasks[*].for_each_task.task.libraries[*].maven.repo +tasks[*].for_each_task.task.libraries[*].pypi.package +tasks[*].for_each_task.task.libraries[*].pypi.repo +tasks[*].for_each_task.task.libraries[*].requirements +tasks[*].for_each_task.task.libraries[*].whl +tasks[*].for_each_task.task.max_retries +tasks[*].for_each_task.task.min_retry_interval_millis +tasks[*].for_each_task.task.new_cluster.apply_policy_default_values +tasks[*].for_each_task.task.new_cluster.autoscale.max_workers +tasks[*].for_each_task.task.new_cluster.autoscale.min_workers +tasks[*].for_each_task.task.new_cluster.autotermination_minutes +tasks[*].for_each_task.task.new_cluster.aws_attributes.availability +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_count +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_iops +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_size +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_throughput +tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_type +tasks[*].for_each_task.task.new_cluster.aws_attributes.first_on_demand +tasks[*].for_each_task.task.new_cluster.aws_attributes.instance_profile_arn +tasks[*].for_each_task.task.new_cluster.aws_attributes.spot_bid_price_percent +tasks[*].for_each_task.task.new_cluster.aws_attributes.zone_id +tasks[*].for_each_task.task.new_cluster.azure_attributes.availability +tasks[*].for_each_task.task.new_cluster.azure_attributes.capacity_reservation_group +tasks[*].for_each_task.task.new_cluster.azure_attributes.first_on_demand +tasks[*].for_each_task.task.new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key +tasks[*].for_each_task.task.new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id +tasks[*].for_each_task.task.new_cluster.azure_attributes.spot_bid_max_price +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.dbfs.destination +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.canned_acl +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.destination +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.enable_encryption +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.encryption_type +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.endpoint +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.kms_key +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.region +tasks[*].for_each_task.task.new_cluster.cluster_log_conf.volumes.destination +tasks[*].for_each_task.task.new_cluster.cluster_name +tasks[*].for_each_task.task.new_cluster.custom_tags.* +tasks[*].for_each_task.task.new_cluster.data_security_mode +tasks[*].for_each_task.task.new_cluster.dependency_mode +tasks[*].for_each_task.task.new_cluster.docker_image.basic_auth.password +tasks[*].for_each_task.task.new_cluster.docker_image.basic_auth.username +tasks[*].for_each_task.task.new_cluster.docker_image.url +tasks[*].for_each_task.task.new_cluster.driver_instance_pool_id +tasks[*].for_each_task.task.new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] +tasks[*].for_each_task.task.new_cluster.driver_node_type_id +tasks[*].for_each_task.task.new_cluster.enable_elastic_disk +tasks[*].for_each_task.task.new_cluster.enable_local_disk_encryption +tasks[*].for_each_task.task.new_cluster.gcp_attributes.availability +tasks[*].for_each_task.task.new_cluster.gcp_attributes.boot_disk_size +tasks[*].for_each_task.task.new_cluster.gcp_attributes.confidential_compute_type +tasks[*].for_each_task.task.new_cluster.gcp_attributes.first_on_demand +tasks[*].for_each_task.task.new_cluster.gcp_attributes.google_service_account +tasks[*].for_each_task.task.new_cluster.gcp_attributes.local_ssd_count +tasks[*].for_each_task.task.new_cluster.gcp_attributes.use_preemptible_executors +tasks[*].for_each_task.task.new_cluster.gcp_attributes.zone_id +tasks[*].for_each_task.task.new_cluster.init_scripts[*].abfss.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].dbfs.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].file.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].gcs.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.canned_acl +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.enable_encryption +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.encryption_type +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.endpoint +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.kms_key +tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.region +tasks[*].for_each_task.task.new_cluster.init_scripts[*].volumes.destination +tasks[*].for_each_task.task.new_cluster.init_scripts[*].workspace.destination +tasks[*].for_each_task.task.new_cluster.instance_pool_id +tasks[*].for_each_task.task.new_cluster.is_single_node +tasks[*].for_each_task.task.new_cluster.kind +tasks[*].for_each_task.task.new_cluster.node_type_id +tasks[*].for_each_task.task.new_cluster.num_workers +tasks[*].for_each_task.task.new_cluster.policy_id +tasks[*].for_each_task.task.new_cluster.remote_disk_throughput +tasks[*].for_each_task.task.new_cluster.runtime_engine +tasks[*].for_each_task.task.new_cluster.single_user_name +tasks[*].for_each_task.task.new_cluster.spark_conf.* +tasks[*].for_each_task.task.new_cluster.spark_env_vars.* +tasks[*].for_each_task.task.new_cluster.spark_version +tasks[*].for_each_task.task.new_cluster.ssh_public_keys[*] +tasks[*].for_each_task.task.new_cluster.total_initial_remote_disk_size +tasks[*].for_each_task.task.new_cluster.use_ml_runtime +tasks[*].for_each_task.task.new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] +tasks[*].for_each_task.task.new_cluster.workload_type.clients.jobs +tasks[*].for_each_task.task.new_cluster.workload_type.clients.notebooks +tasks[*].for_each_task.task.notebook_task.base_parameters.* +tasks[*].for_each_task.task.notebook_task.notebook_path +tasks[*].for_each_task.task.notebook_task.source +tasks[*].for_each_task.task.notebook_task.warehouse_id +tasks[*].for_each_task.task.notification_settings.alert_on_last_attempt +tasks[*].for_each_task.task.notification_settings.no_alert_for_canceled_runs +tasks[*].for_each_task.task.notification_settings.no_alert_for_skipped_runs +tasks[*].for_each_task.task.pipeline_task.full_refresh +tasks[*].for_each_task.task.pipeline_task.full_refresh_selection[*] +tasks[*].for_each_task.task.pipeline_task.parameters.* +tasks[*].for_each_task.task.pipeline_task.pipeline_id +tasks[*].for_each_task.task.pipeline_task.refresh_flow_selection[*] +tasks[*].for_each_task.task.pipeline_task.refresh_selection[*] +tasks[*].for_each_task.task.pipeline_task.reset_checkpoint_selection[*] +tasks[*].for_each_task.task.power_bi_task.connection_resource_name +tasks[*].for_each_task.task.power_bi_task.power_bi_model.authentication_method +tasks[*].for_each_task.task.power_bi_task.power_bi_model.model_name +tasks[*].for_each_task.task.power_bi_task.power_bi_model.overwrite_existing +tasks[*].for_each_task.task.power_bi_task.power_bi_model.storage_mode +tasks[*].for_each_task.task.power_bi_task.power_bi_model.workspace_name +tasks[*].for_each_task.task.power_bi_task.refresh_after_update +tasks[*].for_each_task.task.power_bi_task.tables[*].catalog +tasks[*].for_each_task.task.power_bi_task.tables[*].name +tasks[*].for_each_task.task.power_bi_task.tables[*].schema +tasks[*].for_each_task.task.power_bi_task.tables[*].storage_mode +tasks[*].for_each_task.task.power_bi_task.warehouse_id +tasks[*].for_each_task.task.python_operator_task.main +tasks[*].for_each_task.task.python_operator_task.parameters[*].name +tasks[*].for_each_task.task.python_operator_task.parameters[*].value +tasks[*].for_each_task.task.python_wheel_task.entry_point +tasks[*].for_each_task.task.python_wheel_task.named_parameters.* +tasks[*].for_each_task.task.python_wheel_task.package_name +tasks[*].for_each_task.task.python_wheel_task.parameters[*] +tasks[*].for_each_task.task.retry_on_timeout +tasks[*].for_each_task.task.run_if +tasks[*].for_each_task.task.run_job_task.dbt_commands[*] +tasks[*].for_each_task.task.run_job_task.jar_params[*] +tasks[*].for_each_task.task.run_job_task.job_id +tasks[*].for_each_task.task.run_job_task.job_parameters.* +tasks[*].for_each_task.task.run_job_task.notebook_params.* +tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh +tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh_selection[*] +tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_flow_selection[*] +tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_selection[*] +tasks[*].for_each_task.task.run_job_task.pipeline_params.reset_checkpoint_selection[*] +tasks[*].for_each_task.task.run_job_task.python_named_params.* +tasks[*].for_each_task.task.run_job_task.python_params[*] +tasks[*].for_each_task.task.run_job_task.spark_submit_params[*] +tasks[*].for_each_task.task.run_job_task.sql_params.* +tasks[*].for_each_task.task.spark_jar_task.jar_uri +tasks[*].for_each_task.task.spark_jar_task.main_class_name +tasks[*].for_each_task.task.spark_jar_task.parameters[*] +tasks[*].for_each_task.task.spark_jar_task.run_as_repl +tasks[*].for_each_task.task.spark_python_task.parameters[*] +tasks[*].for_each_task.task.spark_python_task.python_file +tasks[*].for_each_task.task.spark_python_task.source +tasks[*].for_each_task.task.spark_submit_task.parameters[*] +tasks[*].for_each_task.task.sql_task.alert.alert_id +tasks[*].for_each_task.task.sql_task.alert.pause_subscriptions +tasks[*].for_each_task.task.sql_task.alert.subscriptions[*].destination_id +tasks[*].for_each_task.task.sql_task.alert.subscriptions[*].user_name +tasks[*].for_each_task.task.sql_task.dashboard.custom_subject +tasks[*].for_each_task.task.sql_task.dashboard.dashboard_id +tasks[*].for_each_task.task.sql_task.dashboard.pause_subscriptions +tasks[*].for_each_task.task.sql_task.dashboard.subscriptions[*].destination_id +tasks[*].for_each_task.task.sql_task.dashboard.subscriptions[*].user_name +tasks[*].for_each_task.task.sql_task.file.path +tasks[*].for_each_task.task.sql_task.file.source +tasks[*].for_each_task.task.sql_task.parameters.* +tasks[*].for_each_task.task.sql_task.query.query_id +tasks[*].for_each_task.task.sql_task.warehouse_id +tasks[*].for_each_task.task.task_key +tasks[*].for_each_task.task.timeout_seconds +tasks[*].for_each_task.task.webhook_notifications.on_duration_warning_threshold_exceeded[*].id +tasks[*].for_each_task.task.webhook_notifications.on_failure[*].id +tasks[*].for_each_task.task.webhook_notifications.on_start[*].id +tasks[*].for_each_task.task.webhook_notifications.on_streaming_backlog_exceeded[*].id +tasks[*].for_each_task.task.webhook_notifications.on_success[*].id +tasks[*].gen_ai_compute_task.command +tasks[*].gen_ai_compute_task.compute.gpu_node_pool_id +tasks[*].gen_ai_compute_task.compute.gpu_type +tasks[*].gen_ai_compute_task.compute.num_gpus +tasks[*].gen_ai_compute_task.dl_runtime_image +tasks[*].gen_ai_compute_task.mlflow_experiment_name +tasks[*].gen_ai_compute_task.source +tasks[*].gen_ai_compute_task.training_script_path +tasks[*].gen_ai_compute_task.yaml_parameters +tasks[*].gen_ai_compute_task.yaml_parameters_file_path +tasks[*].health.rules[*].metric +tasks[*].health.rules[*].op +tasks[*].health.rules[*].value +tasks[*].job_cluster_key +tasks[*].libraries[*].cran.package +tasks[*].libraries[*].cran.repo +tasks[*].libraries[*].egg +tasks[*].libraries[*].jar +tasks[*].libraries[*].maven.coordinates +tasks[*].libraries[*].maven.exclusions[*] +tasks[*].libraries[*].maven.repo +tasks[*].libraries[*].pypi.package +tasks[*].libraries[*].pypi.repo +tasks[*].libraries[*].requirements +tasks[*].libraries[*].whl +tasks[*].max_retries +tasks[*].min_retry_interval_millis +tasks[*].new_cluster.apply_policy_default_values +tasks[*].new_cluster.autoscale.max_workers +tasks[*].new_cluster.autoscale.min_workers +tasks[*].new_cluster.autotermination_minutes +tasks[*].new_cluster.aws_attributes.availability +tasks[*].new_cluster.aws_attributes.ebs_volume_count +tasks[*].new_cluster.aws_attributes.ebs_volume_iops +tasks[*].new_cluster.aws_attributes.ebs_volume_size +tasks[*].new_cluster.aws_attributes.ebs_volume_throughput +tasks[*].new_cluster.aws_attributes.ebs_volume_type +tasks[*].new_cluster.aws_attributes.first_on_demand +tasks[*].new_cluster.aws_attributes.instance_profile_arn +tasks[*].new_cluster.aws_attributes.spot_bid_price_percent +tasks[*].new_cluster.aws_attributes.zone_id +tasks[*].new_cluster.azure_attributes.availability +tasks[*].new_cluster.azure_attributes.capacity_reservation_group +tasks[*].new_cluster.azure_attributes.first_on_demand +tasks[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key +tasks[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id +tasks[*].new_cluster.azure_attributes.spot_bid_max_price +tasks[*].new_cluster.cluster_log_conf.dbfs.destination +tasks[*].new_cluster.cluster_log_conf.s3.canned_acl +tasks[*].new_cluster.cluster_log_conf.s3.destination +tasks[*].new_cluster.cluster_log_conf.s3.enable_encryption +tasks[*].new_cluster.cluster_log_conf.s3.encryption_type +tasks[*].new_cluster.cluster_log_conf.s3.endpoint +tasks[*].new_cluster.cluster_log_conf.s3.kms_key +tasks[*].new_cluster.cluster_log_conf.s3.region +tasks[*].new_cluster.cluster_log_conf.volumes.destination +tasks[*].new_cluster.cluster_name +tasks[*].new_cluster.custom_tags.* +tasks[*].new_cluster.data_security_mode +tasks[*].new_cluster.dependency_mode +tasks[*].new_cluster.docker_image.basic_auth.password +tasks[*].new_cluster.docker_image.basic_auth.username +tasks[*].new_cluster.docker_image.url +tasks[*].new_cluster.driver_instance_pool_id +tasks[*].new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] +tasks[*].new_cluster.driver_node_type_id +tasks[*].new_cluster.enable_elastic_disk +tasks[*].new_cluster.enable_local_disk_encryption +tasks[*].new_cluster.gcp_attributes.availability +tasks[*].new_cluster.gcp_attributes.boot_disk_size +tasks[*].new_cluster.gcp_attributes.confidential_compute_type +tasks[*].new_cluster.gcp_attributes.first_on_demand +tasks[*].new_cluster.gcp_attributes.google_service_account +tasks[*].new_cluster.gcp_attributes.local_ssd_count +tasks[*].new_cluster.gcp_attributes.use_preemptible_executors +tasks[*].new_cluster.gcp_attributes.zone_id +tasks[*].new_cluster.init_scripts[*].abfss.destination +tasks[*].new_cluster.init_scripts[*].dbfs.destination +tasks[*].new_cluster.init_scripts[*].file.destination +tasks[*].new_cluster.init_scripts[*].gcs.destination +tasks[*].new_cluster.init_scripts[*].s3.canned_acl +tasks[*].new_cluster.init_scripts[*].s3.destination +tasks[*].new_cluster.init_scripts[*].s3.enable_encryption +tasks[*].new_cluster.init_scripts[*].s3.encryption_type +tasks[*].new_cluster.init_scripts[*].s3.endpoint +tasks[*].new_cluster.init_scripts[*].s3.kms_key +tasks[*].new_cluster.init_scripts[*].s3.region +tasks[*].new_cluster.init_scripts[*].volumes.destination +tasks[*].new_cluster.init_scripts[*].workspace.destination +tasks[*].new_cluster.instance_pool_id +tasks[*].new_cluster.is_single_node +tasks[*].new_cluster.kind +tasks[*].new_cluster.node_type_id +tasks[*].new_cluster.num_workers +tasks[*].new_cluster.policy_id +tasks[*].new_cluster.remote_disk_throughput +tasks[*].new_cluster.runtime_engine +tasks[*].new_cluster.single_user_name +tasks[*].new_cluster.spark_conf.* +tasks[*].new_cluster.spark_env_vars.* +tasks[*].new_cluster.spark_version +tasks[*].new_cluster.ssh_public_keys[*] +tasks[*].new_cluster.total_initial_remote_disk_size +tasks[*].new_cluster.use_ml_runtime +tasks[*].new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] +tasks[*].new_cluster.workload_type.clients.jobs +tasks[*].new_cluster.workload_type.clients.notebooks +tasks[*].notebook_task.base_parameters.* +tasks[*].notebook_task.notebook_path +tasks[*].notebook_task.source +tasks[*].notebook_task.warehouse_id +tasks[*].notification_settings.alert_on_last_attempt +tasks[*].notification_settings.no_alert_for_canceled_runs +tasks[*].notification_settings.no_alert_for_skipped_runs +tasks[*].pipeline_task.full_refresh +tasks[*].pipeline_task.full_refresh_selection[*] +tasks[*].pipeline_task.parameters.* +tasks[*].pipeline_task.pipeline_id +tasks[*].pipeline_task.refresh_flow_selection[*] +tasks[*].pipeline_task.refresh_selection[*] +tasks[*].pipeline_task.reset_checkpoint_selection[*] +tasks[*].power_bi_task.connection_resource_name +tasks[*].power_bi_task.power_bi_model.authentication_method +tasks[*].power_bi_task.power_bi_model.model_name +tasks[*].power_bi_task.power_bi_model.overwrite_existing +tasks[*].power_bi_task.power_bi_model.storage_mode +tasks[*].power_bi_task.power_bi_model.workspace_name +tasks[*].power_bi_task.refresh_after_update +tasks[*].power_bi_task.tables[*].catalog +tasks[*].power_bi_task.tables[*].name +tasks[*].power_bi_task.tables[*].schema +tasks[*].power_bi_task.tables[*].storage_mode +tasks[*].power_bi_task.warehouse_id +tasks[*].python_operator_task.main +tasks[*].python_operator_task.parameters[*].name +tasks[*].python_operator_task.parameters[*].value +tasks[*].python_wheel_task.entry_point +tasks[*].python_wheel_task.named_parameters.* +tasks[*].python_wheel_task.package_name +tasks[*].python_wheel_task.parameters[*] +tasks[*].retry_on_timeout +tasks[*].run_if +tasks[*].run_job_task.dbt_commands[*] +tasks[*].run_job_task.jar_params[*] +tasks[*].run_job_task.job_id +tasks[*].run_job_task.job_parameters.* +tasks[*].run_job_task.notebook_params.* +tasks[*].run_job_task.pipeline_params.full_refresh +tasks[*].run_job_task.pipeline_params.full_refresh_selection[*] +tasks[*].run_job_task.pipeline_params.refresh_flow_selection[*] +tasks[*].run_job_task.pipeline_params.refresh_selection[*] +tasks[*].run_job_task.pipeline_params.reset_checkpoint_selection[*] +tasks[*].run_job_task.python_named_params.* +tasks[*].run_job_task.python_params[*] +tasks[*].run_job_task.spark_submit_params[*] +tasks[*].run_job_task.sql_params.* +tasks[*].spark_jar_task.jar_uri +tasks[*].spark_jar_task.main_class_name +tasks[*].spark_jar_task.parameters[*] +tasks[*].spark_jar_task.run_as_repl +tasks[*].spark_python_task.parameters[*] +tasks[*].spark_python_task.python_file +tasks[*].spark_python_task.source +tasks[*].spark_submit_task.parameters[*] +tasks[*].sql_task.alert.alert_id +tasks[*].sql_task.alert.pause_subscriptions +tasks[*].sql_task.alert.subscriptions[*].destination_id +tasks[*].sql_task.alert.subscriptions[*].user_name +tasks[*].sql_task.dashboard.custom_subject +tasks[*].sql_task.dashboard.dashboard_id +tasks[*].sql_task.dashboard.pause_subscriptions +tasks[*].sql_task.dashboard.subscriptions[*].destination_id +tasks[*].sql_task.dashboard.subscriptions[*].user_name +tasks[*].sql_task.file.path +tasks[*].sql_task.file.source +tasks[*].sql_task.parameters.* +tasks[*].sql_task.query.query_id +tasks[*].sql_task.warehouse_id +tasks[*].task_key +tasks[*].timeout_seconds +tasks[*].webhook_notifications.on_duration_warning_threshold_exceeded[*].id +tasks[*].webhook_notifications.on_failure[*].id +tasks[*].webhook_notifications.on_start[*].id +tasks[*].webhook_notifications.on_streaming_backlog_exceeded[*].id +tasks[*].webhook_notifications.on_success[*].id +trigger.model.aliases[*] +trigger.table_update.table_names[*] +triggers[*].continuous.task_retry_mode +triggers[*].file_arrival.min_time_between_triggers_seconds +triggers[*].file_arrival.url +triggers[*].file_arrival.wait_after_last_change_seconds +triggers[*].model.aliases[*] +triggers[*].model.condition +triggers[*].model.min_time_between_triggers_seconds +triggers[*].model.securable_name +triggers[*].model.wait_after_last_change_seconds +triggers[*].pause_status +triggers[*].periodic.interval +triggers[*].periodic.unit +triggers[*].schedule.quartz_cron_expression +triggers[*].schedule.timezone_id +triggers[*].sql_condition.sql_query_id +triggers[*].sql_condition.trigger_mode +triggers[*].sql_condition.warehouse_id +triggers[*].table_update.condition +triggers[*].table_update.min_time_between_triggers_seconds +triggers[*].table_update.table_names[*] +triggers[*].table_update.wait_after_last_change_seconds +webhook_notifications.on_duration_warning_threshold_exceeded[*].id +webhook_notifications.on_failure[*].id +webhook_notifications.on_start[*].id +webhook_notifications.on_streaming_backlog_exceeded[*].id +webhook_notifications.on_success[*].id diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index 3921160ee74..fbcafc2d677 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -1,2055 +1,35 @@ === job.yml.tmpl -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK -budget_policy_id x->y OK -budget_policy_id y->absent OK -budget_policy_id y->x OK -continuous.pause_status absent->x OK -continuous.pause_status absent->y OK -continuous.pause_status x->absent OK -continuous.pause_status x->y OK -continuous.pause_status y->absent OK -continuous.pause_status y->x OK -continuous.task_retry_mode absent->x OK -continuous.task_retry_mode absent->y OK -continuous.task_retry_mode x->absent OK -continuous.task_retry_mode x->y OK -continuous.task_retry_mode y->absent OK -continuous.task_retry_mode y->x OK -deployment.deployment_id absent->x OK -deployment.deployment_id absent->y OK -deployment.deployment_id x->absent OK -deployment.deployment_id x->y OK -deployment.deployment_id y->absent OK -deployment.deployment_id y->x OK -deployment.kind x->y OK -deployment.kind y->x OK -deployment.metadata_file_path absent->x OK -deployment.metadata_file_path absent->y OK -deployment.metadata_file_path x->absent OK -deployment.metadata_file_path x->y OK -deployment.metadata_file_path y->absent OK -deployment.metadata_file_path y->x OK -deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service -deployment.version_id x->absent NOT_OBSERVABLE -deployment.version_id x->y SUPPRESSED managed by the deployment metadata service -deployment.version_id y->absent NOT_OBSERVABLE -deployment.version_id y->x SUPPRESSED managed by the deployment metadata service -description absent->x OK -description absent->y OK -description x->absent OK -description x->y OK -description y->absent OK -description y->x OK -edit_mode absent->x OK -edit_mode absent->y OK -edit_mode x->absent OK -edit_mode x->y OK -edit_mode y->absent OK -edit_mode y->x OK -email_notifications.no_alert_for_skipped_runs absent->false OK -email_notifications.no_alert_for_skipped_runs absent->true OK -email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false->true OK -email_notifications.no_alert_for_skipped_runs true->absent OK -email_notifications.no_alert_for_skipped_runs true->false OK -format MULTI_TASK->SINGLE_TASK OK -format MULTI_TASK->absent OK -format SINGLE_TASK->MULTI_TASK OK -format SINGLE_TASK->absent OK -format absent->MULTI_TASK OK -format absent->SINGLE_TASK OK -git_source.git_branch - SKIPPED needs a coherent git_source block -git_source.git_commit - SKIPPED needs a coherent git_source block -git_source.git_provider - SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block -git_source.git_tag - SKIPPED needs a coherent git_source block -git_source.git_url - SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -max_concurrent_runs 1->5 OK -max_concurrent_runs 1->absent OK -max_concurrent_runs 5->1 OK -max_concurrent_runs 5->absent OK -max_concurrent_runs absent->1 OK -max_concurrent_runs absent->5 OK -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -notification_settings.no_alert_for_canceled_runs absent->false OK -notification_settings.no_alert_for_canceled_runs absent->true OK -notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false->true OK -notification_settings.no_alert_for_canceled_runs true->absent OK -notification_settings.no_alert_for_canceled_runs true->false OK -notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent->true OK -notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false->true OK -notification_settings.no_alert_for_skipped_runs true->absent OK -notification_settings.no_alert_for_skipped_runs true->false OK -parent_path absent->x OK -parent_path absent->y OK -parent_path x->absent OK -parent_path x->y OK -parent_path y->absent OK -parent_path y->x OK -performance_target PERFORMANCE_OPTIMIZED->STANDARD OK -performance_target PERFORMANCE_OPTIMIZED->absent OK -performance_target STANDARD->PERFORMANCE_OPTIMIZED OK -performance_target STANDARD->absent OK -performance_target absent->PERFORMANCE_OPTIMIZED OK -performance_target absent->STANDARD OK -queue.enabled false->true OK -queue.enabled true->false OK -run_as.group_name absent->x OK -run_as.group_name absent->y OK -run_as.group_name x->absent OK -run_as.group_name x->y OK -run_as.group_name y->absent OK -run_as.group_name y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -schedule.pause_status absent->x OK -schedule.pause_status absent->y OK -schedule.pause_status x->absent OK -schedule.pause_status x->y OK -schedule.pause_status y->absent OK -schedule.pause_status y->x OK -schedule.quartz_cron_expression x->y OK -schedule.quartz_cron_expression y->x OK -schedule.sql_condition.sql_query_id x->y OK -schedule.sql_condition.sql_query_id y->x OK -schedule.sql_condition.trigger_mode absent->x OK -schedule.sql_condition.trigger_mode absent->y OK -schedule.sql_condition.trigger_mode x->absent OK -schedule.sql_condition.trigger_mode x->y OK -schedule.sql_condition.trigger_mode y->absent OK -schedule.sql_condition.trigger_mode y->x OK -schedule.sql_condition.warehouse_id x->y OK -schedule.sql_condition.warehouse_id y->x OK -schedule.timezone_id x->y OK -schedule.timezone_id y->x OK -timeout_seconds 1200->600 OK -timeout_seconds 1200->absent OK -timeout_seconds 600->1200 OK -timeout_seconds 600->absent OK -timeout_seconds absent->1200 OK -timeout_seconds absent->600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK -trigger.file_arrival.url x->y OK -trigger.file_arrival.url y->x OK -trigger.file_arrival.wait_after_last_change_seconds 1->2 OK -trigger.file_arrival.wait_after_last_change_seconds 1->absent OK -trigger.file_arrival.wait_after_last_change_seconds 2->1 OK -trigger.file_arrival.wait_after_last_change_seconds 2->absent OK -trigger.file_arrival.wait_after_last_change_seconds absent->1 OK -trigger.file_arrival.wait_after_last_change_seconds absent->2 OK -trigger.model.condition x->y OK -trigger.model.condition y->x OK -trigger.model.min_time_between_triggers_seconds 1->2 OK -trigger.model.min_time_between_triggers_seconds 1->absent OK -trigger.model.min_time_between_triggers_seconds 2->1 OK -trigger.model.min_time_between_triggers_seconds 2->absent OK -trigger.model.min_time_between_triggers_seconds absent->1 OK -trigger.model.min_time_between_triggers_seconds absent->2 OK -trigger.model.securable_name absent->x OK -trigger.model.securable_name absent->y OK -trigger.model.securable_name x->absent OK -trigger.model.securable_name x->y OK -trigger.model.securable_name y->absent OK -trigger.model.securable_name y->x OK -trigger.model.wait_after_last_change_seconds 1->2 OK -trigger.model.wait_after_last_change_seconds 1->absent OK -trigger.model.wait_after_last_change_seconds 2->1 OK -trigger.model.wait_after_last_change_seconds 2->absent OK -trigger.model.wait_after_last_change_seconds absent->1 OK -trigger.model.wait_after_last_change_seconds absent->2 OK -trigger.pause_status absent->x OK -trigger.pause_status absent->y OK -trigger.pause_status x->absent OK -trigger.pause_status x->y OK -trigger.pause_status y->absent OK -trigger.pause_status y->x OK -trigger.periodic.interval 1->2 OK -trigger.periodic.interval 2->1 OK -trigger.periodic.unit x->y OK -trigger.periodic.unit y->x OK -trigger.sql_condition.sql_query_id x->y OK -trigger.sql_condition.sql_query_id y->x OK -trigger.sql_condition.trigger_mode absent->x OK -trigger.sql_condition.trigger_mode absent->y OK -trigger.sql_condition.trigger_mode x->absent OK -trigger.sql_condition.trigger_mode x->y OK -trigger.sql_condition.trigger_mode y->absent OK -trigger.sql_condition.trigger_mode y->x OK -trigger.sql_condition.warehouse_id x->y OK -trigger.sql_condition.warehouse_id y->x OK -trigger.table_update.condition absent->x OK -trigger.table_update.condition absent->y OK -trigger.table_update.condition x->absent OK -trigger.table_update.condition x->y OK -trigger.table_update.condition y->absent OK -trigger.table_update.condition y->x OK -trigger.table_update.min_time_between_triggers_seconds 1->2 OK -trigger.table_update.min_time_between_triggers_seconds 1->absent OK -trigger.table_update.min_time_between_triggers_seconds 2->1 OK -trigger.table_update.min_time_between_triggers_seconds 2->absent OK -trigger.table_update.min_time_between_triggers_seconds absent->1 OK -trigger.table_update.min_time_between_triggers_seconds absent->2 OK -trigger.table_update.wait_after_last_change_seconds 1->2 OK -trigger.table_update.wait_after_last_change_seconds 1->absent OK -trigger.table_update.wait_after_last_change_seconds 2->1 OK -trigger.table_update.wait_after_last_change_seconds 2->absent OK -trigger.table_update.wait_after_last_change_seconds absent->1 OK -trigger.table_update.wait_after_last_change_seconds absent->2 OK -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent OK -usage_policy_id x->y OK -usage_policy_id y->absent OK -usage_policy_id y->x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y x SUPPRESSED managed by the deployment metadata service === job_apply_policy_default_values_for_each_task.yml.tmpl -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK -budget_policy_id x->y OK -budget_policy_id y->absent OK -budget_policy_id y->x OK -continuous.pause_status absent->x OK -continuous.pause_status absent->y OK -continuous.pause_status x->absent OK -continuous.pause_status x->y OK -continuous.pause_status y->absent OK -continuous.pause_status y->x OK -continuous.task_retry_mode absent->x OK -continuous.task_retry_mode absent->y OK -continuous.task_retry_mode x->absent OK -continuous.task_retry_mode x->y OK -continuous.task_retry_mode y->absent OK -continuous.task_retry_mode y->x OK -deployment.deployment_id absent->x OK -deployment.deployment_id absent->y OK -deployment.deployment_id x->absent OK -deployment.deployment_id x->y OK -deployment.deployment_id y->absent OK -deployment.deployment_id y->x OK -deployment.kind x->y OK -deployment.kind y->x OK -deployment.metadata_file_path absent->x OK -deployment.metadata_file_path absent->y OK -deployment.metadata_file_path x->absent OK -deployment.metadata_file_path x->y OK -deployment.metadata_file_path y->absent OK -deployment.metadata_file_path y->x OK -deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service -deployment.version_id x->absent NOT_OBSERVABLE -deployment.version_id x->y SUPPRESSED managed by the deployment metadata service -deployment.version_id y->absent NOT_OBSERVABLE -deployment.version_id y->x SUPPRESSED managed by the deployment metadata service -description absent->x OK -description absent->y OK -description x->absent OK -description x->y OK -description y->absent OK -description y->x OK -edit_mode absent->x OK -edit_mode absent->y OK -edit_mode x->absent OK -edit_mode x->y OK -edit_mode y->absent OK -edit_mode y->x OK -email_notifications.no_alert_for_skipped_runs absent->false OK -email_notifications.no_alert_for_skipped_runs absent->true OK -email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false->true OK -email_notifications.no_alert_for_skipped_runs true->absent OK -email_notifications.no_alert_for_skipped_runs true->false OK -format MULTI_TASK->SINGLE_TASK OK -format MULTI_TASK->absent OK -format SINGLE_TASK->MULTI_TASK OK -format SINGLE_TASK->absent OK -format absent->MULTI_TASK OK -format absent->SINGLE_TASK OK -git_source.git_branch - SKIPPED needs a coherent git_source block -git_source.git_commit - SKIPPED needs a coherent git_source block -git_source.git_provider - SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block -git_source.git_tag - SKIPPED needs a coherent git_source block -git_source.git_url - SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -max_concurrent_runs 1->5 OK -max_concurrent_runs 1->absent OK -max_concurrent_runs 5->1 OK -max_concurrent_runs 5->absent OK -max_concurrent_runs absent->1 OK -max_concurrent_runs absent->5 OK -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -notification_settings.no_alert_for_canceled_runs absent->false OK -notification_settings.no_alert_for_canceled_runs absent->true OK -notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false->true OK -notification_settings.no_alert_for_canceled_runs true->absent OK -notification_settings.no_alert_for_canceled_runs true->false OK -notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent->true OK -notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false->true OK -notification_settings.no_alert_for_skipped_runs true->absent OK -notification_settings.no_alert_for_skipped_runs true->false OK -parent_path absent->x OK -parent_path absent->y OK -parent_path x->absent OK -parent_path x->y OK -parent_path y->absent OK -parent_path y->x OK -performance_target PERFORMANCE_OPTIMIZED->STANDARD OK -performance_target PERFORMANCE_OPTIMIZED->absent OK -performance_target STANDARD->PERFORMANCE_OPTIMIZED OK -performance_target STANDARD->absent OK -performance_target absent->PERFORMANCE_OPTIMIZED OK -performance_target absent->STANDARD OK -queue.enabled false->true OK -queue.enabled true->false OK -run_as.group_name absent->x OK -run_as.group_name absent->y OK -run_as.group_name x->absent OK -run_as.group_name x->y OK -run_as.group_name y->absent OK -run_as.group_name y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -schedule.pause_status absent->x OK -schedule.pause_status absent->y OK -schedule.pause_status x->absent OK -schedule.pause_status x->y OK -schedule.pause_status y->absent OK -schedule.pause_status y->x OK -schedule.quartz_cron_expression x->y OK -schedule.quartz_cron_expression y->x OK -schedule.sql_condition.sql_query_id x->y OK -schedule.sql_condition.sql_query_id y->x OK -schedule.sql_condition.trigger_mode absent->x OK -schedule.sql_condition.trigger_mode absent->y OK -schedule.sql_condition.trigger_mode x->absent OK -schedule.sql_condition.trigger_mode x->y OK -schedule.sql_condition.trigger_mode y->absent OK -schedule.sql_condition.trigger_mode y->x OK -schedule.sql_condition.warehouse_id x->y OK -schedule.sql_condition.warehouse_id y->x OK -schedule.timezone_id x->y OK -schedule.timezone_id y->x OK -timeout_seconds 1200->600 OK -timeout_seconds 1200->absent OK -timeout_seconds 600->1200 OK -timeout_seconds 600->absent OK -timeout_seconds absent->1200 OK -timeout_seconds absent->600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK -trigger.file_arrival.url x->y OK -trigger.file_arrival.url y->x OK -trigger.file_arrival.wait_after_last_change_seconds 1->2 OK -trigger.file_arrival.wait_after_last_change_seconds 1->absent OK -trigger.file_arrival.wait_after_last_change_seconds 2->1 OK -trigger.file_arrival.wait_after_last_change_seconds 2->absent OK -trigger.file_arrival.wait_after_last_change_seconds absent->1 OK -trigger.file_arrival.wait_after_last_change_seconds absent->2 OK -trigger.model.condition x->y OK -trigger.model.condition y->x OK -trigger.model.min_time_between_triggers_seconds 1->2 OK -trigger.model.min_time_between_triggers_seconds 1->absent OK -trigger.model.min_time_between_triggers_seconds 2->1 OK -trigger.model.min_time_between_triggers_seconds 2->absent OK -trigger.model.min_time_between_triggers_seconds absent->1 OK -trigger.model.min_time_between_triggers_seconds absent->2 OK -trigger.model.securable_name absent->x OK -trigger.model.securable_name absent->y OK -trigger.model.securable_name x->absent OK -trigger.model.securable_name x->y OK -trigger.model.securable_name y->absent OK -trigger.model.securable_name y->x OK -trigger.model.wait_after_last_change_seconds 1->2 OK -trigger.model.wait_after_last_change_seconds 1->absent OK -trigger.model.wait_after_last_change_seconds 2->1 OK -trigger.model.wait_after_last_change_seconds 2->absent OK -trigger.model.wait_after_last_change_seconds absent->1 OK -trigger.model.wait_after_last_change_seconds absent->2 OK -trigger.pause_status absent->x OK -trigger.pause_status absent->y OK -trigger.pause_status x->absent OK -trigger.pause_status x->y OK -trigger.pause_status y->absent OK -trigger.pause_status y->x OK -trigger.periodic.interval 1->2 OK -trigger.periodic.interval 2->1 OK -trigger.periodic.unit x->y OK -trigger.periodic.unit y->x OK -trigger.sql_condition.sql_query_id x->y OK -trigger.sql_condition.sql_query_id y->x OK -trigger.sql_condition.trigger_mode absent->x OK -trigger.sql_condition.trigger_mode absent->y OK -trigger.sql_condition.trigger_mode x->absent OK -trigger.sql_condition.trigger_mode x->y OK -trigger.sql_condition.trigger_mode y->absent OK -trigger.sql_condition.trigger_mode y->x OK -trigger.sql_condition.warehouse_id x->y OK -trigger.sql_condition.warehouse_id y->x OK -trigger.table_update.condition absent->x OK -trigger.table_update.condition absent->y OK -trigger.table_update.condition x->absent OK -trigger.table_update.condition x->y OK -trigger.table_update.condition y->absent OK -trigger.table_update.condition y->x OK -trigger.table_update.min_time_between_triggers_seconds 1->2 OK -trigger.table_update.min_time_between_triggers_seconds 1->absent OK -trigger.table_update.min_time_between_triggers_seconds 2->1 OK -trigger.table_update.min_time_between_triggers_seconds 2->absent OK -trigger.table_update.min_time_between_triggers_seconds absent->1 OK -trigger.table_update.min_time_between_triggers_seconds absent->2 OK -trigger.table_update.wait_after_last_change_seconds 1->2 OK -trigger.table_update.wait_after_last_change_seconds 1->absent OK -trigger.table_update.wait_after_last_change_seconds 2->1 OK -trigger.table_update.wait_after_last_change_seconds 2->absent OK -trigger.table_update.wait_after_last_change_seconds absent->1 OK -trigger.table_update.wait_after_last_change_seconds absent->2 OK -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent OK -usage_policy_id x->y OK -usage_policy_id y->absent OK -usage_policy_id y->x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y x SUPPRESSED managed by the deployment metadata service === job_apply_policy_default_values_job_cluster.yml.tmpl -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK -budget_policy_id x->y OK -budget_policy_id y->absent OK -budget_policy_id y->x OK -continuous.pause_status absent->x OK -continuous.pause_status absent->y OK -continuous.pause_status x->absent OK -continuous.pause_status x->y OK -continuous.pause_status y->absent OK -continuous.pause_status y->x OK -continuous.task_retry_mode absent->x OK -continuous.task_retry_mode absent->y OK -continuous.task_retry_mode x->absent OK -continuous.task_retry_mode x->y OK -continuous.task_retry_mode y->absent OK -continuous.task_retry_mode y->x OK -deployment.deployment_id absent->x OK -deployment.deployment_id absent->y OK -deployment.deployment_id x->absent OK -deployment.deployment_id x->y OK -deployment.deployment_id y->absent OK -deployment.deployment_id y->x OK -deployment.kind x->y OK -deployment.kind y->x OK -deployment.metadata_file_path absent->x OK -deployment.metadata_file_path absent->y OK -deployment.metadata_file_path x->absent OK -deployment.metadata_file_path x->y OK -deployment.metadata_file_path y->absent OK -deployment.metadata_file_path y->x OK -deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service -deployment.version_id x->absent NOT_OBSERVABLE -deployment.version_id x->y SUPPRESSED managed by the deployment metadata service -deployment.version_id y->absent NOT_OBSERVABLE -deployment.version_id y->x SUPPRESSED managed by the deployment metadata service -description absent->x OK -description absent->y OK -description x->absent OK -description x->y OK -description y->absent OK -description y->x OK -edit_mode absent->x OK -edit_mode absent->y OK -edit_mode x->absent OK -edit_mode x->y OK -edit_mode y->absent OK -edit_mode y->x OK -email_notifications.no_alert_for_skipped_runs absent->false OK -email_notifications.no_alert_for_skipped_runs absent->true OK -email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false->true OK -email_notifications.no_alert_for_skipped_runs true->absent OK -email_notifications.no_alert_for_skipped_runs true->false OK -format MULTI_TASK->SINGLE_TASK OK -format MULTI_TASK->absent OK -format SINGLE_TASK->MULTI_TASK OK -format SINGLE_TASK->absent OK -format absent->MULTI_TASK OK -format absent->SINGLE_TASK OK -git_source.git_branch - SKIPPED needs a coherent git_source block -git_source.git_commit - SKIPPED needs a coherent git_source block -git_source.git_provider - SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block -git_source.git_tag - SKIPPED needs a coherent git_source block -git_source.git_url - SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -max_concurrent_runs 1->5 OK -max_concurrent_runs 1->absent OK -max_concurrent_runs 5->1 OK -max_concurrent_runs 5->absent OK -max_concurrent_runs absent->1 OK -max_concurrent_runs absent->5 OK -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -notification_settings.no_alert_for_canceled_runs absent->false OK -notification_settings.no_alert_for_canceled_runs absent->true OK -notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false->true OK -notification_settings.no_alert_for_canceled_runs true->absent OK -notification_settings.no_alert_for_canceled_runs true->false OK -notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent->true OK -notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false->true OK -notification_settings.no_alert_for_skipped_runs true->absent OK -notification_settings.no_alert_for_skipped_runs true->false OK -parent_path absent->x OK -parent_path absent->y OK -parent_path x->absent OK -parent_path x->y OK -parent_path y->absent OK -parent_path y->x OK -performance_target PERFORMANCE_OPTIMIZED->STANDARD OK -performance_target PERFORMANCE_OPTIMIZED->absent OK -performance_target STANDARD->PERFORMANCE_OPTIMIZED OK -performance_target STANDARD->absent OK -performance_target absent->PERFORMANCE_OPTIMIZED OK -performance_target absent->STANDARD OK -queue.enabled false->true OK -queue.enabled true->false OK -run_as.group_name absent->x OK -run_as.group_name absent->y OK -run_as.group_name x->absent OK -run_as.group_name x->y OK -run_as.group_name y->absent OK -run_as.group_name y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -schedule.pause_status absent->x OK -schedule.pause_status absent->y OK -schedule.pause_status x->absent OK -schedule.pause_status x->y OK -schedule.pause_status y->absent OK -schedule.pause_status y->x OK -schedule.quartz_cron_expression x->y OK -schedule.quartz_cron_expression y->x OK -schedule.sql_condition.sql_query_id x->y OK -schedule.sql_condition.sql_query_id y->x OK -schedule.sql_condition.trigger_mode absent->x OK -schedule.sql_condition.trigger_mode absent->y OK -schedule.sql_condition.trigger_mode x->absent OK -schedule.sql_condition.trigger_mode x->y OK -schedule.sql_condition.trigger_mode y->absent OK -schedule.sql_condition.trigger_mode y->x OK -schedule.sql_condition.warehouse_id x->y OK -schedule.sql_condition.warehouse_id y->x OK -schedule.timezone_id x->y OK -schedule.timezone_id y->x OK -timeout_seconds 1200->600 OK -timeout_seconds 1200->absent OK -timeout_seconds 600->1200 OK -timeout_seconds 600->absent OK -timeout_seconds absent->1200 OK -timeout_seconds absent->600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK -trigger.file_arrival.url x->y OK -trigger.file_arrival.url y->x OK -trigger.file_arrival.wait_after_last_change_seconds 1->2 OK -trigger.file_arrival.wait_after_last_change_seconds 1->absent OK -trigger.file_arrival.wait_after_last_change_seconds 2->1 OK -trigger.file_arrival.wait_after_last_change_seconds 2->absent OK -trigger.file_arrival.wait_after_last_change_seconds absent->1 OK -trigger.file_arrival.wait_after_last_change_seconds absent->2 OK -trigger.model.condition x->y OK -trigger.model.condition y->x OK -trigger.model.min_time_between_triggers_seconds 1->2 OK -trigger.model.min_time_between_triggers_seconds 1->absent OK -trigger.model.min_time_between_triggers_seconds 2->1 OK -trigger.model.min_time_between_triggers_seconds 2->absent OK -trigger.model.min_time_between_triggers_seconds absent->1 OK -trigger.model.min_time_between_triggers_seconds absent->2 OK -trigger.model.securable_name absent->x OK -trigger.model.securable_name absent->y OK -trigger.model.securable_name x->absent OK -trigger.model.securable_name x->y OK -trigger.model.securable_name y->absent OK -trigger.model.securable_name y->x OK -trigger.model.wait_after_last_change_seconds 1->2 OK -trigger.model.wait_after_last_change_seconds 1->absent OK -trigger.model.wait_after_last_change_seconds 2->1 OK -trigger.model.wait_after_last_change_seconds 2->absent OK -trigger.model.wait_after_last_change_seconds absent->1 OK -trigger.model.wait_after_last_change_seconds absent->2 OK -trigger.pause_status absent->x OK -trigger.pause_status absent->y OK -trigger.pause_status x->absent OK -trigger.pause_status x->y OK -trigger.pause_status y->absent OK -trigger.pause_status y->x OK -trigger.periodic.interval 1->2 OK -trigger.periodic.interval 2->1 OK -trigger.periodic.unit x->y OK -trigger.periodic.unit y->x OK -trigger.sql_condition.sql_query_id x->y OK -trigger.sql_condition.sql_query_id y->x OK -trigger.sql_condition.trigger_mode absent->x OK -trigger.sql_condition.trigger_mode absent->y OK -trigger.sql_condition.trigger_mode x->absent OK -trigger.sql_condition.trigger_mode x->y OK -trigger.sql_condition.trigger_mode y->absent OK -trigger.sql_condition.trigger_mode y->x OK -trigger.sql_condition.warehouse_id x->y OK -trigger.sql_condition.warehouse_id y->x OK -trigger.table_update.condition absent->x OK -trigger.table_update.condition absent->y OK -trigger.table_update.condition x->absent OK -trigger.table_update.condition x->y OK -trigger.table_update.condition y->absent OK -trigger.table_update.condition y->x OK -trigger.table_update.min_time_between_triggers_seconds 1->2 OK -trigger.table_update.min_time_between_triggers_seconds 1->absent OK -trigger.table_update.min_time_between_triggers_seconds 2->1 OK -trigger.table_update.min_time_between_triggers_seconds 2->absent OK -trigger.table_update.min_time_between_triggers_seconds absent->1 OK -trigger.table_update.min_time_between_triggers_seconds absent->2 OK -trigger.table_update.wait_after_last_change_seconds 1->2 OK -trigger.table_update.wait_after_last_change_seconds 1->absent OK -trigger.table_update.wait_after_last_change_seconds 2->1 OK -trigger.table_update.wait_after_last_change_seconds 2->absent OK -trigger.table_update.wait_after_last_change_seconds absent->1 OK -trigger.table_update.wait_after_last_change_seconds absent->2 OK -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent OK -usage_policy_id x->y OK -usage_policy_id y->absent OK -usage_policy_id y->x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y x SUPPRESSED managed by the deployment metadata service === job_apply_policy_default_values_task_cluster.yml.tmpl -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK -budget_policy_id x->y OK -budget_policy_id y->absent OK -budget_policy_id y->x OK -continuous.pause_status absent->x OK -continuous.pause_status absent->y OK -continuous.pause_status x->absent OK -continuous.pause_status x->y OK -continuous.pause_status y->absent OK -continuous.pause_status y->x OK -continuous.task_retry_mode absent->x OK -continuous.task_retry_mode absent->y OK -continuous.task_retry_mode x->absent OK -continuous.task_retry_mode x->y OK -continuous.task_retry_mode y->absent OK -continuous.task_retry_mode y->x OK -deployment.deployment_id absent->x OK -deployment.deployment_id absent->y OK -deployment.deployment_id x->absent OK -deployment.deployment_id x->y OK -deployment.deployment_id y->absent OK -deployment.deployment_id y->x OK -deployment.kind x->y OK -deployment.kind y->x OK -deployment.metadata_file_path absent->x OK -deployment.metadata_file_path absent->y OK -deployment.metadata_file_path x->absent OK -deployment.metadata_file_path x->y OK -deployment.metadata_file_path y->absent OK -deployment.metadata_file_path y->x OK -deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service -deployment.version_id x->absent NOT_OBSERVABLE -deployment.version_id x->y SUPPRESSED managed by the deployment metadata service -deployment.version_id y->absent NOT_OBSERVABLE -deployment.version_id y->x SUPPRESSED managed by the deployment metadata service -description absent->x OK -description absent->y OK -description x->absent OK -description x->y OK -description y->absent OK -description y->x OK -edit_mode absent->x OK -edit_mode absent->y OK -edit_mode x->absent OK -edit_mode x->y OK -edit_mode y->absent OK -edit_mode y->x OK -email_notifications.no_alert_for_skipped_runs absent->false OK -email_notifications.no_alert_for_skipped_runs absent->true OK -email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false->true OK -email_notifications.no_alert_for_skipped_runs true->absent OK -email_notifications.no_alert_for_skipped_runs true->false OK -format MULTI_TASK->SINGLE_TASK OK -format MULTI_TASK->absent OK -format SINGLE_TASK->MULTI_TASK OK -format SINGLE_TASK->absent OK -format absent->MULTI_TASK OK -format absent->SINGLE_TASK OK -git_source.git_branch - SKIPPED needs a coherent git_source block -git_source.git_commit - SKIPPED needs a coherent git_source block -git_source.git_provider - SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block -git_source.git_tag - SKIPPED needs a coherent git_source block -git_source.git_url - SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -max_concurrent_runs 1->5 OK -max_concurrent_runs 1->absent OK -max_concurrent_runs 5->1 OK -max_concurrent_runs 5->absent OK -max_concurrent_runs absent->1 OK -max_concurrent_runs absent->5 OK -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -notification_settings.no_alert_for_canceled_runs absent->false OK -notification_settings.no_alert_for_canceled_runs absent->true OK -notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false->true OK -notification_settings.no_alert_for_canceled_runs true->absent OK -notification_settings.no_alert_for_canceled_runs true->false OK -notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent->true OK -notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false->true OK -notification_settings.no_alert_for_skipped_runs true->absent OK -notification_settings.no_alert_for_skipped_runs true->false OK -parent_path absent->x OK -parent_path absent->y OK -parent_path x->absent OK -parent_path x->y OK -parent_path y->absent OK -parent_path y->x OK -performance_target PERFORMANCE_OPTIMIZED->STANDARD OK -performance_target PERFORMANCE_OPTIMIZED->absent OK -performance_target STANDARD->PERFORMANCE_OPTIMIZED OK -performance_target STANDARD->absent OK -performance_target absent->PERFORMANCE_OPTIMIZED OK -performance_target absent->STANDARD OK -queue.enabled false->true OK -queue.enabled true->false OK -run_as.group_name absent->x OK -run_as.group_name absent->y OK -run_as.group_name x->absent OK -run_as.group_name x->y OK -run_as.group_name y->absent OK -run_as.group_name y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -schedule.pause_status absent->x OK -schedule.pause_status absent->y OK -schedule.pause_status x->absent OK -schedule.pause_status x->y OK -schedule.pause_status y->absent OK -schedule.pause_status y->x OK -schedule.quartz_cron_expression x->y OK -schedule.quartz_cron_expression y->x OK -schedule.sql_condition.sql_query_id x->y OK -schedule.sql_condition.sql_query_id y->x OK -schedule.sql_condition.trigger_mode absent->x OK -schedule.sql_condition.trigger_mode absent->y OK -schedule.sql_condition.trigger_mode x->absent OK -schedule.sql_condition.trigger_mode x->y OK -schedule.sql_condition.trigger_mode y->absent OK -schedule.sql_condition.trigger_mode y->x OK -schedule.sql_condition.warehouse_id x->y OK -schedule.sql_condition.warehouse_id y->x OK -schedule.timezone_id x->y OK -schedule.timezone_id y->x OK -timeout_seconds 1200->600 OK -timeout_seconds 1200->absent OK -timeout_seconds 600->1200 OK -timeout_seconds 600->absent OK -timeout_seconds absent->1200 OK -timeout_seconds absent->600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK -trigger.file_arrival.url x->y OK -trigger.file_arrival.url y->x OK -trigger.file_arrival.wait_after_last_change_seconds 1->2 OK -trigger.file_arrival.wait_after_last_change_seconds 1->absent OK -trigger.file_arrival.wait_after_last_change_seconds 2->1 OK -trigger.file_arrival.wait_after_last_change_seconds 2->absent OK -trigger.file_arrival.wait_after_last_change_seconds absent->1 OK -trigger.file_arrival.wait_after_last_change_seconds absent->2 OK -trigger.model.condition x->y OK -trigger.model.condition y->x OK -trigger.model.min_time_between_triggers_seconds 1->2 OK -trigger.model.min_time_between_triggers_seconds 1->absent OK -trigger.model.min_time_between_triggers_seconds 2->1 OK -trigger.model.min_time_between_triggers_seconds 2->absent OK -trigger.model.min_time_between_triggers_seconds absent->1 OK -trigger.model.min_time_between_triggers_seconds absent->2 OK -trigger.model.securable_name absent->x OK -trigger.model.securable_name absent->y OK -trigger.model.securable_name x->absent OK -trigger.model.securable_name x->y OK -trigger.model.securable_name y->absent OK -trigger.model.securable_name y->x OK -trigger.model.wait_after_last_change_seconds 1->2 OK -trigger.model.wait_after_last_change_seconds 1->absent OK -trigger.model.wait_after_last_change_seconds 2->1 OK -trigger.model.wait_after_last_change_seconds 2->absent OK -trigger.model.wait_after_last_change_seconds absent->1 OK -trigger.model.wait_after_last_change_seconds absent->2 OK -trigger.pause_status absent->x OK -trigger.pause_status absent->y OK -trigger.pause_status x->absent OK -trigger.pause_status x->y OK -trigger.pause_status y->absent OK -trigger.pause_status y->x OK -trigger.periodic.interval 1->2 OK -trigger.periodic.interval 2->1 OK -trigger.periodic.unit x->y OK -trigger.periodic.unit y->x OK -trigger.sql_condition.sql_query_id x->y OK -trigger.sql_condition.sql_query_id y->x OK -trigger.sql_condition.trigger_mode absent->x OK -trigger.sql_condition.trigger_mode absent->y OK -trigger.sql_condition.trigger_mode x->absent OK -trigger.sql_condition.trigger_mode x->y OK -trigger.sql_condition.trigger_mode y->absent OK -trigger.sql_condition.trigger_mode y->x OK -trigger.sql_condition.warehouse_id x->y OK -trigger.sql_condition.warehouse_id y->x OK -trigger.table_update.condition absent->x OK -trigger.table_update.condition absent->y OK -trigger.table_update.condition x->absent OK -trigger.table_update.condition x->y OK -trigger.table_update.condition y->absent OK -trigger.table_update.condition y->x OK -trigger.table_update.min_time_between_triggers_seconds 1->2 OK -trigger.table_update.min_time_between_triggers_seconds 1->absent OK -trigger.table_update.min_time_between_triggers_seconds 2->1 OK -trigger.table_update.min_time_between_triggers_seconds 2->absent OK -trigger.table_update.min_time_between_triggers_seconds absent->1 OK -trigger.table_update.min_time_between_triggers_seconds absent->2 OK -trigger.table_update.wait_after_last_change_seconds 1->2 OK -trigger.table_update.wait_after_last_change_seconds 1->absent OK -trigger.table_update.wait_after_last_change_seconds 2->1 OK -trigger.table_update.wait_after_last_change_seconds 2->absent OK -trigger.table_update.wait_after_last_change_seconds absent->1 OK -trigger.table_update.wait_after_last_change_seconds absent->2 OK -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent OK -usage_policy_id x->y OK -usage_policy_id y->absent OK -usage_policy_id y->x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y x SUPPRESSED managed by the deployment metadata service === job_with_depends_on.yml.tmpl -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK -budget_policy_id x->y OK -budget_policy_id y->absent OK -budget_policy_id y->x OK -continuous.pause_status absent->x OK -continuous.pause_status absent->y OK -continuous.pause_status x->absent OK -continuous.pause_status x->y OK -continuous.pause_status y->absent OK -continuous.pause_status y->x OK -continuous.task_retry_mode absent->x OK -continuous.task_retry_mode absent->y OK -continuous.task_retry_mode x->absent OK -continuous.task_retry_mode x->y OK -continuous.task_retry_mode y->absent OK -continuous.task_retry_mode y->x OK -deployment.deployment_id absent->x OK -deployment.deployment_id absent->y OK -deployment.deployment_id x->absent OK -deployment.deployment_id x->y OK -deployment.deployment_id y->absent OK -deployment.deployment_id y->x OK -deployment.kind x->y OK -deployment.kind y->x OK -deployment.metadata_file_path absent->x OK -deployment.metadata_file_path absent->y OK -deployment.metadata_file_path x->absent OK -deployment.metadata_file_path x->y OK -deployment.metadata_file_path y->absent OK -deployment.metadata_file_path y->x OK -deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service -deployment.version_id x->absent NOT_OBSERVABLE -deployment.version_id x->y SUPPRESSED managed by the deployment metadata service -deployment.version_id y->absent NOT_OBSERVABLE -deployment.version_id y->x SUPPRESSED managed by the deployment metadata service -description absent->x OK -description absent->y OK -description x->absent OK -description x->y OK -description y->absent OK -description y->x OK -edit_mode absent->x OK -edit_mode absent->y OK -edit_mode x->absent OK -edit_mode x->y OK -edit_mode y->absent OK -edit_mode y->x OK -email_notifications.no_alert_for_skipped_runs absent->false OK -email_notifications.no_alert_for_skipped_runs absent->true OK -email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false->true OK -email_notifications.no_alert_for_skipped_runs true->absent OK -email_notifications.no_alert_for_skipped_runs true->false OK -format MULTI_TASK->SINGLE_TASK OK -format MULTI_TASK->absent OK -format SINGLE_TASK->MULTI_TASK OK -format SINGLE_TASK->absent OK -format absent->MULTI_TASK OK -format absent->SINGLE_TASK OK -git_source.git_branch - SKIPPED needs a coherent git_source block -git_source.git_commit - SKIPPED needs a coherent git_source block -git_source.git_provider - SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block -git_source.git_tag - SKIPPED needs a coherent git_source block -git_source.git_url - SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -max_concurrent_runs 1->5 OK -max_concurrent_runs 1->absent OK -max_concurrent_runs 5->1 OK -max_concurrent_runs 5->absent OK -max_concurrent_runs absent->1 OK -max_concurrent_runs absent->5 OK -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -notification_settings.no_alert_for_canceled_runs absent->false OK -notification_settings.no_alert_for_canceled_runs absent->true OK -notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false->true OK -notification_settings.no_alert_for_canceled_runs true->absent OK -notification_settings.no_alert_for_canceled_runs true->false OK -notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent->true OK -notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false->true OK -notification_settings.no_alert_for_skipped_runs true->absent OK -notification_settings.no_alert_for_skipped_runs true->false OK -parent_path absent->x OK -parent_path absent->y OK -parent_path x->absent OK -parent_path x->y OK -parent_path y->absent OK -parent_path y->x OK -performance_target PERFORMANCE_OPTIMIZED->STANDARD OK -performance_target PERFORMANCE_OPTIMIZED->absent OK -performance_target STANDARD->PERFORMANCE_OPTIMIZED OK -performance_target STANDARD->absent OK -performance_target absent->PERFORMANCE_OPTIMIZED OK -performance_target absent->STANDARD OK -queue.enabled false->true OK -queue.enabled true->false OK -run_as.group_name absent->x OK -run_as.group_name absent->y OK -run_as.group_name x->absent OK -run_as.group_name x->y OK -run_as.group_name y->absent OK -run_as.group_name y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -schedule.pause_status absent->x OK -schedule.pause_status absent->y OK -schedule.pause_status x->absent OK -schedule.pause_status x->y OK -schedule.pause_status y->absent OK -schedule.pause_status y->x OK -schedule.quartz_cron_expression x->y OK -schedule.quartz_cron_expression y->x OK -schedule.sql_condition.sql_query_id x->y OK -schedule.sql_condition.sql_query_id y->x OK -schedule.sql_condition.trigger_mode absent->x OK -schedule.sql_condition.trigger_mode absent->y OK -schedule.sql_condition.trigger_mode x->absent OK -schedule.sql_condition.trigger_mode x->y OK -schedule.sql_condition.trigger_mode y->absent OK -schedule.sql_condition.trigger_mode y->x OK -schedule.sql_condition.warehouse_id x->y OK -schedule.sql_condition.warehouse_id y->x OK -schedule.timezone_id x->y OK -schedule.timezone_id y->x OK -timeout_seconds 1200->600 OK -timeout_seconds 1200->absent OK -timeout_seconds 600->1200 OK -timeout_seconds 600->absent OK -timeout_seconds absent->1200 OK -timeout_seconds absent->600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK -trigger.file_arrival.url x->y OK -trigger.file_arrival.url y->x OK -trigger.file_arrival.wait_after_last_change_seconds 1->2 OK -trigger.file_arrival.wait_after_last_change_seconds 1->absent OK -trigger.file_arrival.wait_after_last_change_seconds 2->1 OK -trigger.file_arrival.wait_after_last_change_seconds 2->absent OK -trigger.file_arrival.wait_after_last_change_seconds absent->1 OK -trigger.file_arrival.wait_after_last_change_seconds absent->2 OK -trigger.model.condition x->y OK -trigger.model.condition y->x OK -trigger.model.min_time_between_triggers_seconds 1->2 OK -trigger.model.min_time_between_triggers_seconds 1->absent OK -trigger.model.min_time_between_triggers_seconds 2->1 OK -trigger.model.min_time_between_triggers_seconds 2->absent OK -trigger.model.min_time_between_triggers_seconds absent->1 OK -trigger.model.min_time_between_triggers_seconds absent->2 OK -trigger.model.securable_name absent->x OK -trigger.model.securable_name absent->y OK -trigger.model.securable_name x->absent OK -trigger.model.securable_name x->y OK -trigger.model.securable_name y->absent OK -trigger.model.securable_name y->x OK -trigger.model.wait_after_last_change_seconds 1->2 OK -trigger.model.wait_after_last_change_seconds 1->absent OK -trigger.model.wait_after_last_change_seconds 2->1 OK -trigger.model.wait_after_last_change_seconds 2->absent OK -trigger.model.wait_after_last_change_seconds absent->1 OK -trigger.model.wait_after_last_change_seconds absent->2 OK -trigger.pause_status absent->x OK -trigger.pause_status absent->y OK -trigger.pause_status x->absent OK -trigger.pause_status x->y OK -trigger.pause_status y->absent OK -trigger.pause_status y->x OK -trigger.periodic.interval 1->2 OK -trigger.periodic.interval 2->1 OK -trigger.periodic.unit x->y OK -trigger.periodic.unit y->x OK -trigger.sql_condition.sql_query_id x->y OK -trigger.sql_condition.sql_query_id y->x OK -trigger.sql_condition.trigger_mode absent->x OK -trigger.sql_condition.trigger_mode absent->y OK -trigger.sql_condition.trigger_mode x->absent OK -trigger.sql_condition.trigger_mode x->y OK -trigger.sql_condition.trigger_mode y->absent OK -trigger.sql_condition.trigger_mode y->x OK -trigger.sql_condition.warehouse_id x->y OK -trigger.sql_condition.warehouse_id y->x OK -trigger.table_update.condition absent->x OK -trigger.table_update.condition absent->y OK -trigger.table_update.condition x->absent OK -trigger.table_update.condition x->y OK -trigger.table_update.condition y->absent OK -trigger.table_update.condition y->x OK -trigger.table_update.min_time_between_triggers_seconds 1->2 OK -trigger.table_update.min_time_between_triggers_seconds 1->absent OK -trigger.table_update.min_time_between_triggers_seconds 2->1 OK -trigger.table_update.min_time_between_triggers_seconds 2->absent OK -trigger.table_update.min_time_between_triggers_seconds absent->1 OK -trigger.table_update.min_time_between_triggers_seconds absent->2 OK -trigger.table_update.wait_after_last_change_seconds 1->2 OK -trigger.table_update.wait_after_last_change_seconds 1->absent OK -trigger.table_update.wait_after_last_change_seconds 2->1 OK -trigger.table_update.wait_after_last_change_seconds 2->absent OK -trigger.table_update.wait_after_last_change_seconds absent->1 OK -trigger.table_update.wait_after_last_change_seconds absent->2 OK -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent OK -usage_policy_id x->y OK -usage_policy_id y->absent OK -usage_policy_id y->x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y x SUPPRESSED managed by the deployment metadata service === job_with_task.yml.tmpl -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK -budget_policy_id x->y OK -budget_policy_id y->absent OK -budget_policy_id y->x OK -continuous.pause_status absent->x OK -continuous.pause_status absent->y OK -continuous.pause_status x->absent OK -continuous.pause_status x->y OK -continuous.pause_status y->absent OK -continuous.pause_status y->x OK -continuous.task_retry_mode absent->x OK -continuous.task_retry_mode absent->y OK -continuous.task_retry_mode x->absent OK -continuous.task_retry_mode x->y OK -continuous.task_retry_mode y->absent OK -continuous.task_retry_mode y->x OK -deployment.deployment_id absent->x OK -deployment.deployment_id absent->y OK -deployment.deployment_id x->absent OK -deployment.deployment_id x->y OK -deployment.deployment_id y->absent OK -deployment.deployment_id y->x OK -deployment.kind x->y OK -deployment.kind y->x OK -deployment.metadata_file_path absent->x OK -deployment.metadata_file_path absent->y OK -deployment.metadata_file_path x->absent OK -deployment.metadata_file_path x->y OK -deployment.metadata_file_path y->absent OK -deployment.metadata_file_path y->x OK -deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service -deployment.version_id x->absent NOT_OBSERVABLE -deployment.version_id x->y SUPPRESSED managed by the deployment metadata service -deployment.version_id y->absent NOT_OBSERVABLE -deployment.version_id y->x SUPPRESSED managed by the deployment metadata service -description absent->x OK -description absent->y OK -description x->absent OK -description x->y OK -description y->absent OK -description y->x OK -edit_mode absent->x OK -edit_mode absent->y OK -edit_mode x->absent OK -edit_mode x->y OK -edit_mode y->absent OK -edit_mode y->x OK -email_notifications.no_alert_for_skipped_runs absent->false OK -email_notifications.no_alert_for_skipped_runs absent->true OK -email_notifications.no_alert_for_skipped_runs false->absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false->true OK -email_notifications.no_alert_for_skipped_runs true->absent OK -email_notifications.no_alert_for_skipped_runs true->false OK -format MULTI_TASK->SINGLE_TASK OK -format MULTI_TASK->absent OK -format SINGLE_TASK->MULTI_TASK OK -format SINGLE_TASK->absent OK -format absent->MULTI_TASK OK -format absent->SINGLE_TASK OK -git_source.git_branch - SKIPPED needs a coherent git_source block -git_source.git_commit - SKIPPED needs a coherent git_source block -git_source.git_provider - SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit - SKIPPED needs a coherent git_source block -git_source.git_tag - SKIPPED needs a coherent git_source block -git_source.git_url - SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state - SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch - SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path - SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -max_concurrent_runs 1->5 OK -max_concurrent_runs 1->absent OK -max_concurrent_runs 5->1 OK -max_concurrent_runs 5->absent OK -max_concurrent_runs absent->1 OK -max_concurrent_runs absent->5 OK -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -notification_settings.no_alert_for_canceled_runs absent->false OK -notification_settings.no_alert_for_canceled_runs absent->true OK -notification_settings.no_alert_for_canceled_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false->true OK -notification_settings.no_alert_for_canceled_runs true->absent OK -notification_settings.no_alert_for_canceled_runs true->false OK -notification_settings.no_alert_for_skipped_runs absent->false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent->true OK -notification_settings.no_alert_for_skipped_runs false->absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false->true OK -notification_settings.no_alert_for_skipped_runs true->absent OK -notification_settings.no_alert_for_skipped_runs true->false OK -parent_path absent->x OK -parent_path absent->y OK -parent_path x->absent OK -parent_path x->y OK -parent_path y->absent OK -parent_path y->x OK -performance_target PERFORMANCE_OPTIMIZED->STANDARD OK -performance_target PERFORMANCE_OPTIMIZED->absent OK -performance_target STANDARD->PERFORMANCE_OPTIMIZED OK -performance_target STANDARD->absent OK -performance_target absent->PERFORMANCE_OPTIMIZED OK -performance_target absent->STANDARD OK -queue.enabled false->true OK -queue.enabled true->false OK -run_as.group_name absent->x OK -run_as.group_name absent->y OK -run_as.group_name x->absent OK -run_as.group_name x->y OK -run_as.group_name y->absent OK -run_as.group_name y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -schedule.pause_status absent->x OK -schedule.pause_status absent->y OK -schedule.pause_status x->absent OK -schedule.pause_status x->y OK -schedule.pause_status y->absent OK -schedule.pause_status y->x OK -schedule.quartz_cron_expression x->y OK -schedule.quartz_cron_expression y->x OK -schedule.sql_condition.sql_query_id x->y OK -schedule.sql_condition.sql_query_id y->x OK -schedule.sql_condition.trigger_mode absent->x OK -schedule.sql_condition.trigger_mode absent->y OK -schedule.sql_condition.trigger_mode x->absent OK -schedule.sql_condition.trigger_mode x->y OK -schedule.sql_condition.trigger_mode y->absent OK -schedule.sql_condition.trigger_mode y->x OK -schedule.sql_condition.warehouse_id x->y OK -schedule.sql_condition.warehouse_id y->x OK -schedule.timezone_id x->y OK -schedule.timezone_id y->x OK -timeout_seconds 1200->600 OK -timeout_seconds 1200->absent OK -timeout_seconds 600->1200 OK -timeout_seconds 600->absent OK -timeout_seconds absent->1200 OK -timeout_seconds absent->600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1->absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2->1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2->absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent->1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent->2 OK -trigger.file_arrival.url x->y OK -trigger.file_arrival.url y->x OK -trigger.file_arrival.wait_after_last_change_seconds 1->2 OK -trigger.file_arrival.wait_after_last_change_seconds 1->absent OK -trigger.file_arrival.wait_after_last_change_seconds 2->1 OK -trigger.file_arrival.wait_after_last_change_seconds 2->absent OK -trigger.file_arrival.wait_after_last_change_seconds absent->1 OK -trigger.file_arrival.wait_after_last_change_seconds absent->2 OK -trigger.model.condition x->y OK -trigger.model.condition y->x OK -trigger.model.min_time_between_triggers_seconds 1->2 OK -trigger.model.min_time_between_triggers_seconds 1->absent OK -trigger.model.min_time_between_triggers_seconds 2->1 OK -trigger.model.min_time_between_triggers_seconds 2->absent OK -trigger.model.min_time_between_triggers_seconds absent->1 OK -trigger.model.min_time_between_triggers_seconds absent->2 OK -trigger.model.securable_name absent->x OK -trigger.model.securable_name absent->y OK -trigger.model.securable_name x->absent OK -trigger.model.securable_name x->y OK -trigger.model.securable_name y->absent OK -trigger.model.securable_name y->x OK -trigger.model.wait_after_last_change_seconds 1->2 OK -trigger.model.wait_after_last_change_seconds 1->absent OK -trigger.model.wait_after_last_change_seconds 2->1 OK -trigger.model.wait_after_last_change_seconds 2->absent OK -trigger.model.wait_after_last_change_seconds absent->1 OK -trigger.model.wait_after_last_change_seconds absent->2 OK -trigger.pause_status absent->x OK -trigger.pause_status absent->y OK -trigger.pause_status x->absent OK -trigger.pause_status x->y OK -trigger.pause_status y->absent OK -trigger.pause_status y->x OK -trigger.periodic.interval 1->2 OK -trigger.periodic.interval 2->1 OK -trigger.periodic.unit x->y OK -trigger.periodic.unit y->x OK -trigger.sql_condition.sql_query_id x->y OK -trigger.sql_condition.sql_query_id y->x OK -trigger.sql_condition.trigger_mode absent->x OK -trigger.sql_condition.trigger_mode absent->y OK -trigger.sql_condition.trigger_mode x->absent OK -trigger.sql_condition.trigger_mode x->y OK -trigger.sql_condition.trigger_mode y->absent OK -trigger.sql_condition.trigger_mode y->x OK -trigger.sql_condition.warehouse_id x->y OK -trigger.sql_condition.warehouse_id y->x OK -trigger.table_update.condition absent->x OK -trigger.table_update.condition absent->y OK -trigger.table_update.condition x->absent OK -trigger.table_update.condition x->y OK -trigger.table_update.condition y->absent OK -trigger.table_update.condition y->x OK -trigger.table_update.min_time_between_triggers_seconds 1->2 OK -trigger.table_update.min_time_between_triggers_seconds 1->absent OK -trigger.table_update.min_time_between_triggers_seconds 2->1 OK -trigger.table_update.min_time_between_triggers_seconds 2->absent OK -trigger.table_update.min_time_between_triggers_seconds absent->1 OK -trigger.table_update.min_time_between_triggers_seconds absent->2 OK -trigger.table_update.wait_after_last_change_seconds 1->2 OK -trigger.table_update.wait_after_last_change_seconds 1->absent OK -trigger.table_update.wait_after_last_change_seconds 2->1 OK -trigger.table_update.wait_after_last_change_seconds 2->absent OK -trigger.table_update.wait_after_last_change_seconds absent->1 OK -trigger.table_update.wait_after_last_change_seconds absent->2 OK -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent OK -usage_policy_id x->y OK -usage_policy_id y->absent OK -usage_policy_id y->x OK - -=== summary -OK 1272 -SUPPRESSED 48 -NOT_OBSERVABLE 48 -SKIPPED 54 - -=== not covered: 614 fields under a slice or map -email_notifications.on_duration_warning_threshold_exceeded[*] -email_notifications.on_failure[*] -email_notifications.on_start[*] -email_notifications.on_streaming_backlog_exceeded[*] -email_notifications.on_success[*] -environments[*].environment_key -environments[*].spec.base_environment -environments[*].spec.client -environments[*].spec.dependencies[*] -environments[*].spec.environment_version -environments[*].spec.java_dependencies[*] -git_source.sparse_checkout.patterns[*] -health.rules[*].metric -health.rules[*].op -health.rules[*].value -job_clusters[*].job_cluster_key -job_clusters[*].new_cluster.apply_policy_default_values -job_clusters[*].new_cluster.autoscale.max_workers -job_clusters[*].new_cluster.autoscale.min_workers -job_clusters[*].new_cluster.autotermination_minutes -job_clusters[*].new_cluster.aws_attributes.availability -job_clusters[*].new_cluster.aws_attributes.ebs_volume_count -job_clusters[*].new_cluster.aws_attributes.ebs_volume_iops -job_clusters[*].new_cluster.aws_attributes.ebs_volume_size -job_clusters[*].new_cluster.aws_attributes.ebs_volume_throughput -job_clusters[*].new_cluster.aws_attributes.ebs_volume_type -job_clusters[*].new_cluster.aws_attributes.first_on_demand -job_clusters[*].new_cluster.aws_attributes.instance_profile_arn -job_clusters[*].new_cluster.aws_attributes.spot_bid_price_percent -job_clusters[*].new_cluster.aws_attributes.zone_id -job_clusters[*].new_cluster.azure_attributes.availability -job_clusters[*].new_cluster.azure_attributes.capacity_reservation_group -job_clusters[*].new_cluster.azure_attributes.first_on_demand -job_clusters[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key -job_clusters[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id -job_clusters[*].new_cluster.azure_attributes.spot_bid_max_price -job_clusters[*].new_cluster.cluster_log_conf.dbfs.destination -job_clusters[*].new_cluster.cluster_log_conf.s3.canned_acl -job_clusters[*].new_cluster.cluster_log_conf.s3.destination -job_clusters[*].new_cluster.cluster_log_conf.s3.enable_encryption -job_clusters[*].new_cluster.cluster_log_conf.s3.encryption_type -job_clusters[*].new_cluster.cluster_log_conf.s3.endpoint -job_clusters[*].new_cluster.cluster_log_conf.s3.kms_key -job_clusters[*].new_cluster.cluster_log_conf.s3.region -job_clusters[*].new_cluster.cluster_log_conf.volumes.destination -job_clusters[*].new_cluster.cluster_name -job_clusters[*].new_cluster.custom_tags.* -job_clusters[*].new_cluster.data_security_mode -job_clusters[*].new_cluster.dependency_mode -job_clusters[*].new_cluster.docker_image.basic_auth.password -job_clusters[*].new_cluster.docker_image.basic_auth.username -job_clusters[*].new_cluster.docker_image.url -job_clusters[*].new_cluster.driver_instance_pool_id -job_clusters[*].new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] -job_clusters[*].new_cluster.driver_node_type_id -job_clusters[*].new_cluster.enable_elastic_disk -job_clusters[*].new_cluster.enable_local_disk_encryption -job_clusters[*].new_cluster.gcp_attributes.availability -job_clusters[*].new_cluster.gcp_attributes.boot_disk_size -job_clusters[*].new_cluster.gcp_attributes.confidential_compute_type -job_clusters[*].new_cluster.gcp_attributes.first_on_demand -job_clusters[*].new_cluster.gcp_attributes.google_service_account -job_clusters[*].new_cluster.gcp_attributes.local_ssd_count -job_clusters[*].new_cluster.gcp_attributes.use_preemptible_executors -job_clusters[*].new_cluster.gcp_attributes.zone_id -job_clusters[*].new_cluster.init_scripts[*].abfss.destination -job_clusters[*].new_cluster.init_scripts[*].dbfs.destination -job_clusters[*].new_cluster.init_scripts[*].file.destination -job_clusters[*].new_cluster.init_scripts[*].gcs.destination -job_clusters[*].new_cluster.init_scripts[*].s3.canned_acl -job_clusters[*].new_cluster.init_scripts[*].s3.destination -job_clusters[*].new_cluster.init_scripts[*].s3.enable_encryption -job_clusters[*].new_cluster.init_scripts[*].s3.encryption_type -job_clusters[*].new_cluster.init_scripts[*].s3.endpoint -job_clusters[*].new_cluster.init_scripts[*].s3.kms_key -job_clusters[*].new_cluster.init_scripts[*].s3.region -job_clusters[*].new_cluster.init_scripts[*].volumes.destination -job_clusters[*].new_cluster.init_scripts[*].workspace.destination -job_clusters[*].new_cluster.instance_pool_id -job_clusters[*].new_cluster.is_single_node -job_clusters[*].new_cluster.kind -job_clusters[*].new_cluster.node_type_id -job_clusters[*].new_cluster.num_workers -job_clusters[*].new_cluster.policy_id -job_clusters[*].new_cluster.remote_disk_throughput -job_clusters[*].new_cluster.runtime_engine -job_clusters[*].new_cluster.single_user_name -job_clusters[*].new_cluster.spark_conf.* -job_clusters[*].new_cluster.spark_env_vars.* -job_clusters[*].new_cluster.spark_version -job_clusters[*].new_cluster.ssh_public_keys[*] -job_clusters[*].new_cluster.total_initial_remote_disk_size -job_clusters[*].new_cluster.use_ml_runtime -job_clusters[*].new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] -job_clusters[*].new_cluster.workload_type.clients.jobs -job_clusters[*].new_cluster.workload_type.clients.notebooks -job_clusters[*].serverless_compute_id -parameters[*].default -parameters[*].name -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -tags.* -tasks[*].ai_runtime_task.code_source_path -tasks[*].ai_runtime_task.deployments[*].command_path -tasks[*].ai_runtime_task.deployments[*].compute.accelerator_count -tasks[*].ai_runtime_task.deployments[*].compute.accelerator_type -tasks[*].ai_runtime_task.deployments[*].name -tasks[*].ai_runtime_task.docker_image_url -tasks[*].ai_runtime_task.experiment -tasks[*].ai_runtime_task.mlflow_artifact_location -tasks[*].ai_runtime_task.mlflow_experiment_directory -tasks[*].ai_runtime_task.mlflow_run -tasks[*].alert_task.alert_id -tasks[*].alert_task.subscribers[*].destination_id -tasks[*].alert_task.subscribers[*].user_name -tasks[*].alert_task.warehouse_id -tasks[*].alert_task.workspace_path -tasks[*].clean_rooms_notebook_task.clean_room_name -tasks[*].clean_rooms_notebook_task.etag -tasks[*].clean_rooms_notebook_task.notebook_base_parameters.* -tasks[*].clean_rooms_notebook_task.notebook_name -tasks[*].compute.hardware_accelerator -tasks[*].condition_task.left -tasks[*].condition_task.op -tasks[*].condition_task.right -tasks[*].dashboard_task.dashboard_id -tasks[*].dashboard_task.filters.* -tasks[*].dashboard_task.subscription.custom_subject -tasks[*].dashboard_task.subscription.paused -tasks[*].dashboard_task.subscription.subscribers[*].destination_id -tasks[*].dashboard_task.subscription.subscribers[*].user_name -tasks[*].dashboard_task.warehouse_id -tasks[*].dbt_cloud_task.connection_resource_name -tasks[*].dbt_cloud_task.dbt_cloud_job_id -tasks[*].dbt_platform_task.connection_resource_name -tasks[*].dbt_platform_task.dbt_platform_job_id -tasks[*].dbt_task.catalog -tasks[*].dbt_task.commands[*] -tasks[*].dbt_task.profiles_directory -tasks[*].dbt_task.project_directory -tasks[*].dbt_task.schema -tasks[*].dbt_task.source -tasks[*].dbt_task.warehouse_id -tasks[*].depends_on[*].outcome -tasks[*].depends_on[*].task_key -tasks[*].description -tasks[*].disable_auto_optimization -tasks[*].disabled -tasks[*].email_notifications.no_alert_for_skipped_runs -tasks[*].email_notifications.on_duration_warning_threshold_exceeded[*] -tasks[*].email_notifications.on_failure[*] -tasks[*].email_notifications.on_start[*] -tasks[*].email_notifications.on_streaming_backlog_exceeded[*] -tasks[*].email_notifications.on_success[*] -tasks[*].environment_key -tasks[*].existing_cluster_id -tasks[*].for_each_task.concurrency -tasks[*].for_each_task.inputs -tasks[*].for_each_task.task.ai_runtime_task.code_source_path -tasks[*].for_each_task.task.ai_runtime_task.deployments[*].command_path -tasks[*].for_each_task.task.ai_runtime_task.deployments[*].compute.accelerator_count -tasks[*].for_each_task.task.ai_runtime_task.deployments[*].compute.accelerator_type -tasks[*].for_each_task.task.ai_runtime_task.deployments[*].name -tasks[*].for_each_task.task.ai_runtime_task.docker_image_url -tasks[*].for_each_task.task.ai_runtime_task.experiment -tasks[*].for_each_task.task.ai_runtime_task.mlflow_artifact_location -tasks[*].for_each_task.task.ai_runtime_task.mlflow_experiment_directory -tasks[*].for_each_task.task.ai_runtime_task.mlflow_run -tasks[*].for_each_task.task.alert_task.alert_id -tasks[*].for_each_task.task.alert_task.subscribers[*].destination_id -tasks[*].for_each_task.task.alert_task.subscribers[*].user_name -tasks[*].for_each_task.task.alert_task.warehouse_id -tasks[*].for_each_task.task.alert_task.workspace_path -tasks[*].for_each_task.task.clean_rooms_notebook_task.clean_room_name -tasks[*].for_each_task.task.clean_rooms_notebook_task.etag -tasks[*].for_each_task.task.clean_rooms_notebook_task.notebook_base_parameters.* -tasks[*].for_each_task.task.clean_rooms_notebook_task.notebook_name -tasks[*].for_each_task.task.compute.hardware_accelerator -tasks[*].for_each_task.task.condition_task.left -tasks[*].for_each_task.task.condition_task.op -tasks[*].for_each_task.task.condition_task.right -tasks[*].for_each_task.task.dashboard_task.dashboard_id -tasks[*].for_each_task.task.dashboard_task.filters.* -tasks[*].for_each_task.task.dashboard_task.subscription.custom_subject -tasks[*].for_each_task.task.dashboard_task.subscription.paused -tasks[*].for_each_task.task.dashboard_task.subscription.subscribers[*].destination_id -tasks[*].for_each_task.task.dashboard_task.subscription.subscribers[*].user_name -tasks[*].for_each_task.task.dashboard_task.warehouse_id -tasks[*].for_each_task.task.dbt_cloud_task.connection_resource_name -tasks[*].for_each_task.task.dbt_cloud_task.dbt_cloud_job_id -tasks[*].for_each_task.task.dbt_platform_task.connection_resource_name -tasks[*].for_each_task.task.dbt_platform_task.dbt_platform_job_id -tasks[*].for_each_task.task.dbt_task.catalog -tasks[*].for_each_task.task.dbt_task.commands[*] -tasks[*].for_each_task.task.dbt_task.profiles_directory -tasks[*].for_each_task.task.dbt_task.project_directory -tasks[*].for_each_task.task.dbt_task.schema -tasks[*].for_each_task.task.dbt_task.source -tasks[*].for_each_task.task.dbt_task.warehouse_id -tasks[*].for_each_task.task.depends_on[*].outcome -tasks[*].for_each_task.task.depends_on[*].task_key -tasks[*].for_each_task.task.description -tasks[*].for_each_task.task.disable_auto_optimization -tasks[*].for_each_task.task.disabled -tasks[*].for_each_task.task.email_notifications.no_alert_for_skipped_runs -tasks[*].for_each_task.task.email_notifications.on_duration_warning_threshold_exceeded[*] -tasks[*].for_each_task.task.email_notifications.on_failure[*] -tasks[*].for_each_task.task.email_notifications.on_start[*] -tasks[*].for_each_task.task.email_notifications.on_streaming_backlog_exceeded[*] -tasks[*].for_each_task.task.email_notifications.on_success[*] -tasks[*].for_each_task.task.environment_key -tasks[*].for_each_task.task.existing_cluster_id -tasks[*].for_each_task.task.for_each_task.concurrency -tasks[*].for_each_task.task.for_each_task.inputs -tasks[*].for_each_task.task.gen_ai_compute_task.command -tasks[*].for_each_task.task.gen_ai_compute_task.compute.gpu_node_pool_id -tasks[*].for_each_task.task.gen_ai_compute_task.compute.gpu_type -tasks[*].for_each_task.task.gen_ai_compute_task.compute.num_gpus -tasks[*].for_each_task.task.gen_ai_compute_task.dl_runtime_image -tasks[*].for_each_task.task.gen_ai_compute_task.mlflow_experiment_name -tasks[*].for_each_task.task.gen_ai_compute_task.source -tasks[*].for_each_task.task.gen_ai_compute_task.training_script_path -tasks[*].for_each_task.task.gen_ai_compute_task.yaml_parameters -tasks[*].for_each_task.task.gen_ai_compute_task.yaml_parameters_file_path -tasks[*].for_each_task.task.health.rules[*].metric -tasks[*].for_each_task.task.health.rules[*].op -tasks[*].for_each_task.task.health.rules[*].value -tasks[*].for_each_task.task.job_cluster_key -tasks[*].for_each_task.task.libraries[*].cran.package -tasks[*].for_each_task.task.libraries[*].cran.repo -tasks[*].for_each_task.task.libraries[*].egg -tasks[*].for_each_task.task.libraries[*].jar -tasks[*].for_each_task.task.libraries[*].maven.coordinates -tasks[*].for_each_task.task.libraries[*].maven.exclusions[*] -tasks[*].for_each_task.task.libraries[*].maven.repo -tasks[*].for_each_task.task.libraries[*].pypi.package -tasks[*].for_each_task.task.libraries[*].pypi.repo -tasks[*].for_each_task.task.libraries[*].requirements -tasks[*].for_each_task.task.libraries[*].whl -tasks[*].for_each_task.task.max_retries -tasks[*].for_each_task.task.min_retry_interval_millis -tasks[*].for_each_task.task.new_cluster.apply_policy_default_values -tasks[*].for_each_task.task.new_cluster.autoscale.max_workers -tasks[*].for_each_task.task.new_cluster.autoscale.min_workers -tasks[*].for_each_task.task.new_cluster.autotermination_minutes -tasks[*].for_each_task.task.new_cluster.aws_attributes.availability -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_count -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_iops -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_size -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_throughput -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_type -tasks[*].for_each_task.task.new_cluster.aws_attributes.first_on_demand -tasks[*].for_each_task.task.new_cluster.aws_attributes.instance_profile_arn -tasks[*].for_each_task.task.new_cluster.aws_attributes.spot_bid_price_percent -tasks[*].for_each_task.task.new_cluster.aws_attributes.zone_id -tasks[*].for_each_task.task.new_cluster.azure_attributes.availability -tasks[*].for_each_task.task.new_cluster.azure_attributes.capacity_reservation_group -tasks[*].for_each_task.task.new_cluster.azure_attributes.first_on_demand -tasks[*].for_each_task.task.new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key -tasks[*].for_each_task.task.new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id -tasks[*].for_each_task.task.new_cluster.azure_attributes.spot_bid_max_price -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.dbfs.destination -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.canned_acl -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.destination -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.enable_encryption -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.encryption_type -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.endpoint -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.kms_key -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.region -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.volumes.destination -tasks[*].for_each_task.task.new_cluster.cluster_name -tasks[*].for_each_task.task.new_cluster.custom_tags.* -tasks[*].for_each_task.task.new_cluster.data_security_mode -tasks[*].for_each_task.task.new_cluster.dependency_mode -tasks[*].for_each_task.task.new_cluster.docker_image.basic_auth.password -tasks[*].for_each_task.task.new_cluster.docker_image.basic_auth.username -tasks[*].for_each_task.task.new_cluster.docker_image.url -tasks[*].for_each_task.task.new_cluster.driver_instance_pool_id -tasks[*].for_each_task.task.new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] -tasks[*].for_each_task.task.new_cluster.driver_node_type_id -tasks[*].for_each_task.task.new_cluster.enable_elastic_disk -tasks[*].for_each_task.task.new_cluster.enable_local_disk_encryption -tasks[*].for_each_task.task.new_cluster.gcp_attributes.availability -tasks[*].for_each_task.task.new_cluster.gcp_attributes.boot_disk_size -tasks[*].for_each_task.task.new_cluster.gcp_attributes.confidential_compute_type -tasks[*].for_each_task.task.new_cluster.gcp_attributes.first_on_demand -tasks[*].for_each_task.task.new_cluster.gcp_attributes.google_service_account -tasks[*].for_each_task.task.new_cluster.gcp_attributes.local_ssd_count -tasks[*].for_each_task.task.new_cluster.gcp_attributes.use_preemptible_executors -tasks[*].for_each_task.task.new_cluster.gcp_attributes.zone_id -tasks[*].for_each_task.task.new_cluster.init_scripts[*].abfss.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].dbfs.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].file.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].gcs.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.canned_acl -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.enable_encryption -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.encryption_type -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.endpoint -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.kms_key -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.region -tasks[*].for_each_task.task.new_cluster.init_scripts[*].volumes.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].workspace.destination -tasks[*].for_each_task.task.new_cluster.instance_pool_id -tasks[*].for_each_task.task.new_cluster.is_single_node -tasks[*].for_each_task.task.new_cluster.kind -tasks[*].for_each_task.task.new_cluster.node_type_id -tasks[*].for_each_task.task.new_cluster.num_workers -tasks[*].for_each_task.task.new_cluster.policy_id -tasks[*].for_each_task.task.new_cluster.remote_disk_throughput -tasks[*].for_each_task.task.new_cluster.runtime_engine -tasks[*].for_each_task.task.new_cluster.single_user_name -tasks[*].for_each_task.task.new_cluster.spark_conf.* -tasks[*].for_each_task.task.new_cluster.spark_env_vars.* -tasks[*].for_each_task.task.new_cluster.spark_version -tasks[*].for_each_task.task.new_cluster.ssh_public_keys[*] -tasks[*].for_each_task.task.new_cluster.total_initial_remote_disk_size -tasks[*].for_each_task.task.new_cluster.use_ml_runtime -tasks[*].for_each_task.task.new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] -tasks[*].for_each_task.task.new_cluster.workload_type.clients.jobs -tasks[*].for_each_task.task.new_cluster.workload_type.clients.notebooks -tasks[*].for_each_task.task.notebook_task.base_parameters.* -tasks[*].for_each_task.task.notebook_task.notebook_path -tasks[*].for_each_task.task.notebook_task.source -tasks[*].for_each_task.task.notebook_task.warehouse_id -tasks[*].for_each_task.task.notification_settings.alert_on_last_attempt -tasks[*].for_each_task.task.notification_settings.no_alert_for_canceled_runs -tasks[*].for_each_task.task.notification_settings.no_alert_for_skipped_runs -tasks[*].for_each_task.task.pipeline_task.full_refresh -tasks[*].for_each_task.task.pipeline_task.full_refresh_selection[*] -tasks[*].for_each_task.task.pipeline_task.parameters.* -tasks[*].for_each_task.task.pipeline_task.pipeline_id -tasks[*].for_each_task.task.pipeline_task.refresh_flow_selection[*] -tasks[*].for_each_task.task.pipeline_task.refresh_selection[*] -tasks[*].for_each_task.task.pipeline_task.reset_checkpoint_selection[*] -tasks[*].for_each_task.task.power_bi_task.connection_resource_name -tasks[*].for_each_task.task.power_bi_task.power_bi_model.authentication_method -tasks[*].for_each_task.task.power_bi_task.power_bi_model.model_name -tasks[*].for_each_task.task.power_bi_task.power_bi_model.overwrite_existing -tasks[*].for_each_task.task.power_bi_task.power_bi_model.storage_mode -tasks[*].for_each_task.task.power_bi_task.power_bi_model.workspace_name -tasks[*].for_each_task.task.power_bi_task.refresh_after_update -tasks[*].for_each_task.task.power_bi_task.tables[*].catalog -tasks[*].for_each_task.task.power_bi_task.tables[*].name -tasks[*].for_each_task.task.power_bi_task.tables[*].schema -tasks[*].for_each_task.task.power_bi_task.tables[*].storage_mode -tasks[*].for_each_task.task.power_bi_task.warehouse_id -tasks[*].for_each_task.task.python_operator_task.main -tasks[*].for_each_task.task.python_operator_task.parameters[*].name -tasks[*].for_each_task.task.python_operator_task.parameters[*].value -tasks[*].for_each_task.task.python_wheel_task.entry_point -tasks[*].for_each_task.task.python_wheel_task.named_parameters.* -tasks[*].for_each_task.task.python_wheel_task.package_name -tasks[*].for_each_task.task.python_wheel_task.parameters[*] -tasks[*].for_each_task.task.retry_on_timeout -tasks[*].for_each_task.task.run_if -tasks[*].for_each_task.task.run_job_task.dbt_commands[*] -tasks[*].for_each_task.task.run_job_task.jar_params[*] -tasks[*].for_each_task.task.run_job_task.job_id -tasks[*].for_each_task.task.run_job_task.job_parameters.* -tasks[*].for_each_task.task.run_job_task.notebook_params.* -tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh -tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh_selection[*] -tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_flow_selection[*] -tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_selection[*] -tasks[*].for_each_task.task.run_job_task.pipeline_params.reset_checkpoint_selection[*] -tasks[*].for_each_task.task.run_job_task.python_named_params.* -tasks[*].for_each_task.task.run_job_task.python_params[*] -tasks[*].for_each_task.task.run_job_task.spark_submit_params[*] -tasks[*].for_each_task.task.run_job_task.sql_params.* -tasks[*].for_each_task.task.spark_jar_task.jar_uri -tasks[*].for_each_task.task.spark_jar_task.main_class_name -tasks[*].for_each_task.task.spark_jar_task.parameters[*] -tasks[*].for_each_task.task.spark_jar_task.run_as_repl -tasks[*].for_each_task.task.spark_python_task.parameters[*] -tasks[*].for_each_task.task.spark_python_task.python_file -tasks[*].for_each_task.task.spark_python_task.source -tasks[*].for_each_task.task.spark_submit_task.parameters[*] -tasks[*].for_each_task.task.sql_task.alert.alert_id -tasks[*].for_each_task.task.sql_task.alert.pause_subscriptions -tasks[*].for_each_task.task.sql_task.alert.subscriptions[*].destination_id -tasks[*].for_each_task.task.sql_task.alert.subscriptions[*].user_name -tasks[*].for_each_task.task.sql_task.dashboard.custom_subject -tasks[*].for_each_task.task.sql_task.dashboard.dashboard_id -tasks[*].for_each_task.task.sql_task.dashboard.pause_subscriptions -tasks[*].for_each_task.task.sql_task.dashboard.subscriptions[*].destination_id -tasks[*].for_each_task.task.sql_task.dashboard.subscriptions[*].user_name -tasks[*].for_each_task.task.sql_task.file.path -tasks[*].for_each_task.task.sql_task.file.source -tasks[*].for_each_task.task.sql_task.parameters.* -tasks[*].for_each_task.task.sql_task.query.query_id -tasks[*].for_each_task.task.sql_task.warehouse_id -tasks[*].for_each_task.task.task_key -tasks[*].for_each_task.task.timeout_seconds -tasks[*].for_each_task.task.webhook_notifications.on_duration_warning_threshold_exceeded[*].id -tasks[*].for_each_task.task.webhook_notifications.on_failure[*].id -tasks[*].for_each_task.task.webhook_notifications.on_start[*].id -tasks[*].for_each_task.task.webhook_notifications.on_streaming_backlog_exceeded[*].id -tasks[*].for_each_task.task.webhook_notifications.on_success[*].id -tasks[*].gen_ai_compute_task.command -tasks[*].gen_ai_compute_task.compute.gpu_node_pool_id -tasks[*].gen_ai_compute_task.compute.gpu_type -tasks[*].gen_ai_compute_task.compute.num_gpus -tasks[*].gen_ai_compute_task.dl_runtime_image -tasks[*].gen_ai_compute_task.mlflow_experiment_name -tasks[*].gen_ai_compute_task.source -tasks[*].gen_ai_compute_task.training_script_path -tasks[*].gen_ai_compute_task.yaml_parameters -tasks[*].gen_ai_compute_task.yaml_parameters_file_path -tasks[*].health.rules[*].metric -tasks[*].health.rules[*].op -tasks[*].health.rules[*].value -tasks[*].job_cluster_key -tasks[*].libraries[*].cran.package -tasks[*].libraries[*].cran.repo -tasks[*].libraries[*].egg -tasks[*].libraries[*].jar -tasks[*].libraries[*].maven.coordinates -tasks[*].libraries[*].maven.exclusions[*] -tasks[*].libraries[*].maven.repo -tasks[*].libraries[*].pypi.package -tasks[*].libraries[*].pypi.repo -tasks[*].libraries[*].requirements -tasks[*].libraries[*].whl -tasks[*].max_retries -tasks[*].min_retry_interval_millis -tasks[*].new_cluster.apply_policy_default_values -tasks[*].new_cluster.autoscale.max_workers -tasks[*].new_cluster.autoscale.min_workers -tasks[*].new_cluster.autotermination_minutes -tasks[*].new_cluster.aws_attributes.availability -tasks[*].new_cluster.aws_attributes.ebs_volume_count -tasks[*].new_cluster.aws_attributes.ebs_volume_iops -tasks[*].new_cluster.aws_attributes.ebs_volume_size -tasks[*].new_cluster.aws_attributes.ebs_volume_throughput -tasks[*].new_cluster.aws_attributes.ebs_volume_type -tasks[*].new_cluster.aws_attributes.first_on_demand -tasks[*].new_cluster.aws_attributes.instance_profile_arn -tasks[*].new_cluster.aws_attributes.spot_bid_price_percent -tasks[*].new_cluster.aws_attributes.zone_id -tasks[*].new_cluster.azure_attributes.availability -tasks[*].new_cluster.azure_attributes.capacity_reservation_group -tasks[*].new_cluster.azure_attributes.first_on_demand -tasks[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key -tasks[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id -tasks[*].new_cluster.azure_attributes.spot_bid_max_price -tasks[*].new_cluster.cluster_log_conf.dbfs.destination -tasks[*].new_cluster.cluster_log_conf.s3.canned_acl -tasks[*].new_cluster.cluster_log_conf.s3.destination -tasks[*].new_cluster.cluster_log_conf.s3.enable_encryption -tasks[*].new_cluster.cluster_log_conf.s3.encryption_type -tasks[*].new_cluster.cluster_log_conf.s3.endpoint -tasks[*].new_cluster.cluster_log_conf.s3.kms_key -tasks[*].new_cluster.cluster_log_conf.s3.region -tasks[*].new_cluster.cluster_log_conf.volumes.destination -tasks[*].new_cluster.cluster_name -tasks[*].new_cluster.custom_tags.* -tasks[*].new_cluster.data_security_mode -tasks[*].new_cluster.dependency_mode -tasks[*].new_cluster.docker_image.basic_auth.password -tasks[*].new_cluster.docker_image.basic_auth.username -tasks[*].new_cluster.docker_image.url -tasks[*].new_cluster.driver_instance_pool_id -tasks[*].new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] -tasks[*].new_cluster.driver_node_type_id -tasks[*].new_cluster.enable_elastic_disk -tasks[*].new_cluster.enable_local_disk_encryption -tasks[*].new_cluster.gcp_attributes.availability -tasks[*].new_cluster.gcp_attributes.boot_disk_size -tasks[*].new_cluster.gcp_attributes.confidential_compute_type -tasks[*].new_cluster.gcp_attributes.first_on_demand -tasks[*].new_cluster.gcp_attributes.google_service_account -tasks[*].new_cluster.gcp_attributes.local_ssd_count -tasks[*].new_cluster.gcp_attributes.use_preemptible_executors -tasks[*].new_cluster.gcp_attributes.zone_id -tasks[*].new_cluster.init_scripts[*].abfss.destination -tasks[*].new_cluster.init_scripts[*].dbfs.destination -tasks[*].new_cluster.init_scripts[*].file.destination -tasks[*].new_cluster.init_scripts[*].gcs.destination -tasks[*].new_cluster.init_scripts[*].s3.canned_acl -tasks[*].new_cluster.init_scripts[*].s3.destination -tasks[*].new_cluster.init_scripts[*].s3.enable_encryption -tasks[*].new_cluster.init_scripts[*].s3.encryption_type -tasks[*].new_cluster.init_scripts[*].s3.endpoint -tasks[*].new_cluster.init_scripts[*].s3.kms_key -tasks[*].new_cluster.init_scripts[*].s3.region -tasks[*].new_cluster.init_scripts[*].volumes.destination -tasks[*].new_cluster.init_scripts[*].workspace.destination -tasks[*].new_cluster.instance_pool_id -tasks[*].new_cluster.is_single_node -tasks[*].new_cluster.kind -tasks[*].new_cluster.node_type_id -tasks[*].new_cluster.num_workers -tasks[*].new_cluster.policy_id -tasks[*].new_cluster.remote_disk_throughput -tasks[*].new_cluster.runtime_engine -tasks[*].new_cluster.single_user_name -tasks[*].new_cluster.spark_conf.* -tasks[*].new_cluster.spark_env_vars.* -tasks[*].new_cluster.spark_version -tasks[*].new_cluster.ssh_public_keys[*] -tasks[*].new_cluster.total_initial_remote_disk_size -tasks[*].new_cluster.use_ml_runtime -tasks[*].new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] -tasks[*].new_cluster.workload_type.clients.jobs -tasks[*].new_cluster.workload_type.clients.notebooks -tasks[*].notebook_task.base_parameters.* -tasks[*].notebook_task.notebook_path -tasks[*].notebook_task.source -tasks[*].notebook_task.warehouse_id -tasks[*].notification_settings.alert_on_last_attempt -tasks[*].notification_settings.no_alert_for_canceled_runs -tasks[*].notification_settings.no_alert_for_skipped_runs -tasks[*].pipeline_task.full_refresh -tasks[*].pipeline_task.full_refresh_selection[*] -tasks[*].pipeline_task.parameters.* -tasks[*].pipeline_task.pipeline_id -tasks[*].pipeline_task.refresh_flow_selection[*] -tasks[*].pipeline_task.refresh_selection[*] -tasks[*].pipeline_task.reset_checkpoint_selection[*] -tasks[*].power_bi_task.connection_resource_name -tasks[*].power_bi_task.power_bi_model.authentication_method -tasks[*].power_bi_task.power_bi_model.model_name -tasks[*].power_bi_task.power_bi_model.overwrite_existing -tasks[*].power_bi_task.power_bi_model.storage_mode -tasks[*].power_bi_task.power_bi_model.workspace_name -tasks[*].power_bi_task.refresh_after_update -tasks[*].power_bi_task.tables[*].catalog -tasks[*].power_bi_task.tables[*].name -tasks[*].power_bi_task.tables[*].schema -tasks[*].power_bi_task.tables[*].storage_mode -tasks[*].power_bi_task.warehouse_id -tasks[*].python_operator_task.main -tasks[*].python_operator_task.parameters[*].name -tasks[*].python_operator_task.parameters[*].value -tasks[*].python_wheel_task.entry_point -tasks[*].python_wheel_task.named_parameters.* -tasks[*].python_wheel_task.package_name -tasks[*].python_wheel_task.parameters[*] -tasks[*].retry_on_timeout -tasks[*].run_if -tasks[*].run_job_task.dbt_commands[*] -tasks[*].run_job_task.jar_params[*] -tasks[*].run_job_task.job_id -tasks[*].run_job_task.job_parameters.* -tasks[*].run_job_task.notebook_params.* -tasks[*].run_job_task.pipeline_params.full_refresh -tasks[*].run_job_task.pipeline_params.full_refresh_selection[*] -tasks[*].run_job_task.pipeline_params.refresh_flow_selection[*] -tasks[*].run_job_task.pipeline_params.refresh_selection[*] -tasks[*].run_job_task.pipeline_params.reset_checkpoint_selection[*] -tasks[*].run_job_task.python_named_params.* -tasks[*].run_job_task.python_params[*] -tasks[*].run_job_task.spark_submit_params[*] -tasks[*].run_job_task.sql_params.* -tasks[*].spark_jar_task.jar_uri -tasks[*].spark_jar_task.main_class_name -tasks[*].spark_jar_task.parameters[*] -tasks[*].spark_jar_task.run_as_repl -tasks[*].spark_python_task.parameters[*] -tasks[*].spark_python_task.python_file -tasks[*].spark_python_task.source -tasks[*].spark_submit_task.parameters[*] -tasks[*].sql_task.alert.alert_id -tasks[*].sql_task.alert.pause_subscriptions -tasks[*].sql_task.alert.subscriptions[*].destination_id -tasks[*].sql_task.alert.subscriptions[*].user_name -tasks[*].sql_task.dashboard.custom_subject -tasks[*].sql_task.dashboard.dashboard_id -tasks[*].sql_task.dashboard.pause_subscriptions -tasks[*].sql_task.dashboard.subscriptions[*].destination_id -tasks[*].sql_task.dashboard.subscriptions[*].user_name -tasks[*].sql_task.file.path -tasks[*].sql_task.file.source -tasks[*].sql_task.parameters.* -tasks[*].sql_task.query.query_id -tasks[*].sql_task.warehouse_id -tasks[*].task_key -tasks[*].timeout_seconds -tasks[*].webhook_notifications.on_duration_warning_threshold_exceeded[*].id -tasks[*].webhook_notifications.on_failure[*].id -tasks[*].webhook_notifications.on_start[*].id -tasks[*].webhook_notifications.on_streaming_backlog_exceeded[*].id -tasks[*].webhook_notifications.on_success[*].id -trigger.model.aliases[*] -trigger.table_update.table_names[*] -triggers[*].continuous.task_retry_mode -triggers[*].file_arrival.min_time_between_triggers_seconds -triggers[*].file_arrival.url -triggers[*].file_arrival.wait_after_last_change_seconds -triggers[*].model.aliases[*] -triggers[*].model.condition -triggers[*].model.min_time_between_triggers_seconds -triggers[*].model.securable_name -triggers[*].model.wait_after_last_change_seconds -triggers[*].pause_status -triggers[*].periodic.interval -triggers[*].periodic.unit -triggers[*].schedule.quartz_cron_expression -triggers[*].schedule.timezone_id -triggers[*].sql_condition.sql_query_id -triggers[*].sql_condition.trigger_mode -triggers[*].sql_condition.warehouse_id -triggers[*].table_update.condition -triggers[*].table_update.min_time_between_triggers_seconds -triggers[*].table_update.table_names[*] -triggers[*].table_update.wait_after_last_change_seconds -webhook_notifications.on_duration_warning_threshold_exceeded[*].id -webhook_notifications.on_failure[*].id -webhook_notifications.on_start[*].id -webhook_notifications.on_streaming_backlog_exceeded[*].id -webhook_notifications.on_success[*].id +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y x SUPPRESSED managed by the deployment metadata service diff --git a/bundle/direct/tests/output/model_serving_endpoints.full.txt b/bundle/direct/tests/output/model_serving_endpoints.full.txt new file mode 100644 index 00000000000..e06981a668a --- /dev/null +++ b/bundle/direct/tests/output/model_serving_endpoints.full.txt @@ -0,0 +1,334 @@ +=== model_serving_endpoint.yml.tmpl +ai_gateway.fallback_config.enabled false true OK +ai_gateway.fallback_config.enabled true false OK +ai_gateway.guardrails.input.pii.behavior absent x OK +ai_gateway.guardrails.input.pii.behavior absent y OK +ai_gateway.guardrails.input.pii.behavior x absent OK +ai_gateway.guardrails.input.pii.behavior x y OK +ai_gateway.guardrails.input.pii.behavior y absent OK +ai_gateway.guardrails.input.pii.behavior y x OK +ai_gateway.guardrails.input.safety absent false SUPPRESSED empty +ai_gateway.guardrails.input.safety absent true OK +ai_gateway.guardrails.input.safety false absent SUPPRESSED empty +ai_gateway.guardrails.input.safety false true OK +ai_gateway.guardrails.input.safety true absent OK +ai_gateway.guardrails.input.safety true false OK +ai_gateway.guardrails.output.pii.behavior absent x OK +ai_gateway.guardrails.output.pii.behavior absent y OK +ai_gateway.guardrails.output.pii.behavior x absent OK +ai_gateway.guardrails.output.pii.behavior x y OK +ai_gateway.guardrails.output.pii.behavior y absent OK +ai_gateway.guardrails.output.pii.behavior y x OK +ai_gateway.guardrails.output.safety absent false SUPPRESSED empty +ai_gateway.guardrails.output.safety absent true OK +ai_gateway.guardrails.output.safety false absent SUPPRESSED empty +ai_gateway.guardrails.output.safety false true OK +ai_gateway.guardrails.output.safety true absent OK +ai_gateway.guardrails.output.safety true false OK +ai_gateway.inference_table_config.catalog_name absent x OK +ai_gateway.inference_table_config.catalog_name absent y OK +ai_gateway.inference_table_config.catalog_name x absent OK +ai_gateway.inference_table_config.catalog_name x y OK +ai_gateway.inference_table_config.catalog_name y absent OK +ai_gateway.inference_table_config.catalog_name y x OK +ai_gateway.inference_table_config.enabled absent false SUPPRESSED empty +ai_gateway.inference_table_config.enabled absent true OK +ai_gateway.inference_table_config.enabled false absent SUPPRESSED empty +ai_gateway.inference_table_config.enabled false true OK +ai_gateway.inference_table_config.enabled true absent OK +ai_gateway.inference_table_config.enabled true false OK +ai_gateway.inference_table_config.schema_name absent x OK +ai_gateway.inference_table_config.schema_name absent y OK +ai_gateway.inference_table_config.schema_name x absent OK +ai_gateway.inference_table_config.schema_name x y OK +ai_gateway.inference_table_config.schema_name y absent OK +ai_gateway.inference_table_config.schema_name y x OK +ai_gateway.inference_table_config.table_name_prefix absent x OK +ai_gateway.inference_table_config.table_name_prefix absent y OK +ai_gateway.inference_table_config.table_name_prefix x absent OK +ai_gateway.inference_table_config.table_name_prefix x y OK +ai_gateway.inference_table_config.table_name_prefix y absent OK +ai_gateway.inference_table_config.table_name_prefix y x OK +ai_gateway.usage_tracking_config.enabled absent false OK +ai_gateway.usage_tracking_config.enabled absent true OK +ai_gateway.usage_tracking_config.enabled false absent SUPPRESSED empty +ai_gateway.usage_tracking_config.enabled false true OK +ai_gateway.usage_tracking_config.enabled true absent OK +ai_gateway.usage_tracking_config.enabled true false OK +budget_policy_id absent x SUPPRESSED no_update_api +budget_policy_id absent y SUPPRESSED no_update_api +budget_policy_id x absent NOT_OBSERVABLE +budget_policy_id x y SUPPRESSED no_update_api +budget_policy_id y absent NOT_OBSERVABLE +budget_policy_id y x SUPPRESSED no_update_api +config.auto_capture_config.catalog_name absent x POST_DEPLOY_DRIFT config +config.auto_capture_config.catalog_name absent y OK_RECREATE +config.auto_capture_config.catalog_name x absent OK_RECREATE +config.auto_capture_config.catalog_name x y OK_RECREATE +config.auto_capture_config.catalog_name y absent OK_RECREATE +config.auto_capture_config.catalog_name y x OK_RECREATE +config.auto_capture_config.enabled absent false SUPPRESSED empty +config.auto_capture_config.enabled absent true POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled false absent POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled false true POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled true absent POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled true false POST_DEPLOY_DRIFT config +config.auto_capture_config.schema_name absent x POST_DEPLOY_DRIFT config +config.auto_capture_config.schema_name absent y OK_RECREATE +config.auto_capture_config.schema_name x absent OK_RECREATE +config.auto_capture_config.schema_name x y OK_RECREATE +config.auto_capture_config.schema_name y absent OK_RECREATE +config.auto_capture_config.schema_name y x OK_RECREATE +config.auto_capture_config.table_name_prefix absent x OK_RECREATE +config.auto_capture_config.table_name_prefix absent y OK_RECREATE +config.auto_capture_config.table_name_prefix x absent OK_RECREATE +config.auto_capture_config.table_name_prefix x y OK_RECREATE +config.auto_capture_config.table_name_prefix y absent OK_RECREATE +config.auto_capture_config.table_name_prefix y x OK_RECREATE +description absent x OK_RECREATE +description absent y OK_RECREATE +description x absent OK_RECREATE +description x y OK_RECREATE +description y absent OK_RECREATE +description y x OK_RECREATE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK_RECREATE +name y x OK_RECREATE +route_optimized absent false SUPPRESSED remote_already_set +route_optimized absent true OK_RECREATE +route_optimized false absent SUPPRESSED empty +route_optimized false true OK_RECREATE +route_optimized true absent OK_RECREATE +route_optimized true false OK_RECREATE +telemetry_config.inference_table_config.name SKIPPED needs a writable catalog and schema +telemetry_config.inference_table_config.sampling_fraction SKIPPED needs a writable catalog and schema +telemetry_config.table_names.annotations_table SKIPPED needs a writable catalog and schema +telemetry_config.table_names.logs_table SKIPPED needs a writable catalog and schema +telemetry_config.table_names.metrics_table SKIPPED needs a writable catalog and schema +telemetry_config.table_names.traces_table SKIPPED needs a writable catalog and schema +telemetry_config.telemetry_profile_id SKIPPED needs a writable catalog and schema + +=== model_serving_endpoint_telemetry.yml.tmpl +ai_gateway.fallback_config.enabled false true OK +ai_gateway.fallback_config.enabled true false OK +ai_gateway.guardrails.input.pii.behavior absent x OK +ai_gateway.guardrails.input.pii.behavior absent y OK +ai_gateway.guardrails.input.pii.behavior x absent OK +ai_gateway.guardrails.input.pii.behavior x y OK +ai_gateway.guardrails.input.pii.behavior y absent OK +ai_gateway.guardrails.input.pii.behavior y x OK +ai_gateway.guardrails.input.safety absent false SUPPRESSED empty +ai_gateway.guardrails.input.safety absent true OK +ai_gateway.guardrails.input.safety false absent SUPPRESSED empty +ai_gateway.guardrails.input.safety false true OK +ai_gateway.guardrails.input.safety true absent OK +ai_gateway.guardrails.input.safety true false OK +ai_gateway.guardrails.output.pii.behavior absent x OK +ai_gateway.guardrails.output.pii.behavior absent y OK +ai_gateway.guardrails.output.pii.behavior x absent OK +ai_gateway.guardrails.output.pii.behavior x y OK +ai_gateway.guardrails.output.pii.behavior y absent OK +ai_gateway.guardrails.output.pii.behavior y x OK +ai_gateway.guardrails.output.safety absent false SUPPRESSED empty +ai_gateway.guardrails.output.safety absent true OK +ai_gateway.guardrails.output.safety false absent SUPPRESSED empty +ai_gateway.guardrails.output.safety false true OK +ai_gateway.guardrails.output.safety true absent OK +ai_gateway.guardrails.output.safety true false OK +ai_gateway.inference_table_config.catalog_name absent x OK +ai_gateway.inference_table_config.catalog_name absent y OK +ai_gateway.inference_table_config.catalog_name x absent OK +ai_gateway.inference_table_config.catalog_name x y OK +ai_gateway.inference_table_config.catalog_name y absent OK +ai_gateway.inference_table_config.catalog_name y x OK +ai_gateway.inference_table_config.enabled absent false SUPPRESSED empty +ai_gateway.inference_table_config.enabled absent true OK +ai_gateway.inference_table_config.enabled false absent SUPPRESSED empty +ai_gateway.inference_table_config.enabled false true OK +ai_gateway.inference_table_config.enabled true absent OK +ai_gateway.inference_table_config.enabled true false OK +ai_gateway.inference_table_config.schema_name absent x OK +ai_gateway.inference_table_config.schema_name absent y OK +ai_gateway.inference_table_config.schema_name x absent OK +ai_gateway.inference_table_config.schema_name x y OK +ai_gateway.inference_table_config.schema_name y absent OK +ai_gateway.inference_table_config.schema_name y x OK +ai_gateway.inference_table_config.table_name_prefix absent x OK +ai_gateway.inference_table_config.table_name_prefix absent y OK +ai_gateway.inference_table_config.table_name_prefix x absent OK +ai_gateway.inference_table_config.table_name_prefix x y OK +ai_gateway.inference_table_config.table_name_prefix y absent OK +ai_gateway.inference_table_config.table_name_prefix y x OK +ai_gateway.usage_tracking_config.enabled absent false OK +ai_gateway.usage_tracking_config.enabled absent true OK +ai_gateway.usage_tracking_config.enabled false absent SUPPRESSED empty +ai_gateway.usage_tracking_config.enabled false true OK +ai_gateway.usage_tracking_config.enabled true absent OK +ai_gateway.usage_tracking_config.enabled true false OK +budget_policy_id absent x SUPPRESSED no_update_api +budget_policy_id absent y SUPPRESSED no_update_api +budget_policy_id x absent NOT_OBSERVABLE +budget_policy_id x y SUPPRESSED no_update_api +budget_policy_id y absent NOT_OBSERVABLE +budget_policy_id y x SUPPRESSED no_update_api +config.auto_capture_config.catalog_name absent x OK +config.auto_capture_config.catalog_name absent y OK_RECREATE +config.auto_capture_config.catalog_name x absent OK_RECREATE +config.auto_capture_config.catalog_name x y OK_RECREATE +config.auto_capture_config.catalog_name y absent OK_RECREATE +config.auto_capture_config.catalog_name y x OK_RECREATE +config.auto_capture_config.enabled absent false SUPPRESSED empty +config.auto_capture_config.enabled absent true OK +config.auto_capture_config.enabled false absent SUPPRESSED empty +config.auto_capture_config.enabled false true OK +config.auto_capture_config.enabled true absent OK +config.auto_capture_config.enabled true false OK +config.auto_capture_config.schema_name absent x OK_RECREATE +config.auto_capture_config.schema_name absent y OK_RECREATE +config.auto_capture_config.schema_name x absent OK_RECREATE +config.auto_capture_config.schema_name x y OK_RECREATE +config.auto_capture_config.schema_name y absent OK_RECREATE +config.auto_capture_config.schema_name y x OK_RECREATE +config.auto_capture_config.table_name_prefix absent x OK_RECREATE +config.auto_capture_config.table_name_prefix absent y OK_RECREATE +config.auto_capture_config.table_name_prefix x absent OK_RECREATE +config.auto_capture_config.table_name_prefix x y OK_RECREATE +config.auto_capture_config.table_name_prefix y absent OK_RECREATE +config.auto_capture_config.table_name_prefix y x OK_RECREATE +description absent x OK_RECREATE +description absent y OK_RECREATE +description x absent OK_RECREATE +description x y OK_RECREATE +description y absent OK_RECREATE +description y x OK_RECREATE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK_RECREATE +name y x OK_RECREATE +route_optimized absent false SUPPRESSED remote_already_set +route_optimized absent true OK_RECREATE +route_optimized false absent SUPPRESSED empty +route_optimized false true OK_RECREATE +route_optimized true absent OK_RECREATE +route_optimized true false OK_RECREATE +telemetry_config.inference_table_config.name SKIPPED needs a writable catalog and schema +telemetry_config.inference_table_config.sampling_fraction SKIPPED needs a writable catalog and schema +telemetry_config.table_names.annotations_table SKIPPED needs a writable catalog and schema +telemetry_config.table_names.logs_table SKIPPED needs a writable catalog and schema +telemetry_config.table_names.metrics_table SKIPPED needs a writable catalog and schema +telemetry_config.table_names.traces_table SKIPPED needs a writable catalog and schema +telemetry_config.telemetry_profile_id SKIPPED needs a writable catalog and schema + +=== summary +OK 103 +OK_RECREATE 57 +SUPPRESSED 29 +NOT_OBSERVABLE 16 +POST_DEPLOY_DRIFT 7 +SKIPPED 14 + +=== not covered: 95 fields under a slice or map +ai_gateway.guardrails.input.invalid_keywords[*] +ai_gateway.guardrails.input.valid_topics[*] +ai_gateway.guardrails.output.invalid_keywords[*] +ai_gateway.guardrails.output.valid_topics[*] +ai_gateway.rate_limits[*].calls +ai_gateway.rate_limits[*].key +ai_gateway.rate_limits[*].principal +ai_gateway.rate_limits[*].renewal_period +ai_gateway.rate_limits[*].tokens +config.served_entities[*].burst_scaling_enabled +config.served_entities[*].entity_name +config.served_entities[*].entity_version +config.served_entities[*].environment_vars.* +config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key +config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext +config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id +config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id_plaintext +config.served_entities[*].external_model.amazon_bedrock_config.aws_region +config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key +config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key_plaintext +config.served_entities[*].external_model.amazon_bedrock_config.bedrock_provider +config.served_entities[*].external_model.amazon_bedrock_config.instance_profile_arn +config.served_entities[*].external_model.anthropic_config.anthropic_api_key +config.served_entities[*].external_model.anthropic_config.anthropic_api_key_plaintext +config.served_entities[*].external_model.cohere_config.cohere_api_base +config.served_entities[*].external_model.cohere_config.cohere_api_key +config.served_entities[*].external_model.cohere_config.cohere_api_key_plaintext +config.served_entities[*].external_model.custom_provider_config.api_key_auth.key +config.served_entities[*].external_model.custom_provider_config.api_key_auth.value +config.served_entities[*].external_model.custom_provider_config.api_key_auth.value_plaintext +config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token +config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token_plaintext +config.served_entities[*].external_model.custom_provider_config.custom_provider_url +config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token +config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token_plaintext +config.served_entities[*].external_model.databricks_model_serving_config.databricks_workspace_url +config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key +config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key_plaintext +config.served_entities[*].external_model.google_cloud_vertex_ai_config.project_id +config.served_entities[*].external_model.google_cloud_vertex_ai_config.region +config.served_entities[*].external_model.name +config.served_entities[*].external_model.openai_config.microsoft_entra_client_id +config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret +config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret_plaintext +config.served_entities[*].external_model.openai_config.microsoft_entra_tenant_id +config.served_entities[*].external_model.openai_config.openai_api_base +config.served_entities[*].external_model.openai_config.openai_api_key +config.served_entities[*].external_model.openai_config.openai_api_key_plaintext +config.served_entities[*].external_model.openai_config.openai_api_type +config.served_entities[*].external_model.openai_config.openai_api_version +config.served_entities[*].external_model.openai_config.openai_deployment_name +config.served_entities[*].external_model.openai_config.openai_organization +config.served_entities[*].external_model.palm_config.palm_api_key +config.served_entities[*].external_model.palm_config.palm_api_key_plaintext +config.served_entities[*].external_model.provider +config.served_entities[*].external_model.task +config.served_entities[*].instance_profile_arn +config.served_entities[*].max_provisioned_concurrency +config.served_entities[*].max_provisioned_throughput +config.served_entities[*].min_provisioned_concurrency +config.served_entities[*].min_provisioned_throughput +config.served_entities[*].name +config.served_entities[*].provisioned_model_units +config.served_entities[*].scale_to_zero_enabled +config.served_entities[*].workload_size +config.served_entities[*].workload_type +config.served_models[*].burst_scaling_enabled +config.served_models[*].environment_vars.* +config.served_models[*].instance_profile_arn +config.served_models[*].max_provisioned_concurrency +config.served_models[*].max_provisioned_throughput +config.served_models[*].min_provisioned_concurrency +config.served_models[*].min_provisioned_throughput +config.served_models[*].model_name +config.served_models[*].model_version +config.served_models[*].name +config.served_models[*].provisioned_model_units +config.served_models[*].scale_to_zero_enabled +config.served_models[*].workload_size +config.served_models[*].workload_type +config.traffic_config.routes[*].served_entity_name +config.traffic_config.routes[*].served_model_name +config.traffic_config.routes[*].traffic_percentage +email_notifications.on_update_failure[*] +email_notifications.on_update_success[*] +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +rate_limits[*].calls +rate_limits[*].key +rate_limits[*].renewal_period +tags[*].key +tags[*].value +telemetry_config.enabled_telemetry_features[*] diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index e27fd98e2e2..81fc83f63e5 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -1,334 +1,20 @@ === model_serving_endpoint.yml.tmpl -ai_gateway.fallback_config.enabled false->true OK -ai_gateway.fallback_config.enabled true->false OK -ai_gateway.guardrails.input.pii.behavior absent->x OK -ai_gateway.guardrails.input.pii.behavior absent->y OK -ai_gateway.guardrails.input.pii.behavior x->absent OK -ai_gateway.guardrails.input.pii.behavior x->y OK -ai_gateway.guardrails.input.pii.behavior y->absent OK -ai_gateway.guardrails.input.pii.behavior y->x OK -ai_gateway.guardrails.input.safety absent->false SUPPRESSED empty -ai_gateway.guardrails.input.safety absent->true OK -ai_gateway.guardrails.input.safety false->absent SUPPRESSED empty -ai_gateway.guardrails.input.safety false->true OK -ai_gateway.guardrails.input.safety true->absent OK -ai_gateway.guardrails.input.safety true->false OK -ai_gateway.guardrails.output.pii.behavior absent->x OK -ai_gateway.guardrails.output.pii.behavior absent->y OK -ai_gateway.guardrails.output.pii.behavior x->absent OK -ai_gateway.guardrails.output.pii.behavior x->y OK -ai_gateway.guardrails.output.pii.behavior y->absent OK -ai_gateway.guardrails.output.pii.behavior y->x OK -ai_gateway.guardrails.output.safety absent->false SUPPRESSED empty -ai_gateway.guardrails.output.safety absent->true OK -ai_gateway.guardrails.output.safety false->absent SUPPRESSED empty -ai_gateway.guardrails.output.safety false->true OK -ai_gateway.guardrails.output.safety true->absent OK -ai_gateway.guardrails.output.safety true->false OK -ai_gateway.inference_table_config.catalog_name absent->x OK -ai_gateway.inference_table_config.catalog_name absent->y OK -ai_gateway.inference_table_config.catalog_name x->absent OK -ai_gateway.inference_table_config.catalog_name x->y OK -ai_gateway.inference_table_config.catalog_name y->absent OK -ai_gateway.inference_table_config.catalog_name y->x OK -ai_gateway.inference_table_config.enabled absent->false SUPPRESSED empty -ai_gateway.inference_table_config.enabled absent->true OK -ai_gateway.inference_table_config.enabled false->absent SUPPRESSED empty -ai_gateway.inference_table_config.enabled false->true OK -ai_gateway.inference_table_config.enabled true->absent OK -ai_gateway.inference_table_config.enabled true->false OK -ai_gateway.inference_table_config.schema_name absent->x OK -ai_gateway.inference_table_config.schema_name absent->y OK -ai_gateway.inference_table_config.schema_name x->absent OK -ai_gateway.inference_table_config.schema_name x->y OK -ai_gateway.inference_table_config.schema_name y->absent OK -ai_gateway.inference_table_config.schema_name y->x OK -ai_gateway.inference_table_config.table_name_prefix absent->x OK -ai_gateway.inference_table_config.table_name_prefix absent->y OK -ai_gateway.inference_table_config.table_name_prefix x->absent OK -ai_gateway.inference_table_config.table_name_prefix x->y OK -ai_gateway.inference_table_config.table_name_prefix y->absent OK -ai_gateway.inference_table_config.table_name_prefix y->x OK -ai_gateway.usage_tracking_config.enabled absent->false OK -ai_gateway.usage_tracking_config.enabled absent->true OK -ai_gateway.usage_tracking_config.enabled false->absent SUPPRESSED empty -ai_gateway.usage_tracking_config.enabled false->true OK -ai_gateway.usage_tracking_config.enabled true->absent OK -ai_gateway.usage_tracking_config.enabled true->false OK -budget_policy_id absent->x SUPPRESSED no_update_api -budget_policy_id absent->y SUPPRESSED no_update_api -budget_policy_id x->absent NOT_OBSERVABLE -budget_policy_id x->y SUPPRESSED no_update_api -budget_policy_id y->absent NOT_OBSERVABLE -budget_policy_id y->x SUPPRESSED no_update_api -config.auto_capture_config.catalog_name absent->x DRIFT config -config.auto_capture_config.catalog_name absent->y OK_RECREATE -config.auto_capture_config.catalog_name x->absent OK_RECREATE -config.auto_capture_config.catalog_name x->y OK_RECREATE -config.auto_capture_config.catalog_name y->absent OK_RECREATE -config.auto_capture_config.catalog_name y->x OK_RECREATE -config.auto_capture_config.enabled absent->false SUPPRESSED empty -config.auto_capture_config.enabled absent->true DRIFT config -config.auto_capture_config.enabled false->absent DRIFT config -config.auto_capture_config.enabled false->true DRIFT config -config.auto_capture_config.enabled true->absent DRIFT config -config.auto_capture_config.enabled true->false DRIFT config -config.auto_capture_config.schema_name absent->x DRIFT config -config.auto_capture_config.schema_name absent->y OK_RECREATE -config.auto_capture_config.schema_name x->absent OK_RECREATE -config.auto_capture_config.schema_name x->y OK_RECREATE -config.auto_capture_config.schema_name y->absent OK_RECREATE -config.auto_capture_config.schema_name y->x OK_RECREATE -config.auto_capture_config.table_name_prefix absent->x OK_RECREATE -config.auto_capture_config.table_name_prefix absent->y OK_RECREATE -config.auto_capture_config.table_name_prefix x->absent OK_RECREATE -config.auto_capture_config.table_name_prefix x->y OK_RECREATE -config.auto_capture_config.table_name_prefix y->absent OK_RECREATE -config.auto_capture_config.table_name_prefix y->x OK_RECREATE -description absent->x OK_RECREATE -description absent->y OK_RECREATE -description x->absent OK_RECREATE -description x->y OK_RECREATE -description y->absent OK_RECREATE -description y->x OK_RECREATE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK_RECREATE -name y->x OK_RECREATE -route_optimized absent->false SUPPRESSED remote_already_set -route_optimized absent->true OK_RECREATE -route_optimized false->absent SUPPRESSED empty -route_optimized false->true OK_RECREATE -route_optimized true->absent OK_RECREATE -route_optimized true->false OK_RECREATE -telemetry_config.inference_table_config.name - SKIPPED needs a writable catalog and schema -telemetry_config.inference_table_config.sampling_fraction - SKIPPED needs a writable catalog and schema -telemetry_config.table_names.annotations_table - SKIPPED needs a writable catalog and schema -telemetry_config.table_names.logs_table - SKIPPED needs a writable catalog and schema -telemetry_config.table_names.metrics_table - SKIPPED needs a writable catalog and schema -telemetry_config.table_names.traces_table - SKIPPED needs a writable catalog and schema -telemetry_config.telemetry_profile_id - SKIPPED needs a writable catalog and schema +budget_policy_id absent x SUPPRESSED no_update_api +budget_policy_id absent y SUPPRESSED no_update_api +budget_policy_id x y SUPPRESSED no_update_api +budget_policy_id y x SUPPRESSED no_update_api +config.auto_capture_config.catalog_name absent x POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled absent true POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled false absent POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled false true POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled true absent POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled true false POST_DEPLOY_DRIFT config +config.auto_capture_config.schema_name absent x POST_DEPLOY_DRIFT config +route_optimized absent false SUPPRESSED remote_already_set === model_serving_endpoint_telemetry.yml.tmpl -ai_gateway.fallback_config.enabled false->true OK -ai_gateway.fallback_config.enabled true->false OK -ai_gateway.guardrails.input.pii.behavior absent->x OK -ai_gateway.guardrails.input.pii.behavior absent->y OK -ai_gateway.guardrails.input.pii.behavior x->absent OK -ai_gateway.guardrails.input.pii.behavior x->y OK -ai_gateway.guardrails.input.pii.behavior y->absent OK -ai_gateway.guardrails.input.pii.behavior y->x OK -ai_gateway.guardrails.input.safety absent->false SUPPRESSED empty -ai_gateway.guardrails.input.safety absent->true OK -ai_gateway.guardrails.input.safety false->absent SUPPRESSED empty -ai_gateway.guardrails.input.safety false->true OK -ai_gateway.guardrails.input.safety true->absent OK -ai_gateway.guardrails.input.safety true->false OK -ai_gateway.guardrails.output.pii.behavior absent->x OK -ai_gateway.guardrails.output.pii.behavior absent->y OK -ai_gateway.guardrails.output.pii.behavior x->absent OK -ai_gateway.guardrails.output.pii.behavior x->y OK -ai_gateway.guardrails.output.pii.behavior y->absent OK -ai_gateway.guardrails.output.pii.behavior y->x OK -ai_gateway.guardrails.output.safety absent->false SUPPRESSED empty -ai_gateway.guardrails.output.safety absent->true OK -ai_gateway.guardrails.output.safety false->absent SUPPRESSED empty -ai_gateway.guardrails.output.safety false->true OK -ai_gateway.guardrails.output.safety true->absent OK -ai_gateway.guardrails.output.safety true->false OK -ai_gateway.inference_table_config.catalog_name absent->x OK -ai_gateway.inference_table_config.catalog_name absent->y OK -ai_gateway.inference_table_config.catalog_name x->absent OK -ai_gateway.inference_table_config.catalog_name x->y OK -ai_gateway.inference_table_config.catalog_name y->absent OK -ai_gateway.inference_table_config.catalog_name y->x OK -ai_gateway.inference_table_config.enabled absent->false SUPPRESSED empty -ai_gateway.inference_table_config.enabled absent->true OK -ai_gateway.inference_table_config.enabled false->absent SUPPRESSED empty -ai_gateway.inference_table_config.enabled false->true OK -ai_gateway.inference_table_config.enabled true->absent OK -ai_gateway.inference_table_config.enabled true->false OK -ai_gateway.inference_table_config.schema_name absent->x OK -ai_gateway.inference_table_config.schema_name absent->y OK -ai_gateway.inference_table_config.schema_name x->absent OK -ai_gateway.inference_table_config.schema_name x->y OK -ai_gateway.inference_table_config.schema_name y->absent OK -ai_gateway.inference_table_config.schema_name y->x OK -ai_gateway.inference_table_config.table_name_prefix absent->x OK -ai_gateway.inference_table_config.table_name_prefix absent->y OK -ai_gateway.inference_table_config.table_name_prefix x->absent OK -ai_gateway.inference_table_config.table_name_prefix x->y OK -ai_gateway.inference_table_config.table_name_prefix y->absent OK -ai_gateway.inference_table_config.table_name_prefix y->x OK -ai_gateway.usage_tracking_config.enabled absent->false OK -ai_gateway.usage_tracking_config.enabled absent->true OK -ai_gateway.usage_tracking_config.enabled false->absent SUPPRESSED empty -ai_gateway.usage_tracking_config.enabled false->true OK -ai_gateway.usage_tracking_config.enabled true->absent OK -ai_gateway.usage_tracking_config.enabled true->false OK -budget_policy_id absent->x SUPPRESSED no_update_api -budget_policy_id absent->y SUPPRESSED no_update_api -budget_policy_id x->absent NOT_OBSERVABLE -budget_policy_id x->y SUPPRESSED no_update_api -budget_policy_id y->absent NOT_OBSERVABLE -budget_policy_id y->x SUPPRESSED no_update_api -config.auto_capture_config.catalog_name absent->x OK -config.auto_capture_config.catalog_name absent->y OK_RECREATE -config.auto_capture_config.catalog_name x->absent OK_RECREATE -config.auto_capture_config.catalog_name x->y OK_RECREATE -config.auto_capture_config.catalog_name y->absent OK_RECREATE -config.auto_capture_config.catalog_name y->x OK_RECREATE -config.auto_capture_config.enabled absent->false SUPPRESSED empty -config.auto_capture_config.enabled absent->true OK -config.auto_capture_config.enabled false->absent SUPPRESSED empty -config.auto_capture_config.enabled false->true OK -config.auto_capture_config.enabled true->absent OK -config.auto_capture_config.enabled true->false OK -config.auto_capture_config.schema_name absent->x OK_RECREATE -config.auto_capture_config.schema_name absent->y OK_RECREATE -config.auto_capture_config.schema_name x->absent OK_RECREATE -config.auto_capture_config.schema_name x->y OK_RECREATE -config.auto_capture_config.schema_name y->absent OK_RECREATE -config.auto_capture_config.schema_name y->x OK_RECREATE -config.auto_capture_config.table_name_prefix absent->x OK_RECREATE -config.auto_capture_config.table_name_prefix absent->y OK_RECREATE -config.auto_capture_config.table_name_prefix x->absent OK_RECREATE -config.auto_capture_config.table_name_prefix x->y OK_RECREATE -config.auto_capture_config.table_name_prefix y->absent OK_RECREATE -config.auto_capture_config.table_name_prefix y->x OK_RECREATE -description absent->x OK_RECREATE -description absent->y OK_RECREATE -description x->absent OK_RECREATE -description x->y OK_RECREATE -description y->absent OK_RECREATE -description y->x OK_RECREATE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK_RECREATE -name y->x OK_RECREATE -route_optimized absent->false SUPPRESSED remote_already_set -route_optimized absent->true OK_RECREATE -route_optimized false->absent SUPPRESSED empty -route_optimized false->true OK_RECREATE -route_optimized true->absent OK_RECREATE -route_optimized true->false OK_RECREATE -telemetry_config.inference_table_config.name - SKIPPED needs a writable catalog and schema -telemetry_config.inference_table_config.sampling_fraction - SKIPPED needs a writable catalog and schema -telemetry_config.table_names.annotations_table - SKIPPED needs a writable catalog and schema -telemetry_config.table_names.logs_table - SKIPPED needs a writable catalog and schema -telemetry_config.table_names.metrics_table - SKIPPED needs a writable catalog and schema -telemetry_config.table_names.traces_table - SKIPPED needs a writable catalog and schema -telemetry_config.telemetry_profile_id - SKIPPED needs a writable catalog and schema - -=== summary -OK 103 -OK_RECREATE 57 -SUPPRESSED 29 -NOT_OBSERVABLE 16 -DRIFT 7 -SKIPPED 14 - -=== not covered: 95 fields under a slice or map -ai_gateway.guardrails.input.invalid_keywords[*] -ai_gateway.guardrails.input.valid_topics[*] -ai_gateway.guardrails.output.invalid_keywords[*] -ai_gateway.guardrails.output.valid_topics[*] -ai_gateway.rate_limits[*].calls -ai_gateway.rate_limits[*].key -ai_gateway.rate_limits[*].principal -ai_gateway.rate_limits[*].renewal_period -ai_gateway.rate_limits[*].tokens -config.served_entities[*].burst_scaling_enabled -config.served_entities[*].entity_name -config.served_entities[*].entity_version -config.served_entities[*].environment_vars.* -config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key -config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext -config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id -config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id_plaintext -config.served_entities[*].external_model.amazon_bedrock_config.aws_region -config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key -config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key_plaintext -config.served_entities[*].external_model.amazon_bedrock_config.bedrock_provider -config.served_entities[*].external_model.amazon_bedrock_config.instance_profile_arn -config.served_entities[*].external_model.anthropic_config.anthropic_api_key -config.served_entities[*].external_model.anthropic_config.anthropic_api_key_plaintext -config.served_entities[*].external_model.cohere_config.cohere_api_base -config.served_entities[*].external_model.cohere_config.cohere_api_key -config.served_entities[*].external_model.cohere_config.cohere_api_key_plaintext -config.served_entities[*].external_model.custom_provider_config.api_key_auth.key -config.served_entities[*].external_model.custom_provider_config.api_key_auth.value -config.served_entities[*].external_model.custom_provider_config.api_key_auth.value_plaintext -config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token -config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token_plaintext -config.served_entities[*].external_model.custom_provider_config.custom_provider_url -config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token -config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token_plaintext -config.served_entities[*].external_model.databricks_model_serving_config.databricks_workspace_url -config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key -config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key_plaintext -config.served_entities[*].external_model.google_cloud_vertex_ai_config.project_id -config.served_entities[*].external_model.google_cloud_vertex_ai_config.region -config.served_entities[*].external_model.name -config.served_entities[*].external_model.openai_config.microsoft_entra_client_id -config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret -config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret_plaintext -config.served_entities[*].external_model.openai_config.microsoft_entra_tenant_id -config.served_entities[*].external_model.openai_config.openai_api_base -config.served_entities[*].external_model.openai_config.openai_api_key -config.served_entities[*].external_model.openai_config.openai_api_key_plaintext -config.served_entities[*].external_model.openai_config.openai_api_type -config.served_entities[*].external_model.openai_config.openai_api_version -config.served_entities[*].external_model.openai_config.openai_deployment_name -config.served_entities[*].external_model.openai_config.openai_organization -config.served_entities[*].external_model.palm_config.palm_api_key -config.served_entities[*].external_model.palm_config.palm_api_key_plaintext -config.served_entities[*].external_model.provider -config.served_entities[*].external_model.task -config.served_entities[*].instance_profile_arn -config.served_entities[*].max_provisioned_concurrency -config.served_entities[*].max_provisioned_throughput -config.served_entities[*].min_provisioned_concurrency -config.served_entities[*].min_provisioned_throughput -config.served_entities[*].name -config.served_entities[*].provisioned_model_units -config.served_entities[*].scale_to_zero_enabled -config.served_entities[*].workload_size -config.served_entities[*].workload_type -config.served_models[*].burst_scaling_enabled -config.served_models[*].environment_vars.* -config.served_models[*].instance_profile_arn -config.served_models[*].max_provisioned_concurrency -config.served_models[*].max_provisioned_throughput -config.served_models[*].min_provisioned_concurrency -config.served_models[*].min_provisioned_throughput -config.served_models[*].model_name -config.served_models[*].model_version -config.served_models[*].name -config.served_models[*].provisioned_model_units -config.served_models[*].scale_to_zero_enabled -config.served_models[*].workload_size -config.served_models[*].workload_type -config.traffic_config.routes[*].served_entity_name -config.traffic_config.routes[*].served_model_name -config.traffic_config.routes[*].traffic_percentage -email_notifications.on_update_failure[*] -email_notifications.on_update_success[*] -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -rate_limits[*].calls -rate_limits[*].key -rate_limits[*].renewal_period -tags[*].key -tags[*].value -telemetry_config.enabled_telemetry_features[*] +budget_policy_id absent x SUPPRESSED no_update_api +budget_policy_id absent y SUPPRESSED no_update_api +budget_policy_id x y SUPPRESSED no_update_api +budget_policy_id y x SUPPRESSED no_update_api +route_optimized absent false SUPPRESSED remote_already_set diff --git a/bundle/direct/tests/output/models.full.txt b/bundle/direct/tests/output/models.full.txt new file mode 100644 index 00000000000..c5711ca5524 --- /dev/null +++ b/bundle/direct/tests/output/models.full.txt @@ -0,0 +1,28 @@ +=== model.yml.tmpl +description absent x OK +description absent y OK +description x absent OK +description x y OK +description y absent OK +description y x OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK_RECREATE +name y x OK_RECREATE + +=== summary +OK 6 +OK_RECREATE 2 +NOT_OBSERVABLE 6 + +=== not covered: 6 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +tags[*].key +tags[*].value diff --git a/bundle/direct/tests/output/models.txt b/bundle/direct/tests/output/models.txt index c7453cf2882..e69de29bb2d 100644 --- a/bundle/direct/tests/output/models.txt +++ b/bundle/direct/tests/output/models.txt @@ -1,28 +0,0 @@ -=== model.yml.tmpl -description absent->x OK -description absent->y OK -description x->absent OK -description x->y OK -description y->absent OK -description y->x OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK_RECREATE -name y->x OK_RECREATE - -=== summary -OK 6 -OK_RECREATE 2 -NOT_OBSERVABLE 6 - -=== not covered: 6 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -tags[*].key -tags[*].value diff --git a/bundle/direct/tests/output/pipelines.full.txt b/bundle/direct/tests/output/pipelines.full.txt new file mode 100644 index 00000000000..1ed2c37e9fb --- /dev/null +++ b/bundle/direct/tests/output/pipelines.full.txt @@ -0,0 +1,1406 @@ +=== pipeline.yml.tmpl +allow_duplicate_names absent false SUPPRESSED empty +allow_duplicate_names absent true OK +allow_duplicate_names false absent SUPPRESSED empty +allow_duplicate_names false true OK +allow_duplicate_names true absent OK +allow_duplicate_names true false OK +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent OK +budget_policy_id x y OK +budget_policy_id y absent OK +budget_policy_id y x OK +cascade_on_destroy absent false OK +cascade_on_destroy absent true OK +cascade_on_destroy false absent OK +cascade_on_destroy false true OK +cascade_on_destroy true absent OK +cascade_on_destroy true false OK +catalog absent x OK +catalog absent y OK +catalog x absent OK +catalog x y OK +catalog y absent OK +catalog y x OK +channel absent x OK +channel absent y OK +channel x absent OK +channel x y OK +channel y absent OK +channel y x OK +continuous absent false SUPPRESSED empty +continuous absent true OK +continuous false absent SUPPRESSED empty +continuous false true OK +continuous true absent OK +continuous true false OK +deployment.deployment_id absent x OK +deployment.deployment_id absent y OK +deployment.deployment_id x absent OK +deployment.deployment_id x y OK +deployment.deployment_id y absent OK +deployment.deployment_id y x OK +deployment.kind x y OK +deployment.kind y x OK +deployment.metadata_file_path absent x OK +deployment.metadata_file_path absent y OK +deployment.metadata_file_path x absent OK +deployment.metadata_file_path x y OK +deployment.metadata_file_path y absent OK +deployment.metadata_file_path y x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x absent NOT_OBSERVABLE +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y absent NOT_OBSERVABLE +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +development absent false SUPPRESSED empty +development absent true OK +development false absent SUPPRESSED empty +development false true OK +development true absent OK +development true false OK +dry_run absent false SUPPRESSED empty +dry_run absent true POST_DEPLOY_DRIFT dry_run +dry_run false absent SUPPRESSED remote_already_set +dry_run false true POST_DEPLOY_DRIFT dry_run +dry_run true absent SUPPRESSED remote_already_set +dry_run true false OK +edition absent x OK +edition absent y OK +edition x absent OK +edition x y OK +edition y absent OK +edition y x OK +environment.environment_version absent x OK +environment.environment_version absent y OK +environment.environment_version x absent OK +environment.environment_version x y OK +environment.environment_version y absent OK +environment.environment_version y x OK +event_log.catalog absent x OK +event_log.catalog absent y OK +event_log.catalog x absent OK +event_log.catalog x y OK +event_log.catalog y absent OK +event_log.catalog y x OK +event_log.name absent x OK +event_log.name absent y OK +event_log.name x absent OK +event_log.name x y OK +event_log.name y absent OK +event_log.name y x OK +event_log.schema absent x OK +event_log.schema absent y OK +event_log.schema x absent OK +event_log.schema x y OK +event_log.schema y absent OK +event_log.schema y x OK +gateway_definition.connection_id absent x OK +gateway_definition.connection_id absent y OK_RECREATE +gateway_definition.connection_id x absent OK_RECREATE +gateway_definition.connection_id x y OK_RECREATE +gateway_definition.connection_id y absent OK_RECREATE +gateway_definition.connection_id y x OK_RECREATE +gateway_definition.connection_name x y OK_RECREATE +gateway_definition.connection_name y x OK_RECREATE +gateway_definition.connection_parameters.source_catalog absent x OK +gateway_definition.connection_parameters.source_catalog absent y OK +gateway_definition.connection_parameters.source_catalog x absent OK +gateway_definition.connection_parameters.source_catalog x y OK +gateway_definition.connection_parameters.source_catalog y absent OK +gateway_definition.connection_parameters.source_catalog y x OK +gateway_definition.gateway_storage_catalog x y OK_RECREATE +gateway_definition.gateway_storage_catalog y x OK_RECREATE +gateway_definition.gateway_storage_name absent x OK +gateway_definition.gateway_storage_name absent y OK +gateway_definition.gateway_storage_name x absent OK +gateway_definition.gateway_storage_name x y OK +gateway_definition.gateway_storage_name y absent OK +gateway_definition.gateway_storage_name y x OK +gateway_definition.gateway_storage_schema x y OK_RECREATE +gateway_definition.gateway_storage_schema y x OK_RECREATE +id absent x NOT_OBSERVABLE +id absent y NOT_OBSERVABLE +id x absent NOT_OBSERVABLE +id x y NOT_OBSERVABLE +id y absent NOT_OBSERVABLE +id y x NOT_OBSERVABLE +ingestion_definition.connection_name absent x OK +ingestion_definition.connection_name absent y OK_RECREATE +ingestion_definition.connection_name x absent OK_RECREATE +ingestion_definition.connection_name x y OK_RECREATE +ingestion_definition.connection_name y absent OK_RECREATE +ingestion_definition.connection_name y x OK_RECREATE +ingestion_definition.connector_type absent x OK +ingestion_definition.connector_type absent y OK +ingestion_definition.connector_type x absent OK +ingestion_definition.connector_type x y OK +ingestion_definition.connector_type y absent OK +ingestion_definition.connector_type y x OK +ingestion_definition.data_staging_options.catalog_name x y OK +ingestion_definition.data_staging_options.catalog_name y x OK +ingestion_definition.data_staging_options.schema_name x y OK +ingestion_definition.data_staging_options.schema_name y x OK +ingestion_definition.data_staging_options.volume_name absent x OK +ingestion_definition.data_staging_options.volume_name absent y OK +ingestion_definition.data_staging_options.volume_name x absent OK +ingestion_definition.data_staging_options.volume_name x y OK +ingestion_definition.data_staging_options.volume_name y absent OK +ingestion_definition.data_staging_options.volume_name y x OK +ingestion_definition.full_refresh_window.start_hour 1 2 OK +ingestion_definition.full_refresh_window.start_hour 2 1 OK +ingestion_definition.full_refresh_window.time_zone_id absent x OK +ingestion_definition.full_refresh_window.time_zone_id absent y OK +ingestion_definition.full_refresh_window.time_zone_id x absent OK +ingestion_definition.full_refresh_window.time_zone_id x y OK +ingestion_definition.full_refresh_window.time_zone_id y absent OK +ingestion_definition.full_refresh_window.time_zone_id y x OK +ingestion_definition.ingest_from_uc_foreign_catalog absent false SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog absent true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog false absent SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog false true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true absent OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true false OK_RECREATE +ingestion_definition.ingestion_gateway_id absent x OK_RECREATE +ingestion_definition.ingestion_gateway_id absent y OK_RECREATE +ingestion_definition.ingestion_gateway_id x absent OK_RECREATE +ingestion_definition.ingestion_gateway_id x y OK_RECREATE +ingestion_definition.ingestion_gateway_id y absent OK_RECREATE +ingestion_definition.ingestion_gateway_id y x OK_RECREATE +ingestion_definition.netsuite_jar_path absent x OK +ingestion_definition.netsuite_jar_path absent y OK +ingestion_definition.netsuite_jar_path x absent OK +ingestion_definition.netsuite_jar_path x y OK +ingestion_definition.netsuite_jar_path y absent OK +ingestion_definition.netsuite_jar_path y x OK +ingestion_definition.source_type absent x OK +ingestion_definition.source_type absent y OK +ingestion_definition.source_type x absent OK +ingestion_definition.source_type x y OK +ingestion_definition.source_type y absent OK +ingestion_definition.source_type y x OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false true OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true false OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 2 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 2 OK +ingestion_definition.table_configuration.enable_auto_clustering absent false SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering absent true OK +ingestion_definition.table_configuration.enable_auto_clustering false absent SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering false true OK +ingestion_definition.table_configuration.enable_auto_clustering true absent OK +ingestion_definition.table_configuration.enable_auto_clustering true false OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent x OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y x OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 2 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 2 OK +ingestion_definition.table_configuration.row_filter absent x OK +ingestion_definition.table_configuration.row_filter absent y OK +ingestion_definition.table_configuration.row_filter x absent OK +ingestion_definition.table_configuration.row_filter x y OK +ingestion_definition.table_configuration.row_filter y absent OK +ingestion_definition.table_configuration.row_filter y x OK +ingestion_definition.table_configuration.salesforce_include_formula_fields absent false SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields absent true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields false absent SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields false true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true absent OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true false OK +ingestion_definition.table_configuration.scd_type absent x OK +ingestion_definition.table_configuration.scd_type absent y OK +ingestion_definition.table_configuration.scd_type x absent OK +ingestion_definition.table_configuration.scd_type x y OK +ingestion_definition.table_configuration.scd_type y absent OK +ingestion_definition.table_configuration.scd_type y x OK +ingestion_definition.table_configuration.source_metadata_column absent x OK +ingestion_definition.table_configuration.source_metadata_column absent y OK +ingestion_definition.table_configuration.source_metadata_column x absent OK +ingestion_definition.table_configuration.source_metadata_column x y OK +ingestion_definition.table_configuration.source_metadata_column y absent OK +ingestion_definition.table_configuration.source_metadata_column y x OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent false OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental false absent SUPPRESSED empty +ingestion_definition.table_configuration.workday_report_parameters.incremental false true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true absent OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true false OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +photon absent false SUPPRESSED empty +photon absent true OK +photon false absent SUPPRESSED empty +photon false true OK +photon true absent OK +photon true false OK +restart_window.start_hour 1 2 OK +restart_window.start_hour 2 1 OK +restart_window.time_zone_id absent x OK +restart_window.time_zone_id absent y OK +restart_window.time_zone_id x absent OK +restart_window.time_zone_id x y OK +restart_window.time_zone_id y absent OK +restart_window.time_zone_id y x OK +root_path absent x OK +root_path absent y OK +root_path x absent OK +root_path x y OK +root_path y absent OK +root_path y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +schema absent x OK +schema absent y OK +schema x absent OK +schema x y OK +schema y absent OK +schema y x OK +serverless absent false SUPPRESSED empty +serverless absent true OK +serverless false absent SUPPRESSED empty +serverless false true OK +serverless true absent OK +serverless true false OK +serverless_compute_id absent x OK +serverless_compute_id absent y OK +serverless_compute_id x absent OK +serverless_compute_id x y OK +serverless_compute_id y absent OK +serverless_compute_id y x OK +storage absent x OK_RECREATE +storage absent y OK_RECREATE +storage x absent OK_RECREATE +storage x y OK_RECREATE +storage y absent OK_RECREATE +storage y x OK_RECREATE +target absent x OK +target absent y OK +target x absent OK +target x y OK +target y absent OK +target y x OK +trigger.cron.quartz_cron_schedule absent x OK +trigger.cron.quartz_cron_schedule absent y OK +trigger.cron.quartz_cron_schedule x absent OK +trigger.cron.quartz_cron_schedule x y OK +trigger.cron.quartz_cron_schedule y absent OK +trigger.cron.quartz_cron_schedule y x OK +trigger.cron.timezone_id absent x OK +trigger.cron.timezone_id absent y OK +trigger.cron.timezone_id x absent OK +trigger.cron.timezone_id x y OK +trigger.cron.timezone_id y absent OK +trigger.cron.timezone_id y x OK +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent OK +usage_policy_id x y OK +usage_policy_id y absent OK +usage_policy_id y x OK + +=== pipeline_allow_duplicate_names.yml.tmpl +allow_duplicate_names absent false SUPPRESSED empty +allow_duplicate_names absent true OK +allow_duplicate_names false absent SUPPRESSED empty +allow_duplicate_names false true OK +allow_duplicate_names true absent OK +allow_duplicate_names true false OK +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent OK +budget_policy_id x y OK +budget_policy_id y absent OK +budget_policy_id y x OK +cascade_on_destroy absent false OK +cascade_on_destroy absent true OK +cascade_on_destroy false absent OK +cascade_on_destroy false true OK +cascade_on_destroy true absent OK +cascade_on_destroy true false OK +catalog absent x OK +catalog absent y OK +catalog x absent OK +catalog x y OK +catalog y absent OK +catalog y x OK +channel absent x OK +channel absent y OK +channel x absent OK +channel x y OK +channel y absent OK +channel y x OK +continuous absent false SUPPRESSED empty +continuous absent true OK +continuous false absent SUPPRESSED empty +continuous false true OK +continuous true absent OK +continuous true false OK +deployment.deployment_id absent x OK +deployment.deployment_id absent y OK +deployment.deployment_id x absent OK +deployment.deployment_id x y OK +deployment.deployment_id y absent OK +deployment.deployment_id y x OK +deployment.kind x y OK +deployment.kind y x OK +deployment.metadata_file_path absent x OK +deployment.metadata_file_path absent y OK +deployment.metadata_file_path x absent OK +deployment.metadata_file_path x y OK +deployment.metadata_file_path y absent OK +deployment.metadata_file_path y x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x absent NOT_OBSERVABLE +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y absent NOT_OBSERVABLE +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +development absent false SUPPRESSED empty +development absent true OK +development false absent SUPPRESSED empty +development false true OK +development true absent OK +development true false OK +dry_run absent false SUPPRESSED empty +dry_run absent true POST_DEPLOY_DRIFT dry_run +dry_run false absent SUPPRESSED remote_already_set +dry_run false true POST_DEPLOY_DRIFT dry_run +dry_run true absent SUPPRESSED remote_already_set +dry_run true false OK +edition absent x OK +edition absent y OK +edition x absent OK +edition x y OK +edition y absent OK +edition y x OK +environment.environment_version absent x OK +environment.environment_version absent y OK +environment.environment_version x absent OK +environment.environment_version x y OK +environment.environment_version y absent OK +environment.environment_version y x OK +event_log.catalog absent x OK +event_log.catalog absent y OK +event_log.catalog x absent OK +event_log.catalog x y OK +event_log.catalog y absent OK +event_log.catalog y x OK +event_log.name absent x OK +event_log.name absent y OK +event_log.name x absent OK +event_log.name x y OK +event_log.name y absent OK +event_log.name y x OK +event_log.schema absent x OK +event_log.schema absent y OK +event_log.schema x absent OK +event_log.schema x y OK +event_log.schema y absent OK +event_log.schema y x OK +gateway_definition.connection_id absent x OK +gateway_definition.connection_id absent y OK_RECREATE +gateway_definition.connection_id x absent OK_RECREATE +gateway_definition.connection_id x y OK_RECREATE +gateway_definition.connection_id y absent OK_RECREATE +gateway_definition.connection_id y x OK_RECREATE +gateway_definition.connection_name x y OK_RECREATE +gateway_definition.connection_name y x OK_RECREATE +gateway_definition.connection_parameters.source_catalog absent x OK +gateway_definition.connection_parameters.source_catalog absent y OK +gateway_definition.connection_parameters.source_catalog x absent OK +gateway_definition.connection_parameters.source_catalog x y OK +gateway_definition.connection_parameters.source_catalog y absent OK +gateway_definition.connection_parameters.source_catalog y x OK +gateway_definition.gateway_storage_catalog x y OK_RECREATE +gateway_definition.gateway_storage_catalog y x OK_RECREATE +gateway_definition.gateway_storage_name absent x OK +gateway_definition.gateway_storage_name absent y OK +gateway_definition.gateway_storage_name x absent OK +gateway_definition.gateway_storage_name x y OK +gateway_definition.gateway_storage_name y absent OK +gateway_definition.gateway_storage_name y x OK +gateway_definition.gateway_storage_schema x y OK_RECREATE +gateway_definition.gateway_storage_schema y x OK_RECREATE +id absent x NOT_OBSERVABLE +id absent y NOT_OBSERVABLE +id x absent NOT_OBSERVABLE +id x y NOT_OBSERVABLE +id y absent NOT_OBSERVABLE +id y x NOT_OBSERVABLE +ingestion_definition.connection_name absent x OK +ingestion_definition.connection_name absent y OK_RECREATE +ingestion_definition.connection_name x absent OK_RECREATE +ingestion_definition.connection_name x y OK_RECREATE +ingestion_definition.connection_name y absent OK_RECREATE +ingestion_definition.connection_name y x OK_RECREATE +ingestion_definition.connector_type absent x OK +ingestion_definition.connector_type absent y OK +ingestion_definition.connector_type x absent OK +ingestion_definition.connector_type x y OK +ingestion_definition.connector_type y absent OK +ingestion_definition.connector_type y x OK +ingestion_definition.data_staging_options.catalog_name x y OK +ingestion_definition.data_staging_options.catalog_name y x OK +ingestion_definition.data_staging_options.schema_name x y OK +ingestion_definition.data_staging_options.schema_name y x OK +ingestion_definition.data_staging_options.volume_name absent x OK +ingestion_definition.data_staging_options.volume_name absent y OK +ingestion_definition.data_staging_options.volume_name x absent OK +ingestion_definition.data_staging_options.volume_name x y OK +ingestion_definition.data_staging_options.volume_name y absent OK +ingestion_definition.data_staging_options.volume_name y x OK +ingestion_definition.full_refresh_window.start_hour 1 2 OK +ingestion_definition.full_refresh_window.start_hour 2 1 OK +ingestion_definition.full_refresh_window.time_zone_id absent x OK +ingestion_definition.full_refresh_window.time_zone_id absent y OK +ingestion_definition.full_refresh_window.time_zone_id x absent OK +ingestion_definition.full_refresh_window.time_zone_id x y OK +ingestion_definition.full_refresh_window.time_zone_id y absent OK +ingestion_definition.full_refresh_window.time_zone_id y x OK +ingestion_definition.ingest_from_uc_foreign_catalog absent false SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog absent true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog false absent SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog false true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true absent OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true false OK_RECREATE +ingestion_definition.ingestion_gateway_id absent x OK_RECREATE +ingestion_definition.ingestion_gateway_id absent y OK_RECREATE +ingestion_definition.ingestion_gateway_id x absent OK_RECREATE +ingestion_definition.ingestion_gateway_id x y OK_RECREATE +ingestion_definition.ingestion_gateway_id y absent OK_RECREATE +ingestion_definition.ingestion_gateway_id y x OK_RECREATE +ingestion_definition.netsuite_jar_path absent x OK +ingestion_definition.netsuite_jar_path absent y OK +ingestion_definition.netsuite_jar_path x absent OK +ingestion_definition.netsuite_jar_path x y OK +ingestion_definition.netsuite_jar_path y absent OK +ingestion_definition.netsuite_jar_path y x OK +ingestion_definition.source_type absent x OK +ingestion_definition.source_type absent y OK +ingestion_definition.source_type x absent OK +ingestion_definition.source_type x y OK +ingestion_definition.source_type y absent OK +ingestion_definition.source_type y x OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false true OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true false OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 2 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 2 OK +ingestion_definition.table_configuration.enable_auto_clustering absent false SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering absent true OK +ingestion_definition.table_configuration.enable_auto_clustering false absent SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering false true OK +ingestion_definition.table_configuration.enable_auto_clustering true absent OK +ingestion_definition.table_configuration.enable_auto_clustering true false OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent x OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y x OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 2 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 2 OK +ingestion_definition.table_configuration.row_filter absent x OK +ingestion_definition.table_configuration.row_filter absent y OK +ingestion_definition.table_configuration.row_filter x absent OK +ingestion_definition.table_configuration.row_filter x y OK +ingestion_definition.table_configuration.row_filter y absent OK +ingestion_definition.table_configuration.row_filter y x OK +ingestion_definition.table_configuration.salesforce_include_formula_fields absent false SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields absent true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields false absent SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields false true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true absent OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true false OK +ingestion_definition.table_configuration.scd_type absent x OK +ingestion_definition.table_configuration.scd_type absent y OK +ingestion_definition.table_configuration.scd_type x absent OK +ingestion_definition.table_configuration.scd_type x y OK +ingestion_definition.table_configuration.scd_type y absent OK +ingestion_definition.table_configuration.scd_type y x OK +ingestion_definition.table_configuration.source_metadata_column absent x OK +ingestion_definition.table_configuration.source_metadata_column absent y OK +ingestion_definition.table_configuration.source_metadata_column x absent OK +ingestion_definition.table_configuration.source_metadata_column x y OK +ingestion_definition.table_configuration.source_metadata_column y absent OK +ingestion_definition.table_configuration.source_metadata_column y x OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent false OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental false absent SUPPRESSED empty +ingestion_definition.table_configuration.workday_report_parameters.incremental false true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true absent OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true false OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +photon absent false SUPPRESSED empty +photon absent true OK +photon false absent SUPPRESSED empty +photon false true OK +photon true absent OK +photon true false OK +restart_window.start_hour 1 2 OK +restart_window.start_hour 2 1 OK +restart_window.time_zone_id absent x OK +restart_window.time_zone_id absent y OK +restart_window.time_zone_id x absent OK +restart_window.time_zone_id x y OK +restart_window.time_zone_id y absent OK +restart_window.time_zone_id y x OK +root_path absent x OK +root_path absent y OK +root_path x absent OK +root_path x y OK +root_path y absent OK +root_path y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +schema absent x OK +schema absent y OK +schema x absent OK +schema x y OK +schema y absent OK +schema y x OK +serverless absent false SUPPRESSED empty +serverless absent true OK +serverless false absent SUPPRESSED empty +serverless false true OK +serverless true absent OK +serverless true false OK +serverless_compute_id absent x OK +serverless_compute_id absent y OK +serverless_compute_id x absent OK +serverless_compute_id x y OK +serverless_compute_id y absent OK +serverless_compute_id y x OK +storage absent x OK_RECREATE +storage absent y OK_RECREATE +storage x absent OK_RECREATE +storage x y OK_RECREATE +storage y absent OK_RECREATE +storage y x OK_RECREATE +target absent x OK +target absent y OK +target x absent OK +target x y OK +target y absent OK +target y x OK +trigger.cron.quartz_cron_schedule absent x OK +trigger.cron.quartz_cron_schedule absent y OK +trigger.cron.quartz_cron_schedule x absent OK +trigger.cron.quartz_cron_schedule x y OK +trigger.cron.quartz_cron_schedule y absent OK +trigger.cron.quartz_cron_schedule y x OK +trigger.cron.timezone_id absent x OK +trigger.cron.timezone_id absent y OK +trigger.cron.timezone_id x absent OK +trigger.cron.timezone_id x y OK +trigger.cron.timezone_id y absent OK +trigger.cron.timezone_id y x OK +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent OK +usage_policy_id x y OK +usage_policy_id y absent OK +usage_policy_id y x OK + +=== pipeline_apply_policy_default_values.yml.tmpl +allow_duplicate_names absent false SUPPRESSED empty +allow_duplicate_names absent true OK +allow_duplicate_names false absent SUPPRESSED empty +allow_duplicate_names false true OK +allow_duplicate_names true absent OK +allow_duplicate_names true false OK +budget_policy_id absent x OK +budget_policy_id absent y OK +budget_policy_id x absent OK +budget_policy_id x y OK +budget_policy_id y absent OK +budget_policy_id y x OK +cascade_on_destroy absent false OK +cascade_on_destroy absent true OK +cascade_on_destroy false absent OK +cascade_on_destroy false true OK +cascade_on_destroy true absent OK +cascade_on_destroy true false OK +catalog absent x OK +catalog absent y OK +catalog x absent OK +catalog x y OK +catalog y absent OK +catalog y x OK +channel absent x OK +channel absent y OK +channel x absent OK +channel x y OK +channel y absent OK +channel y x OK +continuous absent false SUPPRESSED empty +continuous absent true OK +continuous false absent SUPPRESSED empty +continuous false true OK +continuous true absent OK +continuous true false OK +deployment.deployment_id absent x OK +deployment.deployment_id absent y OK +deployment.deployment_id x absent OK +deployment.deployment_id x y OK +deployment.deployment_id y absent OK +deployment.deployment_id y x OK +deployment.kind x y OK +deployment.kind y x OK +deployment.metadata_file_path absent x OK +deployment.metadata_file_path absent y OK +deployment.metadata_file_path x absent OK +deployment.metadata_file_path x y OK +deployment.metadata_file_path y absent OK +deployment.metadata_file_path y x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x absent NOT_OBSERVABLE +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y absent NOT_OBSERVABLE +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +development absent false SUPPRESSED empty +development absent true OK +development false absent SUPPRESSED empty +development false true OK +development true absent OK +development true false OK +dry_run absent false SUPPRESSED empty +dry_run absent true POST_DEPLOY_DRIFT dry_run +dry_run false absent SUPPRESSED remote_already_set +dry_run false true POST_DEPLOY_DRIFT dry_run +dry_run true absent SUPPRESSED remote_already_set +dry_run true false OK +edition absent x OK +edition absent y OK +edition x absent OK +edition x y OK +edition y absent OK +edition y x OK +environment.environment_version absent x OK +environment.environment_version absent y OK +environment.environment_version x absent OK +environment.environment_version x y OK +environment.environment_version y absent OK +environment.environment_version y x OK +event_log.catalog absent x OK +event_log.catalog absent y OK +event_log.catalog x absent OK +event_log.catalog x y OK +event_log.catalog y absent OK +event_log.catalog y x OK +event_log.name absent x OK +event_log.name absent y OK +event_log.name x absent OK +event_log.name x y OK +event_log.name y absent OK +event_log.name y x OK +event_log.schema absent x OK +event_log.schema absent y OK +event_log.schema x absent OK +event_log.schema x y OK +event_log.schema y absent OK +event_log.schema y x OK +gateway_definition.connection_id absent x OK +gateway_definition.connection_id absent y OK_RECREATE +gateway_definition.connection_id x absent OK_RECREATE +gateway_definition.connection_id x y OK_RECREATE +gateway_definition.connection_id y absent OK_RECREATE +gateway_definition.connection_id y x OK_RECREATE +gateway_definition.connection_name x y OK_RECREATE +gateway_definition.connection_name y x OK_RECREATE +gateway_definition.connection_parameters.source_catalog absent x OK +gateway_definition.connection_parameters.source_catalog absent y OK +gateway_definition.connection_parameters.source_catalog x absent OK +gateway_definition.connection_parameters.source_catalog x y OK +gateway_definition.connection_parameters.source_catalog y absent OK +gateway_definition.connection_parameters.source_catalog y x OK +gateway_definition.gateway_storage_catalog x y OK_RECREATE +gateway_definition.gateway_storage_catalog y x OK_RECREATE +gateway_definition.gateway_storage_name absent x OK +gateway_definition.gateway_storage_name absent y OK +gateway_definition.gateway_storage_name x absent OK +gateway_definition.gateway_storage_name x y OK +gateway_definition.gateway_storage_name y absent OK +gateway_definition.gateway_storage_name y x OK +gateway_definition.gateway_storage_schema x y OK_RECREATE +gateway_definition.gateway_storage_schema y x OK_RECREATE +id absent x NOT_OBSERVABLE +id absent y NOT_OBSERVABLE +id x absent NOT_OBSERVABLE +id x y NOT_OBSERVABLE +id y absent NOT_OBSERVABLE +id y x NOT_OBSERVABLE +ingestion_definition.connection_name absent x OK +ingestion_definition.connection_name absent y OK_RECREATE +ingestion_definition.connection_name x absent OK_RECREATE +ingestion_definition.connection_name x y OK_RECREATE +ingestion_definition.connection_name y absent OK_RECREATE +ingestion_definition.connection_name y x OK_RECREATE +ingestion_definition.connector_type absent x OK +ingestion_definition.connector_type absent y OK +ingestion_definition.connector_type x absent OK +ingestion_definition.connector_type x y OK +ingestion_definition.connector_type y absent OK +ingestion_definition.connector_type y x OK +ingestion_definition.data_staging_options.catalog_name x y OK +ingestion_definition.data_staging_options.catalog_name y x OK +ingestion_definition.data_staging_options.schema_name x y OK +ingestion_definition.data_staging_options.schema_name y x OK +ingestion_definition.data_staging_options.volume_name absent x OK +ingestion_definition.data_staging_options.volume_name absent y OK +ingestion_definition.data_staging_options.volume_name x absent OK +ingestion_definition.data_staging_options.volume_name x y OK +ingestion_definition.data_staging_options.volume_name y absent OK +ingestion_definition.data_staging_options.volume_name y x OK +ingestion_definition.full_refresh_window.start_hour 1 2 OK +ingestion_definition.full_refresh_window.start_hour 2 1 OK +ingestion_definition.full_refresh_window.time_zone_id absent x OK +ingestion_definition.full_refresh_window.time_zone_id absent y OK +ingestion_definition.full_refresh_window.time_zone_id x absent OK +ingestion_definition.full_refresh_window.time_zone_id x y OK +ingestion_definition.full_refresh_window.time_zone_id y absent OK +ingestion_definition.full_refresh_window.time_zone_id y x OK +ingestion_definition.ingest_from_uc_foreign_catalog absent false SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog absent true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog false absent SUPPRESSED empty +ingestion_definition.ingest_from_uc_foreign_catalog false true OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true absent OK_RECREATE +ingestion_definition.ingest_from_uc_foreign_catalog true false OK_RECREATE +ingestion_definition.ingestion_gateway_id absent x OK_RECREATE +ingestion_definition.ingestion_gateway_id absent y OK_RECREATE +ingestion_definition.ingestion_gateway_id x absent OK_RECREATE +ingestion_definition.ingestion_gateway_id x y OK_RECREATE +ingestion_definition.ingestion_gateway_id y absent OK_RECREATE +ingestion_definition.ingestion_gateway_id y x OK_RECREATE +ingestion_definition.netsuite_jar_path absent x OK +ingestion_definition.netsuite_jar_path absent y OK +ingestion_definition.netsuite_jar_path x absent OK +ingestion_definition.netsuite_jar_path x y OK +ingestion_definition.netsuite_jar_path y absent OK +ingestion_definition.netsuite_jar_path y x OK +ingestion_definition.source_type absent x OK +ingestion_definition.source_type absent y OK +ingestion_definition.source_type x absent OK +ingestion_definition.source_type x y OK +ingestion_definition.source_type y absent OK +ingestion_definition.source_type y x OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false true OK +ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true false OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 2 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 absent OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 1 OK +ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 2 OK +ingestion_definition.table_configuration.enable_auto_clustering absent false SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering absent true OK +ingestion_definition.table_configuration.enable_auto_clustering false absent SUPPRESSED empty +ingestion_definition.table_configuration.enable_auto_clustering false true OK +ingestion_definition.table_configuration.enable_auto_clustering true absent OK +ingestion_definition.table_configuration.enable_auto_clustering true false OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent x OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x y OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y absent OK +ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y x OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 2 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 absent OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 1 OK +ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 2 OK +ingestion_definition.table_configuration.row_filter absent x OK +ingestion_definition.table_configuration.row_filter absent y OK +ingestion_definition.table_configuration.row_filter x absent OK +ingestion_definition.table_configuration.row_filter x y OK +ingestion_definition.table_configuration.row_filter y absent OK +ingestion_definition.table_configuration.row_filter y x OK +ingestion_definition.table_configuration.salesforce_include_formula_fields absent false SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields absent true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields false absent SUPPRESSED empty +ingestion_definition.table_configuration.salesforce_include_formula_fields false true OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true absent OK +ingestion_definition.table_configuration.salesforce_include_formula_fields true false OK +ingestion_definition.table_configuration.scd_type absent x OK +ingestion_definition.table_configuration.scd_type absent y OK +ingestion_definition.table_configuration.scd_type x absent OK +ingestion_definition.table_configuration.scd_type x y OK +ingestion_definition.table_configuration.scd_type y absent OK +ingestion_definition.table_configuration.scd_type y x OK +ingestion_definition.table_configuration.source_metadata_column absent x OK +ingestion_definition.table_configuration.source_metadata_column absent y OK +ingestion_definition.table_configuration.source_metadata_column x absent OK +ingestion_definition.table_configuration.source_metadata_column x y OK +ingestion_definition.table_configuration.source_metadata_column y absent OK +ingestion_definition.table_configuration.source_metadata_column y x OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent false OK +ingestion_definition.table_configuration.workday_report_parameters.incremental absent true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental false absent SUPPRESSED empty +ingestion_definition.table_configuration.workday_report_parameters.incremental false true OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true absent OK +ingestion_definition.table_configuration.workday_report_parameters.incremental true false OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name absent x OK +name absent y OK +name x absent OK +name x y OK +name y absent OK +name y x OK +photon absent false SUPPRESSED empty +photon absent true OK +photon false absent SUPPRESSED empty +photon false true OK +photon true absent OK +photon true false OK +restart_window.start_hour 1 2 OK +restart_window.start_hour 2 1 OK +restart_window.time_zone_id absent x OK +restart_window.time_zone_id absent y OK +restart_window.time_zone_id x absent OK +restart_window.time_zone_id x y OK +restart_window.time_zone_id y absent OK +restart_window.time_zone_id y x OK +root_path absent x OK +root_path absent y OK +root_path x absent OK +root_path x y OK +root_path y absent OK +root_path y x OK +run_as.service_principal_name absent x OK +run_as.service_principal_name absent y OK +run_as.service_principal_name x absent OK +run_as.service_principal_name x y OK +run_as.service_principal_name y absent OK +run_as.service_principal_name y x OK +run_as.user_name absent x OK +run_as.user_name absent y OK +run_as.user_name x absent OK +run_as.user_name x y OK +run_as.user_name y absent OK +run_as.user_name y x OK +schema absent x OK +schema absent y OK +schema x absent OK +schema x y OK +schema y absent OK +schema y x OK +serverless absent false SUPPRESSED empty +serverless absent true OK +serverless false absent SUPPRESSED empty +serverless false true OK +serverless true absent OK +serverless true false OK +serverless_compute_id absent x OK +serverless_compute_id absent y OK +serverless_compute_id x absent OK +serverless_compute_id x y OK +serverless_compute_id y absent OK +serverless_compute_id y x OK +storage absent x OK_RECREATE +storage absent y OK_RECREATE +storage x absent OK_RECREATE +storage x y OK_RECREATE +storage y absent OK_RECREATE +storage y x OK_RECREATE +target absent x OK +target absent y OK +target x absent OK +target x y OK +target y absent OK +target y x OK +trigger.cron.quartz_cron_schedule absent x OK +trigger.cron.quartz_cron_schedule absent y OK +trigger.cron.quartz_cron_schedule x absent OK +trigger.cron.quartz_cron_schedule x y OK +trigger.cron.quartz_cron_schedule y absent OK +trigger.cron.quartz_cron_schedule y x OK +trigger.cron.timezone_id absent x OK +trigger.cron.timezone_id absent y OK +trigger.cron.timezone_id x absent OK +trigger.cron.timezone_id x y OK +trigger.cron.timezone_id y absent OK +trigger.cron.timezone_id y x OK +usage_policy_id absent x OK +usage_policy_id absent y OK +usage_policy_id x absent OK +usage_policy_id x y OK +usage_policy_id y absent OK +usage_policy_id y x OK + +=== summary +OK 774 +OK_RECREATE 96 +SUPPRESSED 72 +NOT_OBSERVABLE 42 +POST_DEPLOY_DRIFT 6 + +=== not covered: 402 fields under a slice or map +clusters[*].apply_policy_default_values +clusters[*].autoscale.max_workers +clusters[*].autoscale.min_workers +clusters[*].autoscale.mode +clusters[*].aws_attributes.availability +clusters[*].aws_attributes.ebs_volume_count +clusters[*].aws_attributes.ebs_volume_iops +clusters[*].aws_attributes.ebs_volume_size +clusters[*].aws_attributes.ebs_volume_throughput +clusters[*].aws_attributes.ebs_volume_type +clusters[*].aws_attributes.first_on_demand +clusters[*].aws_attributes.instance_profile_arn +clusters[*].aws_attributes.spot_bid_price_percent +clusters[*].aws_attributes.zone_id +clusters[*].azure_attributes.availability +clusters[*].azure_attributes.capacity_reservation_group +clusters[*].azure_attributes.first_on_demand +clusters[*].azure_attributes.log_analytics_info.log_analytics_primary_key +clusters[*].azure_attributes.log_analytics_info.log_analytics_workspace_id +clusters[*].azure_attributes.spot_bid_max_price +clusters[*].cluster_log_conf.dbfs.destination +clusters[*].cluster_log_conf.s3.canned_acl +clusters[*].cluster_log_conf.s3.destination +clusters[*].cluster_log_conf.s3.enable_encryption +clusters[*].cluster_log_conf.s3.encryption_type +clusters[*].cluster_log_conf.s3.endpoint +clusters[*].cluster_log_conf.s3.kms_key +clusters[*].cluster_log_conf.s3.region +clusters[*].cluster_log_conf.volumes.destination +clusters[*].custom_tags.* +clusters[*].driver_instance_pool_id +clusters[*].driver_node_type_id +clusters[*].enable_local_disk_encryption +clusters[*].gcp_attributes.availability +clusters[*].gcp_attributes.boot_disk_size +clusters[*].gcp_attributes.confidential_compute_type +clusters[*].gcp_attributes.first_on_demand +clusters[*].gcp_attributes.google_service_account +clusters[*].gcp_attributes.local_ssd_count +clusters[*].gcp_attributes.use_preemptible_executors +clusters[*].gcp_attributes.zone_id +clusters[*].init_scripts[*].abfss.destination +clusters[*].init_scripts[*].dbfs.destination +clusters[*].init_scripts[*].file.destination +clusters[*].init_scripts[*].gcs.destination +clusters[*].init_scripts[*].s3.canned_acl +clusters[*].init_scripts[*].s3.destination +clusters[*].init_scripts[*].s3.enable_encryption +clusters[*].init_scripts[*].s3.encryption_type +clusters[*].init_scripts[*].s3.endpoint +clusters[*].init_scripts[*].s3.kms_key +clusters[*].init_scripts[*].s3.region +clusters[*].init_scripts[*].volumes.destination +clusters[*].init_scripts[*].workspace.destination +clusters[*].instance_pool_id +clusters[*].label +clusters[*].node_type_id +clusters[*].num_workers +clusters[*].policy_id +clusters[*].spark_conf.* +clusters[*].spark_env_vars.* +clusters[*].ssh_public_keys[*] +configuration.* +environment.dependencies[*] +filters.exclude[*] +filters.include[*] +ingestion_definition.full_refresh_window.days_of_week[*] +ingestion_definition.objects[*].report.destination_catalog +ingestion_definition.objects[*].report.destination_schema +ingestion_definition.objects[*].report.destination_table +ingestion_definition.objects[*].report.source_url +ingestion_definition.objects[*].report.table_configuration.auto_full_refresh_policy.enabled +ingestion_definition.objects[*].report.table_configuration.auto_full_refresh_policy.min_interval_hours +ingestion_definition.objects[*].report.table_configuration.clustering_columns[*] +ingestion_definition.objects[*].report.table_configuration.enable_auto_clustering +ingestion_definition.objects[*].report.table_configuration.exclude_columns[*] +ingestion_definition.objects[*].report.table_configuration.include_columns[*] +ingestion_definition.objects[*].report.table_configuration.primary_keys[*] +ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.cursor_columns[*] +ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.deletion_condition +ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds +ingestion_definition.objects[*].report.table_configuration.row_filter +ingestion_definition.objects[*].report.table_configuration.salesforce_include_formula_fields +ingestion_definition.objects[*].report.table_configuration.scd_type +ingestion_definition.objects[*].report.table_configuration.sequence_by[*] +ingestion_definition.objects[*].report.table_configuration.source_metadata_column +ingestion_definition.objects[*].report.table_configuration.table_properties.* +ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.incremental +ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.parameters.* +ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].key +ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].value +ingestion_definition.objects[*].schema.connector_options.api_source_connector_options.options.* +ingestion_definition.objects[*].schema.connector_options.confluence_options.include_confluence_spaces[*] +ingestion_definition.objects[*].schema.connector_options.gdrive_options.entity_type +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format_options.* +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.infer_column_types +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.rescued_data_column +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_hints +ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.single_variant_column +ingestion_definition.objects[*].schema.connector_options.gdrive_options.url +ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.resource +ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.resource_fields[*] +ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.segments[*] +ingestion_definition.objects[*].schema.connector_options.google_ads_options.lookback_window_days +ingestion_definition.objects[*].schema.connector_options.google_ads_options.manager_account_id +ingestion_definition.objects[*].schema.connector_options.google_ads_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.jira_options.include_jira_spaces[*] +ingestion_definition.objects[*].schema.connector_options.kafka_options.client_config.* +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.format +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.input_column +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.as_variant +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_file_path +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_hints +ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.output_column +ingestion_definition.objects[*].schema.connector_options.kafka_options.max_offsets_per_trigger +ingestion_definition.objects[*].schema.connector_options.kafka_options.starting_offset +ingestion_definition.objects[*].schema.connector_options.kafka_options.topic_pattern +ingestion_definition.objects[*].schema.connector_options.kafka_options.topics[*] +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.format +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.input_column +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.as_variant +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_file_path +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_hints +ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.output_column +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.entity_granularity[*] +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.finder +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.time_granularity +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.lookback_window_days +ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.marketo_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_attribution_windows[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_report_time +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_insights_lookback_window +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_attribution_windows[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_report_time +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.level +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.time_increment +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.level +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.start_date +ingestion_definition.objects[*].schema.connector_options.meta_ads_options.time_increment +ingestion_definition.objects[*].schema.connector_options.outlook_options.attachment_mode +ingestion_definition.objects[*].schema.connector_options.outlook_options.body_format +ingestion_definition.objects[*].schema.connector_options.outlook_options.folder_filter[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.include_folders[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.include_mailboxes[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.include_senders[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.include_subjects[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.sender_filter[*] +ingestion_definition.objects[*].schema.connector_options.outlook_options.start_date +ingestion_definition.objects[*].schema.connector_options.outlook_options.subject_filter[*] +ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.custom_report_options.breakdowns[*] +ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.custom_report_options.fields[*] +ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.lookback_window_days +ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.entity_type +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format_options.* +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.infer_column_types +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_hints +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.single_variant_column +ingestion_definition.objects[*].schema.connector_options.sharepoint_options.url +ingestion_definition.objects[*].schema.connector_options.smartsheet_options.enforce_schema +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.data_level +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.dimensions[*] +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.query_lifetime +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.report_type +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.data_level +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.dimensions[*] +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.lookback_window_days +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.metrics[*] +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.query_lifetime +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.report_type +ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.sync_start_date +ingestion_definition.objects[*].schema.connector_options.zendesk_support_options.start_date +ingestion_definition.objects[*].schema.destination_catalog +ingestion_definition.objects[*].schema.destination_schema +ingestion_definition.objects[*].schema.fanout_options.fanout_by +ingestion_definition.objects[*].schema.fanout_options.transforms[*].format +ingestion_definition.objects[*].schema.fanout_options.transforms[*].input_column +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.as_variant +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_evolution_mode +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_file_path +ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_hints +ingestion_definition.objects[*].schema.fanout_options.transforms[*].output_column +ingestion_definition.objects[*].schema.source_catalog +ingestion_definition.objects[*].schema.source_schema +ingestion_definition.objects[*].schema.table_configuration.auto_full_refresh_policy.enabled +ingestion_definition.objects[*].schema.table_configuration.auto_full_refresh_policy.min_interval_hours +ingestion_definition.objects[*].schema.table_configuration.clustering_columns[*] +ingestion_definition.objects[*].schema.table_configuration.enable_auto_clustering +ingestion_definition.objects[*].schema.table_configuration.exclude_columns[*] +ingestion_definition.objects[*].schema.table_configuration.include_columns[*] +ingestion_definition.objects[*].schema.table_configuration.primary_keys[*] +ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.cursor_columns[*] +ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.deletion_condition +ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds +ingestion_definition.objects[*].schema.table_configuration.row_filter +ingestion_definition.objects[*].schema.table_configuration.salesforce_include_formula_fields +ingestion_definition.objects[*].schema.table_configuration.scd_type +ingestion_definition.objects[*].schema.table_configuration.sequence_by[*] +ingestion_definition.objects[*].schema.table_configuration.source_metadata_column +ingestion_definition.objects[*].schema.table_configuration.table_properties.* +ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.incremental +ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.parameters.* +ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].key +ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].value +ingestion_definition.objects[*].table.connector_options.api_source_connector_options.options.* +ingestion_definition.objects[*].table.connector_options.confluence_options.include_confluence_spaces[*] +ingestion_definition.objects[*].table.connector_options.gdrive_options.entity_type +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format_options.* +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.infer_column_types +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.rescued_data_column +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_hints +ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.single_variant_column +ingestion_definition.objects[*].table.connector_options.gdrive_options.url +ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.resource +ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.resource_fields[*] +ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.segments[*] +ingestion_definition.objects[*].table.connector_options.google_ads_options.lookback_window_days +ingestion_definition.objects[*].table.connector_options.google_ads_options.manager_account_id +ingestion_definition.objects[*].table.connector_options.google_ads_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.jira_options.include_jira_spaces[*] +ingestion_definition.objects[*].table.connector_options.kafka_options.client_config.* +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.format +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.input_column +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.as_variant +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_file_path +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_hints +ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.output_column +ingestion_definition.objects[*].table.connector_options.kafka_options.max_offsets_per_trigger +ingestion_definition.objects[*].table.connector_options.kafka_options.starting_offset +ingestion_definition.objects[*].table.connector_options.kafka_options.topic_pattern +ingestion_definition.objects[*].table.connector_options.kafka_options.topics[*] +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.format +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.input_column +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.as_variant +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_file_path +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_hints +ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.output_column +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.entity_granularity[*] +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.finder +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.time_granularity +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.lookback_window_days +ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.marketo_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_attribution_windows[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_breakdowns[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_report_time +ingestion_definition.objects[*].table.connector_options.meta_ads_options.breakdowns[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_insights_lookback_window +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_attribution_windows[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_breakdowns[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_report_time +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.breakdowns[*] +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.level +ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.time_increment +ingestion_definition.objects[*].table.connector_options.meta_ads_options.level +ingestion_definition.objects[*].table.connector_options.meta_ads_options.start_date +ingestion_definition.objects[*].table.connector_options.meta_ads_options.time_increment +ingestion_definition.objects[*].table.connector_options.outlook_options.attachment_mode +ingestion_definition.objects[*].table.connector_options.outlook_options.body_format +ingestion_definition.objects[*].table.connector_options.outlook_options.folder_filter[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.include_folders[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.include_mailboxes[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.include_senders[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.include_subjects[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.sender_filter[*] +ingestion_definition.objects[*].table.connector_options.outlook_options.start_date +ingestion_definition.objects[*].table.connector_options.outlook_options.subject_filter[*] +ingestion_definition.objects[*].table.connector_options.reddit_ads_options.custom_report_options.breakdowns[*] +ingestion_definition.objects[*].table.connector_options.reddit_ads_options.custom_report_options.fields[*] +ingestion_definition.objects[*].table.connector_options.reddit_ads_options.lookback_window_days +ingestion_definition.objects[*].table.connector_options.reddit_ads_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.sharepoint_options.entity_type +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format_options.* +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.infer_column_types +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_hints +ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.single_variant_column +ingestion_definition.objects[*].table.connector_options.sharepoint_options.url +ingestion_definition.objects[*].table.connector_options.smartsheet_options.enforce_schema +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.data_level +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.dimensions[*] +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.metrics[*] +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.query_lifetime +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.report_type +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.data_level +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.dimensions[*] +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.lookback_window_days +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.metrics[*] +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.query_lifetime +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.report_type +ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.sync_start_date +ingestion_definition.objects[*].table.connector_options.zendesk_support_options.start_date +ingestion_definition.objects[*].table.destination_catalog +ingestion_definition.objects[*].table.destination_schema +ingestion_definition.objects[*].table.destination_table +ingestion_definition.objects[*].table.source_catalog +ingestion_definition.objects[*].table.source_schema +ingestion_definition.objects[*].table.source_table +ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy.enabled +ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy.min_interval_hours +ingestion_definition.objects[*].table.table_configuration.clustering_columns[*] +ingestion_definition.objects[*].table.table_configuration.enable_auto_clustering +ingestion_definition.objects[*].table.table_configuration.exclude_columns[*] +ingestion_definition.objects[*].table.table_configuration.include_columns[*] +ingestion_definition.objects[*].table.table_configuration.primary_keys[*] +ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.cursor_columns[*] +ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.deletion_condition +ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds +ingestion_definition.objects[*].table.table_configuration.row_filter +ingestion_definition.objects[*].table.table_configuration.salesforce_include_formula_fields +ingestion_definition.objects[*].table.table_configuration.scd_type +ingestion_definition.objects[*].table.table_configuration.sequence_by[*] +ingestion_definition.objects[*].table.table_configuration.source_metadata_column +ingestion_definition.objects[*].table.table_configuration.table_properties.* +ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.incremental +ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.parameters.* +ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.report_parameters[*].key +ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.report_parameters[*].value +ingestion_definition.source_configurations[*].api_source_connector_config.configs.* +ingestion_definition.source_configurations[*].catalog.postgres.slot_config.publication_name +ingestion_definition.source_configurations[*].catalog.postgres.slot_config.slot_name +ingestion_definition.source_configurations[*].catalog.source_catalog +ingestion_definition.source_configurations[*].google_ads_config.manager_account_id +ingestion_definition.table_configuration.clustering_columns[*] +ingestion_definition.table_configuration.exclude_columns[*] +ingestion_definition.table_configuration.include_columns[*] +ingestion_definition.table_configuration.primary_keys[*] +ingestion_definition.table_configuration.query_based_connector_config.cursor_columns[*] +ingestion_definition.table_configuration.sequence_by[*] +ingestion_definition.table_configuration.table_properties.* +ingestion_definition.table_configuration.workday_report_parameters.parameters.* +ingestion_definition.table_configuration.workday_report_parameters.report_parameters[*].key +ingestion_definition.table_configuration.workday_report_parameters.report_parameters[*].value +libraries[*].file.path +libraries[*].glob.include +libraries[*].jar +libraries[*].maven.coordinates +libraries[*].maven.exclusions[*] +libraries[*].maven.repo +libraries[*].notebook.path +libraries[*].whl +notifications[*].alerts[*] +notifications[*].email_recipients[*] +parameters.* +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +restart_window.days_of_week[*] +tags.* diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 37ceee5dae9..e1009d0d530 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -1,1406 +1,29 @@ === pipeline.yml.tmpl -allow_duplicate_names absent->false SUPPRESSED empty -allow_duplicate_names absent->true OK -allow_duplicate_names false->absent SUPPRESSED empty -allow_duplicate_names false->true OK -allow_duplicate_names true->absent OK -allow_duplicate_names true->false OK -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK -budget_policy_id x->y OK -budget_policy_id y->absent OK -budget_policy_id y->x OK -cascade_on_destroy absent->false OK -cascade_on_destroy absent->true OK -cascade_on_destroy false->absent OK -cascade_on_destroy false->true OK -cascade_on_destroy true->absent OK -cascade_on_destroy true->false OK -catalog absent->x OK -catalog absent->y OK -catalog x->absent OK -catalog x->y OK -catalog y->absent OK -catalog y->x OK -channel absent->x OK -channel absent->y OK -channel x->absent OK -channel x->y OK -channel y->absent OK -channel y->x OK -continuous absent->false SUPPRESSED empty -continuous absent->true OK -continuous false->absent SUPPRESSED empty -continuous false->true OK -continuous true->absent OK -continuous true->false OK -deployment.deployment_id absent->x OK -deployment.deployment_id absent->y OK -deployment.deployment_id x->absent OK -deployment.deployment_id x->y OK -deployment.deployment_id y->absent OK -deployment.deployment_id y->x OK -deployment.kind x->y OK -deployment.kind y->x OK -deployment.metadata_file_path absent->x OK -deployment.metadata_file_path absent->y OK -deployment.metadata_file_path x->absent OK -deployment.metadata_file_path x->y OK -deployment.metadata_file_path y->absent OK -deployment.metadata_file_path y->x OK -deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service -deployment.version_id x->absent NOT_OBSERVABLE -deployment.version_id x->y SUPPRESSED managed by the deployment metadata service -deployment.version_id y->absent NOT_OBSERVABLE -deployment.version_id y->x SUPPRESSED managed by the deployment metadata service -development absent->false SUPPRESSED empty -development absent->true OK -development false->absent SUPPRESSED empty -development false->true OK -development true->absent OK -development true->false OK -dry_run absent->false SUPPRESSED empty -dry_run absent->true DRIFT dry_run -dry_run false->absent SUPPRESSED remote_already_set -dry_run false->true DRIFT dry_run -dry_run true->absent SUPPRESSED remote_already_set -dry_run true->false OK -edition absent->x OK -edition absent->y OK -edition x->absent OK -edition x->y OK -edition y->absent OK -edition y->x OK -environment.environment_version absent->x OK -environment.environment_version absent->y OK -environment.environment_version x->absent OK -environment.environment_version x->y OK -environment.environment_version y->absent OK -environment.environment_version y->x OK -event_log.catalog absent->x OK -event_log.catalog absent->y OK -event_log.catalog x->absent OK -event_log.catalog x->y OK -event_log.catalog y->absent OK -event_log.catalog y->x OK -event_log.name absent->x OK -event_log.name absent->y OK -event_log.name x->absent OK -event_log.name x->y OK -event_log.name y->absent OK -event_log.name y->x OK -event_log.schema absent->x OK -event_log.schema absent->y OK -event_log.schema x->absent OK -event_log.schema x->y OK -event_log.schema y->absent OK -event_log.schema y->x OK -gateway_definition.connection_id absent->x OK -gateway_definition.connection_id absent->y OK_RECREATE -gateway_definition.connection_id x->absent OK_RECREATE -gateway_definition.connection_id x->y OK_RECREATE -gateway_definition.connection_id y->absent OK_RECREATE -gateway_definition.connection_id y->x OK_RECREATE -gateway_definition.connection_name x->y OK_RECREATE -gateway_definition.connection_name y->x OK_RECREATE -gateway_definition.connection_parameters.source_catalog absent->x OK -gateway_definition.connection_parameters.source_catalog absent->y OK -gateway_definition.connection_parameters.source_catalog x->absent OK -gateway_definition.connection_parameters.source_catalog x->y OK -gateway_definition.connection_parameters.source_catalog y->absent OK -gateway_definition.connection_parameters.source_catalog y->x OK -gateway_definition.gateway_storage_catalog x->y OK_RECREATE -gateway_definition.gateway_storage_catalog y->x OK_RECREATE -gateway_definition.gateway_storage_name absent->x OK -gateway_definition.gateway_storage_name absent->y OK -gateway_definition.gateway_storage_name x->absent OK -gateway_definition.gateway_storage_name x->y OK -gateway_definition.gateway_storage_name y->absent OK -gateway_definition.gateway_storage_name y->x OK -gateway_definition.gateway_storage_schema x->y OK_RECREATE -gateway_definition.gateway_storage_schema y->x OK_RECREATE -id absent->x NOT_OBSERVABLE -id absent->y NOT_OBSERVABLE -id x->absent NOT_OBSERVABLE -id x->y NOT_OBSERVABLE -id y->absent NOT_OBSERVABLE -id y->x NOT_OBSERVABLE -ingestion_definition.connection_name absent->x OK -ingestion_definition.connection_name absent->y OK_RECREATE -ingestion_definition.connection_name x->absent OK_RECREATE -ingestion_definition.connection_name x->y OK_RECREATE -ingestion_definition.connection_name y->absent OK_RECREATE -ingestion_definition.connection_name y->x OK_RECREATE -ingestion_definition.connector_type absent->x OK -ingestion_definition.connector_type absent->y OK -ingestion_definition.connector_type x->absent OK -ingestion_definition.connector_type x->y OK -ingestion_definition.connector_type y->absent OK -ingestion_definition.connector_type y->x OK -ingestion_definition.data_staging_options.catalog_name x->y OK -ingestion_definition.data_staging_options.catalog_name y->x OK -ingestion_definition.data_staging_options.schema_name x->y OK -ingestion_definition.data_staging_options.schema_name y->x OK -ingestion_definition.data_staging_options.volume_name absent->x OK -ingestion_definition.data_staging_options.volume_name absent->y OK -ingestion_definition.data_staging_options.volume_name x->absent OK -ingestion_definition.data_staging_options.volume_name x->y OK -ingestion_definition.data_staging_options.volume_name y->absent OK -ingestion_definition.data_staging_options.volume_name y->x OK -ingestion_definition.full_refresh_window.start_hour 1->2 OK -ingestion_definition.full_refresh_window.start_hour 2->1 OK -ingestion_definition.full_refresh_window.time_zone_id absent->x OK -ingestion_definition.full_refresh_window.time_zone_id absent->y OK -ingestion_definition.full_refresh_window.time_zone_id x->absent OK -ingestion_definition.full_refresh_window.time_zone_id x->y OK -ingestion_definition.full_refresh_window.time_zone_id y->absent OK -ingestion_definition.full_refresh_window.time_zone_id y->x OK -ingestion_definition.ingest_from_uc_foreign_catalog absent->false SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog absent->true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog false->absent SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog false->true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true->absent OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true->false OK_RECREATE -ingestion_definition.ingestion_gateway_id absent->x OK_RECREATE -ingestion_definition.ingestion_gateway_id absent->y OK_RECREATE -ingestion_definition.ingestion_gateway_id x->absent OK_RECREATE -ingestion_definition.ingestion_gateway_id x->y OK_RECREATE -ingestion_definition.ingestion_gateway_id y->absent OK_RECREATE -ingestion_definition.ingestion_gateway_id y->x OK_RECREATE -ingestion_definition.netsuite_jar_path absent->x OK -ingestion_definition.netsuite_jar_path absent->y OK -ingestion_definition.netsuite_jar_path x->absent OK -ingestion_definition.netsuite_jar_path x->y OK -ingestion_definition.netsuite_jar_path y->absent OK -ingestion_definition.netsuite_jar_path y->x OK -ingestion_definition.source_type absent->x OK -ingestion_definition.source_type absent->y OK -ingestion_definition.source_type x->absent OK -ingestion_definition.source_type x->y OK -ingestion_definition.source_type y->absent OK -ingestion_definition.source_type y->x OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false->true OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true->false OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->2 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->2 OK -ingestion_definition.table_configuration.enable_auto_clustering absent->false SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering absent->true OK -ingestion_definition.table_configuration.enable_auto_clustering false->absent SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering false->true OK -ingestion_definition.table_configuration.enable_auto_clustering true->absent OK -ingestion_definition.table_configuration.enable_auto_clustering true->false OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->x OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->x OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->2 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->2 OK -ingestion_definition.table_configuration.row_filter absent->x OK -ingestion_definition.table_configuration.row_filter absent->y OK -ingestion_definition.table_configuration.row_filter x->absent OK -ingestion_definition.table_configuration.row_filter x->y OK -ingestion_definition.table_configuration.row_filter y->absent OK -ingestion_definition.table_configuration.row_filter y->x OK -ingestion_definition.table_configuration.salesforce_include_formula_fields absent->false SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields absent->true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields false->absent SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields false->true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true->absent OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true->false OK -ingestion_definition.table_configuration.scd_type absent->x OK -ingestion_definition.table_configuration.scd_type absent->y OK -ingestion_definition.table_configuration.scd_type x->absent OK -ingestion_definition.table_configuration.scd_type x->y OK -ingestion_definition.table_configuration.scd_type y->absent OK -ingestion_definition.table_configuration.scd_type y->x OK -ingestion_definition.table_configuration.source_metadata_column absent->x OK -ingestion_definition.table_configuration.source_metadata_column absent->y OK -ingestion_definition.table_configuration.source_metadata_column x->absent OK -ingestion_definition.table_configuration.source_metadata_column x->y OK -ingestion_definition.table_configuration.source_metadata_column y->absent OK -ingestion_definition.table_configuration.source_metadata_column y->x OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent->false OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent->true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental false->absent SUPPRESSED empty -ingestion_definition.table_configuration.workday_report_parameters.incremental false->true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true->absent OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true->false OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -photon absent->false SUPPRESSED empty -photon absent->true OK -photon false->absent SUPPRESSED empty -photon false->true OK -photon true->absent OK -photon true->false OK -restart_window.start_hour 1->2 OK -restart_window.start_hour 2->1 OK -restart_window.time_zone_id absent->x OK -restart_window.time_zone_id absent->y OK -restart_window.time_zone_id x->absent OK -restart_window.time_zone_id x->y OK -restart_window.time_zone_id y->absent OK -restart_window.time_zone_id y->x OK -root_path absent->x OK -root_path absent->y OK -root_path x->absent OK -root_path x->y OK -root_path y->absent OK -root_path y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -schema absent->x OK -schema absent->y OK -schema x->absent OK -schema x->y OK -schema y->absent OK -schema y->x OK -serverless absent->false SUPPRESSED empty -serverless absent->true OK -serverless false->absent SUPPRESSED empty -serverless false->true OK -serverless true->absent OK -serverless true->false OK -serverless_compute_id absent->x OK -serverless_compute_id absent->y OK -serverless_compute_id x->absent OK -serverless_compute_id x->y OK -serverless_compute_id y->absent OK -serverless_compute_id y->x OK -storage absent->x OK_RECREATE -storage absent->y OK_RECREATE -storage x->absent OK_RECREATE -storage x->y OK_RECREATE -storage y->absent OK_RECREATE -storage y->x OK_RECREATE -target absent->x OK -target absent->y OK -target x->absent OK -target x->y OK -target y->absent OK -target y->x OK -trigger.cron.quartz_cron_schedule absent->x OK -trigger.cron.quartz_cron_schedule absent->y OK -trigger.cron.quartz_cron_schedule x->absent OK -trigger.cron.quartz_cron_schedule x->y OK -trigger.cron.quartz_cron_schedule y->absent OK -trigger.cron.quartz_cron_schedule y->x OK -trigger.cron.timezone_id absent->x OK -trigger.cron.timezone_id absent->y OK -trigger.cron.timezone_id x->absent OK -trigger.cron.timezone_id x->y OK -trigger.cron.timezone_id y->absent OK -trigger.cron.timezone_id y->x OK -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent OK -usage_policy_id x->y OK -usage_policy_id y->absent OK -usage_policy_id y->x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +dry_run absent true POST_DEPLOY_DRIFT dry_run +dry_run false absent SUPPRESSED remote_already_set +dry_run false true POST_DEPLOY_DRIFT dry_run +dry_run true absent SUPPRESSED remote_already_set === pipeline_allow_duplicate_names.yml.tmpl -allow_duplicate_names absent->false SUPPRESSED empty -allow_duplicate_names absent->true OK -allow_duplicate_names false->absent SUPPRESSED empty -allow_duplicate_names false->true OK -allow_duplicate_names true->absent OK -allow_duplicate_names true->false OK -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK -budget_policy_id x->y OK -budget_policy_id y->absent OK -budget_policy_id y->x OK -cascade_on_destroy absent->false OK -cascade_on_destroy absent->true OK -cascade_on_destroy false->absent OK -cascade_on_destroy false->true OK -cascade_on_destroy true->absent OK -cascade_on_destroy true->false OK -catalog absent->x OK -catalog absent->y OK -catalog x->absent OK -catalog x->y OK -catalog y->absent OK -catalog y->x OK -channel absent->x OK -channel absent->y OK -channel x->absent OK -channel x->y OK -channel y->absent OK -channel y->x OK -continuous absent->false SUPPRESSED empty -continuous absent->true OK -continuous false->absent SUPPRESSED empty -continuous false->true OK -continuous true->absent OK -continuous true->false OK -deployment.deployment_id absent->x OK -deployment.deployment_id absent->y OK -deployment.deployment_id x->absent OK -deployment.deployment_id x->y OK -deployment.deployment_id y->absent OK -deployment.deployment_id y->x OK -deployment.kind x->y OK -deployment.kind y->x OK -deployment.metadata_file_path absent->x OK -deployment.metadata_file_path absent->y OK -deployment.metadata_file_path x->absent OK -deployment.metadata_file_path x->y OK -deployment.metadata_file_path y->absent OK -deployment.metadata_file_path y->x OK -deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service -deployment.version_id x->absent NOT_OBSERVABLE -deployment.version_id x->y SUPPRESSED managed by the deployment metadata service -deployment.version_id y->absent NOT_OBSERVABLE -deployment.version_id y->x SUPPRESSED managed by the deployment metadata service -development absent->false SUPPRESSED empty -development absent->true OK -development false->absent SUPPRESSED empty -development false->true OK -development true->absent OK -development true->false OK -dry_run absent->false SUPPRESSED empty -dry_run absent->true DRIFT dry_run -dry_run false->absent SUPPRESSED remote_already_set -dry_run false->true DRIFT dry_run -dry_run true->absent SUPPRESSED remote_already_set -dry_run true->false OK -edition absent->x OK -edition absent->y OK -edition x->absent OK -edition x->y OK -edition y->absent OK -edition y->x OK -environment.environment_version absent->x OK -environment.environment_version absent->y OK -environment.environment_version x->absent OK -environment.environment_version x->y OK -environment.environment_version y->absent OK -environment.environment_version y->x OK -event_log.catalog absent->x OK -event_log.catalog absent->y OK -event_log.catalog x->absent OK -event_log.catalog x->y OK -event_log.catalog y->absent OK -event_log.catalog y->x OK -event_log.name absent->x OK -event_log.name absent->y OK -event_log.name x->absent OK -event_log.name x->y OK -event_log.name y->absent OK -event_log.name y->x OK -event_log.schema absent->x OK -event_log.schema absent->y OK -event_log.schema x->absent OK -event_log.schema x->y OK -event_log.schema y->absent OK -event_log.schema y->x OK -gateway_definition.connection_id absent->x OK -gateway_definition.connection_id absent->y OK_RECREATE -gateway_definition.connection_id x->absent OK_RECREATE -gateway_definition.connection_id x->y OK_RECREATE -gateway_definition.connection_id y->absent OK_RECREATE -gateway_definition.connection_id y->x OK_RECREATE -gateway_definition.connection_name x->y OK_RECREATE -gateway_definition.connection_name y->x OK_RECREATE -gateway_definition.connection_parameters.source_catalog absent->x OK -gateway_definition.connection_parameters.source_catalog absent->y OK -gateway_definition.connection_parameters.source_catalog x->absent OK -gateway_definition.connection_parameters.source_catalog x->y OK -gateway_definition.connection_parameters.source_catalog y->absent OK -gateway_definition.connection_parameters.source_catalog y->x OK -gateway_definition.gateway_storage_catalog x->y OK_RECREATE -gateway_definition.gateway_storage_catalog y->x OK_RECREATE -gateway_definition.gateway_storage_name absent->x OK -gateway_definition.gateway_storage_name absent->y OK -gateway_definition.gateway_storage_name x->absent OK -gateway_definition.gateway_storage_name x->y OK -gateway_definition.gateway_storage_name y->absent OK -gateway_definition.gateway_storage_name y->x OK -gateway_definition.gateway_storage_schema x->y OK_RECREATE -gateway_definition.gateway_storage_schema y->x OK_RECREATE -id absent->x NOT_OBSERVABLE -id absent->y NOT_OBSERVABLE -id x->absent NOT_OBSERVABLE -id x->y NOT_OBSERVABLE -id y->absent NOT_OBSERVABLE -id y->x NOT_OBSERVABLE -ingestion_definition.connection_name absent->x OK -ingestion_definition.connection_name absent->y OK_RECREATE -ingestion_definition.connection_name x->absent OK_RECREATE -ingestion_definition.connection_name x->y OK_RECREATE -ingestion_definition.connection_name y->absent OK_RECREATE -ingestion_definition.connection_name y->x OK_RECREATE -ingestion_definition.connector_type absent->x OK -ingestion_definition.connector_type absent->y OK -ingestion_definition.connector_type x->absent OK -ingestion_definition.connector_type x->y OK -ingestion_definition.connector_type y->absent OK -ingestion_definition.connector_type y->x OK -ingestion_definition.data_staging_options.catalog_name x->y OK -ingestion_definition.data_staging_options.catalog_name y->x OK -ingestion_definition.data_staging_options.schema_name x->y OK -ingestion_definition.data_staging_options.schema_name y->x OK -ingestion_definition.data_staging_options.volume_name absent->x OK -ingestion_definition.data_staging_options.volume_name absent->y OK -ingestion_definition.data_staging_options.volume_name x->absent OK -ingestion_definition.data_staging_options.volume_name x->y OK -ingestion_definition.data_staging_options.volume_name y->absent OK -ingestion_definition.data_staging_options.volume_name y->x OK -ingestion_definition.full_refresh_window.start_hour 1->2 OK -ingestion_definition.full_refresh_window.start_hour 2->1 OK -ingestion_definition.full_refresh_window.time_zone_id absent->x OK -ingestion_definition.full_refresh_window.time_zone_id absent->y OK -ingestion_definition.full_refresh_window.time_zone_id x->absent OK -ingestion_definition.full_refresh_window.time_zone_id x->y OK -ingestion_definition.full_refresh_window.time_zone_id y->absent OK -ingestion_definition.full_refresh_window.time_zone_id y->x OK -ingestion_definition.ingest_from_uc_foreign_catalog absent->false SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog absent->true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog false->absent SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog false->true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true->absent OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true->false OK_RECREATE -ingestion_definition.ingestion_gateway_id absent->x OK_RECREATE -ingestion_definition.ingestion_gateway_id absent->y OK_RECREATE -ingestion_definition.ingestion_gateway_id x->absent OK_RECREATE -ingestion_definition.ingestion_gateway_id x->y OK_RECREATE -ingestion_definition.ingestion_gateway_id y->absent OK_RECREATE -ingestion_definition.ingestion_gateway_id y->x OK_RECREATE -ingestion_definition.netsuite_jar_path absent->x OK -ingestion_definition.netsuite_jar_path absent->y OK -ingestion_definition.netsuite_jar_path x->absent OK -ingestion_definition.netsuite_jar_path x->y OK -ingestion_definition.netsuite_jar_path y->absent OK -ingestion_definition.netsuite_jar_path y->x OK -ingestion_definition.source_type absent->x OK -ingestion_definition.source_type absent->y OK -ingestion_definition.source_type x->absent OK -ingestion_definition.source_type x->y OK -ingestion_definition.source_type y->absent OK -ingestion_definition.source_type y->x OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false->true OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true->false OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->2 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->2 OK -ingestion_definition.table_configuration.enable_auto_clustering absent->false SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering absent->true OK -ingestion_definition.table_configuration.enable_auto_clustering false->absent SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering false->true OK -ingestion_definition.table_configuration.enable_auto_clustering true->absent OK -ingestion_definition.table_configuration.enable_auto_clustering true->false OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->x OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->x OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->2 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->2 OK -ingestion_definition.table_configuration.row_filter absent->x OK -ingestion_definition.table_configuration.row_filter absent->y OK -ingestion_definition.table_configuration.row_filter x->absent OK -ingestion_definition.table_configuration.row_filter x->y OK -ingestion_definition.table_configuration.row_filter y->absent OK -ingestion_definition.table_configuration.row_filter y->x OK -ingestion_definition.table_configuration.salesforce_include_formula_fields absent->false SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields absent->true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields false->absent SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields false->true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true->absent OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true->false OK -ingestion_definition.table_configuration.scd_type absent->x OK -ingestion_definition.table_configuration.scd_type absent->y OK -ingestion_definition.table_configuration.scd_type x->absent OK -ingestion_definition.table_configuration.scd_type x->y OK -ingestion_definition.table_configuration.scd_type y->absent OK -ingestion_definition.table_configuration.scd_type y->x OK -ingestion_definition.table_configuration.source_metadata_column absent->x OK -ingestion_definition.table_configuration.source_metadata_column absent->y OK -ingestion_definition.table_configuration.source_metadata_column x->absent OK -ingestion_definition.table_configuration.source_metadata_column x->y OK -ingestion_definition.table_configuration.source_metadata_column y->absent OK -ingestion_definition.table_configuration.source_metadata_column y->x OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent->false OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent->true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental false->absent SUPPRESSED empty -ingestion_definition.table_configuration.workday_report_parameters.incremental false->true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true->absent OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true->false OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -photon absent->false SUPPRESSED empty -photon absent->true OK -photon false->absent SUPPRESSED empty -photon false->true OK -photon true->absent OK -photon true->false OK -restart_window.start_hour 1->2 OK -restart_window.start_hour 2->1 OK -restart_window.time_zone_id absent->x OK -restart_window.time_zone_id absent->y OK -restart_window.time_zone_id x->absent OK -restart_window.time_zone_id x->y OK -restart_window.time_zone_id y->absent OK -restart_window.time_zone_id y->x OK -root_path absent->x OK -root_path absent->y OK -root_path x->absent OK -root_path x->y OK -root_path y->absent OK -root_path y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -schema absent->x OK -schema absent->y OK -schema x->absent OK -schema x->y OK -schema y->absent OK -schema y->x OK -serverless absent->false SUPPRESSED empty -serverless absent->true OK -serverless false->absent SUPPRESSED empty -serverless false->true OK -serverless true->absent OK -serverless true->false OK -serverless_compute_id absent->x OK -serverless_compute_id absent->y OK -serverless_compute_id x->absent OK -serverless_compute_id x->y OK -serverless_compute_id y->absent OK -serverless_compute_id y->x OK -storage absent->x OK_RECREATE -storage absent->y OK_RECREATE -storage x->absent OK_RECREATE -storage x->y OK_RECREATE -storage y->absent OK_RECREATE -storage y->x OK_RECREATE -target absent->x OK -target absent->y OK -target x->absent OK -target x->y OK -target y->absent OK -target y->x OK -trigger.cron.quartz_cron_schedule absent->x OK -trigger.cron.quartz_cron_schedule absent->y OK -trigger.cron.quartz_cron_schedule x->absent OK -trigger.cron.quartz_cron_schedule x->y OK -trigger.cron.quartz_cron_schedule y->absent OK -trigger.cron.quartz_cron_schedule y->x OK -trigger.cron.timezone_id absent->x OK -trigger.cron.timezone_id absent->y OK -trigger.cron.timezone_id x->absent OK -trigger.cron.timezone_id x->y OK -trigger.cron.timezone_id y->absent OK -trigger.cron.timezone_id y->x OK -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent OK -usage_policy_id x->y OK -usage_policy_id y->absent OK -usage_policy_id y->x OK +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +dry_run absent true POST_DEPLOY_DRIFT dry_run +dry_run false absent SUPPRESSED remote_already_set +dry_run false true POST_DEPLOY_DRIFT dry_run +dry_run true absent SUPPRESSED remote_already_set === pipeline_apply_policy_default_values.yml.tmpl -allow_duplicate_names absent->false SUPPRESSED empty -allow_duplicate_names absent->true OK -allow_duplicate_names false->absent SUPPRESSED empty -allow_duplicate_names false->true OK -allow_duplicate_names true->absent OK -allow_duplicate_names true->false OK -budget_policy_id absent->x OK -budget_policy_id absent->y OK -budget_policy_id x->absent OK -budget_policy_id x->y OK -budget_policy_id y->absent OK -budget_policy_id y->x OK -cascade_on_destroy absent->false OK -cascade_on_destroy absent->true OK -cascade_on_destroy false->absent OK -cascade_on_destroy false->true OK -cascade_on_destroy true->absent OK -cascade_on_destroy true->false OK -catalog absent->x OK -catalog absent->y OK -catalog x->absent OK -catalog x->y OK -catalog y->absent OK -catalog y->x OK -channel absent->x OK -channel absent->y OK -channel x->absent OK -channel x->y OK -channel y->absent OK -channel y->x OK -continuous absent->false SUPPRESSED empty -continuous absent->true OK -continuous false->absent SUPPRESSED empty -continuous false->true OK -continuous true->absent OK -continuous true->false OK -deployment.deployment_id absent->x OK -deployment.deployment_id absent->y OK -deployment.deployment_id x->absent OK -deployment.deployment_id x->y OK -deployment.deployment_id y->absent OK -deployment.deployment_id y->x OK -deployment.kind x->y OK -deployment.kind y->x OK -deployment.metadata_file_path absent->x OK -deployment.metadata_file_path absent->y OK -deployment.metadata_file_path x->absent OK -deployment.metadata_file_path x->y OK -deployment.metadata_file_path y->absent OK -deployment.metadata_file_path y->x OK -deployment.version_id absent->x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent->y SUPPRESSED managed by the deployment metadata service -deployment.version_id x->absent NOT_OBSERVABLE -deployment.version_id x->y SUPPRESSED managed by the deployment metadata service -deployment.version_id y->absent NOT_OBSERVABLE -deployment.version_id y->x SUPPRESSED managed by the deployment metadata service -development absent->false SUPPRESSED empty -development absent->true OK -development false->absent SUPPRESSED empty -development false->true OK -development true->absent OK -development true->false OK -dry_run absent->false SUPPRESSED empty -dry_run absent->true DRIFT dry_run -dry_run false->absent SUPPRESSED remote_already_set -dry_run false->true DRIFT dry_run -dry_run true->absent SUPPRESSED remote_already_set -dry_run true->false OK -edition absent->x OK -edition absent->y OK -edition x->absent OK -edition x->y OK -edition y->absent OK -edition y->x OK -environment.environment_version absent->x OK -environment.environment_version absent->y OK -environment.environment_version x->absent OK -environment.environment_version x->y OK -environment.environment_version y->absent OK -environment.environment_version y->x OK -event_log.catalog absent->x OK -event_log.catalog absent->y OK -event_log.catalog x->absent OK -event_log.catalog x->y OK -event_log.catalog y->absent OK -event_log.catalog y->x OK -event_log.name absent->x OK -event_log.name absent->y OK -event_log.name x->absent OK -event_log.name x->y OK -event_log.name y->absent OK -event_log.name y->x OK -event_log.schema absent->x OK -event_log.schema absent->y OK -event_log.schema x->absent OK -event_log.schema x->y OK -event_log.schema y->absent OK -event_log.schema y->x OK -gateway_definition.connection_id absent->x OK -gateway_definition.connection_id absent->y OK_RECREATE -gateway_definition.connection_id x->absent OK_RECREATE -gateway_definition.connection_id x->y OK_RECREATE -gateway_definition.connection_id y->absent OK_RECREATE -gateway_definition.connection_id y->x OK_RECREATE -gateway_definition.connection_name x->y OK_RECREATE -gateway_definition.connection_name y->x OK_RECREATE -gateway_definition.connection_parameters.source_catalog absent->x OK -gateway_definition.connection_parameters.source_catalog absent->y OK -gateway_definition.connection_parameters.source_catalog x->absent OK -gateway_definition.connection_parameters.source_catalog x->y OK -gateway_definition.connection_parameters.source_catalog y->absent OK -gateway_definition.connection_parameters.source_catalog y->x OK -gateway_definition.gateway_storage_catalog x->y OK_RECREATE -gateway_definition.gateway_storage_catalog y->x OK_RECREATE -gateway_definition.gateway_storage_name absent->x OK -gateway_definition.gateway_storage_name absent->y OK -gateway_definition.gateway_storage_name x->absent OK -gateway_definition.gateway_storage_name x->y OK -gateway_definition.gateway_storage_name y->absent OK -gateway_definition.gateway_storage_name y->x OK -gateway_definition.gateway_storage_schema x->y OK_RECREATE -gateway_definition.gateway_storage_schema y->x OK_RECREATE -id absent->x NOT_OBSERVABLE -id absent->y NOT_OBSERVABLE -id x->absent NOT_OBSERVABLE -id x->y NOT_OBSERVABLE -id y->absent NOT_OBSERVABLE -id y->x NOT_OBSERVABLE -ingestion_definition.connection_name absent->x OK -ingestion_definition.connection_name absent->y OK_RECREATE -ingestion_definition.connection_name x->absent OK_RECREATE -ingestion_definition.connection_name x->y OK_RECREATE -ingestion_definition.connection_name y->absent OK_RECREATE -ingestion_definition.connection_name y->x OK_RECREATE -ingestion_definition.connector_type absent->x OK -ingestion_definition.connector_type absent->y OK -ingestion_definition.connector_type x->absent OK -ingestion_definition.connector_type x->y OK -ingestion_definition.connector_type y->absent OK -ingestion_definition.connector_type y->x OK -ingestion_definition.data_staging_options.catalog_name x->y OK -ingestion_definition.data_staging_options.catalog_name y->x OK -ingestion_definition.data_staging_options.schema_name x->y OK -ingestion_definition.data_staging_options.schema_name y->x OK -ingestion_definition.data_staging_options.volume_name absent->x OK -ingestion_definition.data_staging_options.volume_name absent->y OK -ingestion_definition.data_staging_options.volume_name x->absent OK -ingestion_definition.data_staging_options.volume_name x->y OK -ingestion_definition.data_staging_options.volume_name y->absent OK -ingestion_definition.data_staging_options.volume_name y->x OK -ingestion_definition.full_refresh_window.start_hour 1->2 OK -ingestion_definition.full_refresh_window.start_hour 2->1 OK -ingestion_definition.full_refresh_window.time_zone_id absent->x OK -ingestion_definition.full_refresh_window.time_zone_id absent->y OK -ingestion_definition.full_refresh_window.time_zone_id x->absent OK -ingestion_definition.full_refresh_window.time_zone_id x->y OK -ingestion_definition.full_refresh_window.time_zone_id y->absent OK -ingestion_definition.full_refresh_window.time_zone_id y->x OK -ingestion_definition.ingest_from_uc_foreign_catalog absent->false SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog absent->true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog false->absent SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog false->true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true->absent OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true->false OK_RECREATE -ingestion_definition.ingestion_gateway_id absent->x OK_RECREATE -ingestion_definition.ingestion_gateway_id absent->y OK_RECREATE -ingestion_definition.ingestion_gateway_id x->absent OK_RECREATE -ingestion_definition.ingestion_gateway_id x->y OK_RECREATE -ingestion_definition.ingestion_gateway_id y->absent OK_RECREATE -ingestion_definition.ingestion_gateway_id y->x OK_RECREATE -ingestion_definition.netsuite_jar_path absent->x OK -ingestion_definition.netsuite_jar_path absent->y OK -ingestion_definition.netsuite_jar_path x->absent OK -ingestion_definition.netsuite_jar_path x->y OK -ingestion_definition.netsuite_jar_path y->absent OK -ingestion_definition.netsuite_jar_path y->x OK -ingestion_definition.source_type absent->x OK -ingestion_definition.source_type absent->y OK -ingestion_definition.source_type x->absent OK -ingestion_definition.source_type x->y OK -ingestion_definition.source_type y->absent OK -ingestion_definition.source_type y->x OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false->true OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true->false OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->2 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1->absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2->absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent->2 OK -ingestion_definition.table_configuration.enable_auto_clustering absent->false SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering absent->true OK -ingestion_definition.table_configuration.enable_auto_clustering false->absent SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering false->true OK -ingestion_definition.table_configuration.enable_auto_clustering true->absent OK -ingestion_definition.table_configuration.enable_auto_clustering true->false OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->x OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent->y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x->y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y->x OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->2 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1->absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2->absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent->2 OK -ingestion_definition.table_configuration.row_filter absent->x OK -ingestion_definition.table_configuration.row_filter absent->y OK -ingestion_definition.table_configuration.row_filter x->absent OK -ingestion_definition.table_configuration.row_filter x->y OK -ingestion_definition.table_configuration.row_filter y->absent OK -ingestion_definition.table_configuration.row_filter y->x OK -ingestion_definition.table_configuration.salesforce_include_formula_fields absent->false SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields absent->true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields false->absent SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields false->true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true->absent OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true->false OK -ingestion_definition.table_configuration.scd_type absent->x OK -ingestion_definition.table_configuration.scd_type absent->y OK -ingestion_definition.table_configuration.scd_type x->absent OK -ingestion_definition.table_configuration.scd_type x->y OK -ingestion_definition.table_configuration.scd_type y->absent OK -ingestion_definition.table_configuration.scd_type y->x OK -ingestion_definition.table_configuration.source_metadata_column absent->x OK -ingestion_definition.table_configuration.source_metadata_column absent->y OK -ingestion_definition.table_configuration.source_metadata_column x->absent OK -ingestion_definition.table_configuration.source_metadata_column x->y OK -ingestion_definition.table_configuration.source_metadata_column y->absent OK -ingestion_definition.table_configuration.source_metadata_column y->x OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent->false OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent->true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental false->absent SUPPRESSED empty -ingestion_definition.table_configuration.workday_report_parameters.incremental false->true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true->absent OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true->false OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name absent->x OK -name absent->y OK -name x->absent OK -name x->y OK -name y->absent OK -name y->x OK -photon absent->false SUPPRESSED empty -photon absent->true OK -photon false->absent SUPPRESSED empty -photon false->true OK -photon true->absent OK -photon true->false OK -restart_window.start_hour 1->2 OK -restart_window.start_hour 2->1 OK -restart_window.time_zone_id absent->x OK -restart_window.time_zone_id absent->y OK -restart_window.time_zone_id x->absent OK -restart_window.time_zone_id x->y OK -restart_window.time_zone_id y->absent OK -restart_window.time_zone_id y->x OK -root_path absent->x OK -root_path absent->y OK -root_path x->absent OK -root_path x->y OK -root_path y->absent OK -root_path y->x OK -run_as.service_principal_name absent->x OK -run_as.service_principal_name absent->y OK -run_as.service_principal_name x->absent OK -run_as.service_principal_name x->y OK -run_as.service_principal_name y->absent OK -run_as.service_principal_name y->x OK -run_as.user_name absent->x OK -run_as.user_name absent->y OK -run_as.user_name x->absent OK -run_as.user_name x->y OK -run_as.user_name y->absent OK -run_as.user_name y->x OK -schema absent->x OK -schema absent->y OK -schema x->absent OK -schema x->y OK -schema y->absent OK -schema y->x OK -serverless absent->false SUPPRESSED empty -serverless absent->true OK -serverless false->absent SUPPRESSED empty -serverless false->true OK -serverless true->absent OK -serverless true->false OK -serverless_compute_id absent->x OK -serverless_compute_id absent->y OK -serverless_compute_id x->absent OK -serverless_compute_id x->y OK -serverless_compute_id y->absent OK -serverless_compute_id y->x OK -storage absent->x OK_RECREATE -storage absent->y OK_RECREATE -storage x->absent OK_RECREATE -storage x->y OK_RECREATE -storage y->absent OK_RECREATE -storage y->x OK_RECREATE -target absent->x OK -target absent->y OK -target x->absent OK -target x->y OK -target y->absent OK -target y->x OK -trigger.cron.quartz_cron_schedule absent->x OK -trigger.cron.quartz_cron_schedule absent->y OK -trigger.cron.quartz_cron_schedule x->absent OK -trigger.cron.quartz_cron_schedule x->y OK -trigger.cron.quartz_cron_schedule y->absent OK -trigger.cron.quartz_cron_schedule y->x OK -trigger.cron.timezone_id absent->x OK -trigger.cron.timezone_id absent->y OK -trigger.cron.timezone_id x->absent OK -trigger.cron.timezone_id x->y OK -trigger.cron.timezone_id y->absent OK -trigger.cron.timezone_id y->x OK -usage_policy_id absent->x OK -usage_policy_id absent->y OK -usage_policy_id x->absent OK -usage_policy_id x->y OK -usage_policy_id y->absent OK -usage_policy_id y->x OK - -=== summary -OK 774 -OK_RECREATE 96 -SUPPRESSED 72 -NOT_OBSERVABLE 42 -DRIFT 6 - -=== not covered: 402 fields under a slice or map -clusters[*].apply_policy_default_values -clusters[*].autoscale.max_workers -clusters[*].autoscale.min_workers -clusters[*].autoscale.mode -clusters[*].aws_attributes.availability -clusters[*].aws_attributes.ebs_volume_count -clusters[*].aws_attributes.ebs_volume_iops -clusters[*].aws_attributes.ebs_volume_size -clusters[*].aws_attributes.ebs_volume_throughput -clusters[*].aws_attributes.ebs_volume_type -clusters[*].aws_attributes.first_on_demand -clusters[*].aws_attributes.instance_profile_arn -clusters[*].aws_attributes.spot_bid_price_percent -clusters[*].aws_attributes.zone_id -clusters[*].azure_attributes.availability -clusters[*].azure_attributes.capacity_reservation_group -clusters[*].azure_attributes.first_on_demand -clusters[*].azure_attributes.log_analytics_info.log_analytics_primary_key -clusters[*].azure_attributes.log_analytics_info.log_analytics_workspace_id -clusters[*].azure_attributes.spot_bid_max_price -clusters[*].cluster_log_conf.dbfs.destination -clusters[*].cluster_log_conf.s3.canned_acl -clusters[*].cluster_log_conf.s3.destination -clusters[*].cluster_log_conf.s3.enable_encryption -clusters[*].cluster_log_conf.s3.encryption_type -clusters[*].cluster_log_conf.s3.endpoint -clusters[*].cluster_log_conf.s3.kms_key -clusters[*].cluster_log_conf.s3.region -clusters[*].cluster_log_conf.volumes.destination -clusters[*].custom_tags.* -clusters[*].driver_instance_pool_id -clusters[*].driver_node_type_id -clusters[*].enable_local_disk_encryption -clusters[*].gcp_attributes.availability -clusters[*].gcp_attributes.boot_disk_size -clusters[*].gcp_attributes.confidential_compute_type -clusters[*].gcp_attributes.first_on_demand -clusters[*].gcp_attributes.google_service_account -clusters[*].gcp_attributes.local_ssd_count -clusters[*].gcp_attributes.use_preemptible_executors -clusters[*].gcp_attributes.zone_id -clusters[*].init_scripts[*].abfss.destination -clusters[*].init_scripts[*].dbfs.destination -clusters[*].init_scripts[*].file.destination -clusters[*].init_scripts[*].gcs.destination -clusters[*].init_scripts[*].s3.canned_acl -clusters[*].init_scripts[*].s3.destination -clusters[*].init_scripts[*].s3.enable_encryption -clusters[*].init_scripts[*].s3.encryption_type -clusters[*].init_scripts[*].s3.endpoint -clusters[*].init_scripts[*].s3.kms_key -clusters[*].init_scripts[*].s3.region -clusters[*].init_scripts[*].volumes.destination -clusters[*].init_scripts[*].workspace.destination -clusters[*].instance_pool_id -clusters[*].label -clusters[*].node_type_id -clusters[*].num_workers -clusters[*].policy_id -clusters[*].spark_conf.* -clusters[*].spark_env_vars.* -clusters[*].ssh_public_keys[*] -configuration.* -environment.dependencies[*] -filters.exclude[*] -filters.include[*] -ingestion_definition.full_refresh_window.days_of_week[*] -ingestion_definition.objects[*].report.destination_catalog -ingestion_definition.objects[*].report.destination_schema -ingestion_definition.objects[*].report.destination_table -ingestion_definition.objects[*].report.source_url -ingestion_definition.objects[*].report.table_configuration.auto_full_refresh_policy.enabled -ingestion_definition.objects[*].report.table_configuration.auto_full_refresh_policy.min_interval_hours -ingestion_definition.objects[*].report.table_configuration.clustering_columns[*] -ingestion_definition.objects[*].report.table_configuration.enable_auto_clustering -ingestion_definition.objects[*].report.table_configuration.exclude_columns[*] -ingestion_definition.objects[*].report.table_configuration.include_columns[*] -ingestion_definition.objects[*].report.table_configuration.primary_keys[*] -ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.cursor_columns[*] -ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.deletion_condition -ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds -ingestion_definition.objects[*].report.table_configuration.row_filter -ingestion_definition.objects[*].report.table_configuration.salesforce_include_formula_fields -ingestion_definition.objects[*].report.table_configuration.scd_type -ingestion_definition.objects[*].report.table_configuration.sequence_by[*] -ingestion_definition.objects[*].report.table_configuration.source_metadata_column -ingestion_definition.objects[*].report.table_configuration.table_properties.* -ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.incremental -ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.parameters.* -ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].key -ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].value -ingestion_definition.objects[*].schema.connector_options.api_source_connector_options.options.* -ingestion_definition.objects[*].schema.connector_options.confluence_options.include_confluence_spaces[*] -ingestion_definition.objects[*].schema.connector_options.gdrive_options.entity_type -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format_options.* -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.infer_column_types -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.rescued_data_column -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_hints -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.single_variant_column -ingestion_definition.objects[*].schema.connector_options.gdrive_options.url -ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.resource -ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.resource_fields[*] -ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.segments[*] -ingestion_definition.objects[*].schema.connector_options.google_ads_options.lookback_window_days -ingestion_definition.objects[*].schema.connector_options.google_ads_options.manager_account_id -ingestion_definition.objects[*].schema.connector_options.google_ads_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.jira_options.include_jira_spaces[*] -ingestion_definition.objects[*].schema.connector_options.kafka_options.client_config.* -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.format -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.input_column -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.as_variant -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_file_path -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_hints -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.output_column -ingestion_definition.objects[*].schema.connector_options.kafka_options.max_offsets_per_trigger -ingestion_definition.objects[*].schema.connector_options.kafka_options.starting_offset -ingestion_definition.objects[*].schema.connector_options.kafka_options.topic_pattern -ingestion_definition.objects[*].schema.connector_options.kafka_options.topics[*] -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.format -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.input_column -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.as_variant -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_file_path -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_hints -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.output_column -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.entity_granularity[*] -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.finder -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.time_granularity -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.lookback_window_days -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.marketo_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_attribution_windows[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_report_time -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_insights_lookback_window -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_attribution_windows[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_report_time -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.level -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.time_increment -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.level -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.start_date -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.time_increment -ingestion_definition.objects[*].schema.connector_options.outlook_options.attachment_mode -ingestion_definition.objects[*].schema.connector_options.outlook_options.body_format -ingestion_definition.objects[*].schema.connector_options.outlook_options.folder_filter[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.include_folders[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.include_mailboxes[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.include_senders[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.include_subjects[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.sender_filter[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.start_date -ingestion_definition.objects[*].schema.connector_options.outlook_options.subject_filter[*] -ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.custom_report_options.breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.custom_report_options.fields[*] -ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.lookback_window_days -ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.entity_type -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format_options.* -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.infer_column_types -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_hints -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.single_variant_column -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.url -ingestion_definition.objects[*].schema.connector_options.smartsheet_options.enforce_schema -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.data_level -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.dimensions[*] -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.query_lifetime -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.report_type -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.data_level -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.dimensions[*] -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.lookback_window_days -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.metrics[*] -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.query_lifetime -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.report_type -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.zendesk_support_options.start_date -ingestion_definition.objects[*].schema.destination_catalog -ingestion_definition.objects[*].schema.destination_schema -ingestion_definition.objects[*].schema.fanout_options.fanout_by -ingestion_definition.objects[*].schema.fanout_options.transforms[*].format -ingestion_definition.objects[*].schema.fanout_options.transforms[*].input_column -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.as_variant -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_evolution_mode -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_file_path -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_hints -ingestion_definition.objects[*].schema.fanout_options.transforms[*].output_column -ingestion_definition.objects[*].schema.source_catalog -ingestion_definition.objects[*].schema.source_schema -ingestion_definition.objects[*].schema.table_configuration.auto_full_refresh_policy.enabled -ingestion_definition.objects[*].schema.table_configuration.auto_full_refresh_policy.min_interval_hours -ingestion_definition.objects[*].schema.table_configuration.clustering_columns[*] -ingestion_definition.objects[*].schema.table_configuration.enable_auto_clustering -ingestion_definition.objects[*].schema.table_configuration.exclude_columns[*] -ingestion_definition.objects[*].schema.table_configuration.include_columns[*] -ingestion_definition.objects[*].schema.table_configuration.primary_keys[*] -ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.cursor_columns[*] -ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.deletion_condition -ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds -ingestion_definition.objects[*].schema.table_configuration.row_filter -ingestion_definition.objects[*].schema.table_configuration.salesforce_include_formula_fields -ingestion_definition.objects[*].schema.table_configuration.scd_type -ingestion_definition.objects[*].schema.table_configuration.sequence_by[*] -ingestion_definition.objects[*].schema.table_configuration.source_metadata_column -ingestion_definition.objects[*].schema.table_configuration.table_properties.* -ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.incremental -ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.parameters.* -ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].key -ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].value -ingestion_definition.objects[*].table.connector_options.api_source_connector_options.options.* -ingestion_definition.objects[*].table.connector_options.confluence_options.include_confluence_spaces[*] -ingestion_definition.objects[*].table.connector_options.gdrive_options.entity_type -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format_options.* -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.infer_column_types -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.rescued_data_column -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_hints -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.single_variant_column -ingestion_definition.objects[*].table.connector_options.gdrive_options.url -ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.resource -ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.resource_fields[*] -ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.segments[*] -ingestion_definition.objects[*].table.connector_options.google_ads_options.lookback_window_days -ingestion_definition.objects[*].table.connector_options.google_ads_options.manager_account_id -ingestion_definition.objects[*].table.connector_options.google_ads_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.jira_options.include_jira_spaces[*] -ingestion_definition.objects[*].table.connector_options.kafka_options.client_config.* -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.format -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.input_column -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.as_variant -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_file_path -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_hints -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.output_column -ingestion_definition.objects[*].table.connector_options.kafka_options.max_offsets_per_trigger -ingestion_definition.objects[*].table.connector_options.kafka_options.starting_offset -ingestion_definition.objects[*].table.connector_options.kafka_options.topic_pattern -ingestion_definition.objects[*].table.connector_options.kafka_options.topics[*] -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.format -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.input_column -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.as_variant -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_file_path -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_hints -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.output_column -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.entity_granularity[*] -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.finder -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.time_granularity -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.lookback_window_days -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.marketo_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_attribution_windows[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_breakdowns[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_report_time -ingestion_definition.objects[*].table.connector_options.meta_ads_options.breakdowns[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_insights_lookback_window -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_attribution_windows[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_breakdowns[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_report_time -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.breakdowns[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.level -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.time_increment -ingestion_definition.objects[*].table.connector_options.meta_ads_options.level -ingestion_definition.objects[*].table.connector_options.meta_ads_options.start_date -ingestion_definition.objects[*].table.connector_options.meta_ads_options.time_increment -ingestion_definition.objects[*].table.connector_options.outlook_options.attachment_mode -ingestion_definition.objects[*].table.connector_options.outlook_options.body_format -ingestion_definition.objects[*].table.connector_options.outlook_options.folder_filter[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.include_folders[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.include_mailboxes[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.include_senders[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.include_subjects[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.sender_filter[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.start_date -ingestion_definition.objects[*].table.connector_options.outlook_options.subject_filter[*] -ingestion_definition.objects[*].table.connector_options.reddit_ads_options.custom_report_options.breakdowns[*] -ingestion_definition.objects[*].table.connector_options.reddit_ads_options.custom_report_options.fields[*] -ingestion_definition.objects[*].table.connector_options.reddit_ads_options.lookback_window_days -ingestion_definition.objects[*].table.connector_options.reddit_ads_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.sharepoint_options.entity_type -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format_options.* -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.infer_column_types -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_hints -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.single_variant_column -ingestion_definition.objects[*].table.connector_options.sharepoint_options.url -ingestion_definition.objects[*].table.connector_options.smartsheet_options.enforce_schema -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.data_level -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.dimensions[*] -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.query_lifetime -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.report_type -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.data_level -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.dimensions[*] -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.lookback_window_days -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.metrics[*] -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.query_lifetime -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.report_type -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.zendesk_support_options.start_date -ingestion_definition.objects[*].table.destination_catalog -ingestion_definition.objects[*].table.destination_schema -ingestion_definition.objects[*].table.destination_table -ingestion_definition.objects[*].table.source_catalog -ingestion_definition.objects[*].table.source_schema -ingestion_definition.objects[*].table.source_table -ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy.enabled -ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy.min_interval_hours -ingestion_definition.objects[*].table.table_configuration.clustering_columns[*] -ingestion_definition.objects[*].table.table_configuration.enable_auto_clustering -ingestion_definition.objects[*].table.table_configuration.exclude_columns[*] -ingestion_definition.objects[*].table.table_configuration.include_columns[*] -ingestion_definition.objects[*].table.table_configuration.primary_keys[*] -ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.cursor_columns[*] -ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.deletion_condition -ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds -ingestion_definition.objects[*].table.table_configuration.row_filter -ingestion_definition.objects[*].table.table_configuration.salesforce_include_formula_fields -ingestion_definition.objects[*].table.table_configuration.scd_type -ingestion_definition.objects[*].table.table_configuration.sequence_by[*] -ingestion_definition.objects[*].table.table_configuration.source_metadata_column -ingestion_definition.objects[*].table.table_configuration.table_properties.* -ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.incremental -ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.parameters.* -ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.report_parameters[*].key -ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.report_parameters[*].value -ingestion_definition.source_configurations[*].api_source_connector_config.configs.* -ingestion_definition.source_configurations[*].catalog.postgres.slot_config.publication_name -ingestion_definition.source_configurations[*].catalog.postgres.slot_config.slot_name -ingestion_definition.source_configurations[*].catalog.source_catalog -ingestion_definition.source_configurations[*].google_ads_config.manager_account_id -ingestion_definition.table_configuration.clustering_columns[*] -ingestion_definition.table_configuration.exclude_columns[*] -ingestion_definition.table_configuration.include_columns[*] -ingestion_definition.table_configuration.primary_keys[*] -ingestion_definition.table_configuration.query_based_connector_config.cursor_columns[*] -ingestion_definition.table_configuration.sequence_by[*] -ingestion_definition.table_configuration.table_properties.* -ingestion_definition.table_configuration.workday_report_parameters.parameters.* -ingestion_definition.table_configuration.workday_report_parameters.report_parameters[*].key -ingestion_definition.table_configuration.workday_report_parameters.report_parameters[*].value -libraries[*].file.path -libraries[*].glob.include -libraries[*].jar -libraries[*].maven.coordinates -libraries[*].maven.exclusions[*] -libraries[*].maven.repo -libraries[*].notebook.path -libraries[*].whl -notifications[*].alerts[*] -notifications[*].email_recipients[*] -parameters.* -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -restart_window.days_of_week[*] -tags.* +deployment.version_id absent x SUPPRESSED managed by the deployment metadata service +deployment.version_id absent y SUPPRESSED managed by the deployment metadata service +deployment.version_id x y SUPPRESSED managed by the deployment metadata service +deployment.version_id y x SUPPRESSED managed by the deployment metadata service +dry_run absent true POST_DEPLOY_DRIFT dry_run +dry_run false absent SUPPRESSED remote_already_set +dry_run false true POST_DEPLOY_DRIFT dry_run +dry_run true absent SUPPRESSED remote_already_set diff --git a/bundle/direct/tests/output/postgres_projects.full.txt b/bundle/direct/tests/output/postgres_projects.full.txt new file mode 100644 index 00000000000..7a3c139ae42 --- /dev/null +++ b/bundle/direct/tests/output/postgres_projects.full.txt @@ -0,0 +1,80 @@ +=== postgres_project.yml.tmpl +budget_policy_id absent x OK +budget_policy_id absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +budget_policy_id x y OK +budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +budget_policy_id y x OK +default_branch absent x OK +default_branch absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... +default_branch x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... +default_branch x y OK +default_branch y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... +default_branch y x OK +default_endpoint_settings.autoscaling_limit_max_cu 1 2 OK +default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_max_cu 2 1 OK +default_endpoint_settings.autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_max_cu absent 1 OK +default_endpoint_settings.autoscaling_limit_max_cu absent 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_min_cu 1 2 OK +default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_min_cu 2 1 OK +default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_min_cu absent 1 OK +default_endpoint_settings.autoscaling_limit_min_cu absent 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.no_suspension absent false OK +default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension false absent OK +default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +display_name absent y OK +display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +display_name x y OK +display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +display_name y x OK +enable_pg_native_login absent false SUPPRESSED empty +enable_pg_native_login absent true OK +enable_pg_native_login false absent SUPPRESSED empty +enable_pg_native_login false true OK +enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... +enable_pg_native_login true false OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +pg_version 16 17 OK_RECREATE +pg_version 16 absent OK_RECREATE +pg_version 17 16 OK_RECREATE +pg_version 17 absent OK_RECREATE +pg_version absent 16 OK_RECREATE +pg_version absent 17 OK_RECREATE +project_id pgproj1 pgproj2 OK_RECREATE +project_id pgproj2 pgproj1 OK_RECREATE +purge_on_delete absent false SUPPRESSED empty +purge_on_delete absent true OK +purge_on_delete false absent SUPPRESSED empty +purge_on_delete false true OK +purge_on_delete true absent OK +purge_on_delete true false OK + +=== summary +OK 24 +OK_RECREATE 8 +SUPPRESSED 4 +NOT_OBSERVABLE 6 +BACKEND_ERROR 15 +BASE_ERROR 5 + +=== not covered: 7 fields under a slice or map +custom_tags[*].key +custom_tags[*].value +default_endpoint_settings.pg_settings.* +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index ae074fb3cad..e98bae45387 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -1,80 +1,21 @@ === postgres_project.yml.tmpl -budget_policy_id absent->x OK -budget_policy_id absent->y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -budget_policy_id x->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -budget_policy_id x->y OK -budget_policy_id y->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -budget_policy_id y->x OK -default_branch absent->x OK -default_branch absent->y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... -default_branch x->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... -default_branch x->y OK -default_branch y->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... -default_branch y->x OK -default_endpoint_settings.autoscaling_limit_max_cu 1->2 OK -default_endpoint_settings.autoscaling_limit_max_cu 1->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_max_cu 2->1 OK -default_endpoint_settings.autoscaling_limit_max_cu 2->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_max_cu absent->1 OK -default_endpoint_settings.autoscaling_limit_max_cu absent->2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu 1->2 OK -default_endpoint_settings.autoscaling_limit_min_cu 1->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu 2->1 OK -default_endpoint_settings.autoscaling_limit_min_cu 2->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu absent->1 OK -default_endpoint_settings.autoscaling_limit_min_cu absent->2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.no_suspension absent->false OK -default_endpoint_settings.no_suspension absent->true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension false->absent OK -default_endpoint_settings.no_suspension false->true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true->false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -display_name absent->x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name absent->y OK -display_name x->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name x->y OK -display_name y->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name y->x OK -enable_pg_native_login absent->false SUPPRESSED empty -enable_pg_native_login absent->true OK -enable_pg_native_login false->absent SUPPRESSED empty -enable_pg_native_login false->true OK -enable_pg_native_login true->absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... -enable_pg_native_login true->false OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -pg_version 16->17 OK_RECREATE -pg_version 16->absent OK_RECREATE -pg_version 17->16 OK_RECREATE -pg_version 17->absent OK_RECREATE -pg_version absent->16 OK_RECREATE -pg_version absent->17 OK_RECREATE -project_id pgproj1->pgproj2 OK_RECREATE -project_id pgproj2->pgproj1 OK_RECREATE -purge_on_delete absent->false SUPPRESSED empty -purge_on_delete absent->true OK -purge_on_delete false->absent SUPPRESSED empty -purge_on_delete false->true OK -purge_on_delete true->absent OK -purge_on_delete true->false OK - -=== summary -OK 24 -OK_RECREATE 8 -SUPPRESSED 4 -NOT_OBSERVABLE 6 -BACKEND_ERROR 15 -BASE_ERROR 5 - -=== not covered: 7 fields under a slice or map -custom_tags[*].key -custom_tags[*].value -default_endpoint_settings.pg_settings.* -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name +budget_policy_id absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +default_branch absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... +default_branch x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... +default_branch y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... +default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_max_cu absent 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_min_cu absent 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... diff --git a/bundle/direct/tests/output/postgres_synced_tables.full.txt b/bundle/direct/tests/output/postgres_synced_tables.full.txt new file mode 100644 index 00000000000..d023b3ac993 --- /dev/null +++ b/bundle/direct/tests/output/postgres_synced_tables.full.txt @@ -0,0 +1,96 @@ +=== postgres_synced_table.yml.tmpl +accelerated_sync absent false SUPPRESSED empty +accelerated_sync absent true OK_RECREATE +accelerated_sync false absent SUPPRESSED empty +accelerated_sync false true OK_RECREATE +accelerated_sync true absent OK_RECREATE +accelerated_sync true false OK_RECREATE +branch absent x OK_RECREATE +branch absent y OK_RECREATE +branch x absent OK_RECREATE +branch x y OK_RECREATE +branch y absent OK_RECREATE +branch y x OK_RECREATE +create_database_objects_if_missing absent false SUPPRESSED empty +create_database_objects_if_missing absent true OK_RECREATE +create_database_objects_if_missing false absent SUPPRESSED empty +create_database_objects_if_missing false true OK_RECREATE +create_database_objects_if_missing true absent OK_RECREATE +create_database_objects_if_missing true false OK_RECREATE +existing_pipeline_id absent x OK_RECREATE +existing_pipeline_id absent y OK_RECREATE +existing_pipeline_id x absent OK_RECREATE +existing_pipeline_id x y OK_RECREATE +existing_pipeline_id y absent OK_RECREATE +existing_pipeline_id y x OK_RECREATE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +new_pipeline_spec.budget_policy_id absent x OK_RECREATE +new_pipeline_spec.budget_policy_id absent y OK_RECREATE +new_pipeline_spec.budget_policy_id x absent OK_RECREATE +new_pipeline_spec.budget_policy_id x y OK_RECREATE +new_pipeline_spec.budget_policy_id y absent OK_RECREATE +new_pipeline_spec.budget_policy_id y x OK_RECREATE +new_pipeline_spec.pipeline_channel absent x OK_RECREATE +new_pipeline_spec.pipeline_channel absent y OK_RECREATE +new_pipeline_spec.pipeline_channel x absent OK_RECREATE +new_pipeline_spec.pipeline_channel x y OK_RECREATE +new_pipeline_spec.pipeline_channel y absent OK_RECREATE +new_pipeline_spec.pipeline_channel y x OK_RECREATE +new_pipeline_spec.storage_catalog absent x OK_RECREATE +new_pipeline_spec.storage_catalog absent y OK_RECREATE +new_pipeline_spec.storage_catalog x absent OK_RECREATE +new_pipeline_spec.storage_catalog x y OK_RECREATE +new_pipeline_spec.storage_catalog y absent OK_RECREATE +new_pipeline_spec.storage_catalog y x OK_RECREATE +new_pipeline_spec.storage_schema absent x OK_RECREATE +new_pipeline_spec.storage_schema absent y OK_RECREATE +new_pipeline_spec.storage_schema x absent OK_RECREATE +new_pipeline_spec.storage_schema x y OK_RECREATE +new_pipeline_spec.storage_schema y absent OK_RECREATE +new_pipeline_spec.storage_schema y x OK_RECREATE +postgres_database absent x OK_RECREATE +postgres_database absent y OK_RECREATE +postgres_database x absent OK_RECREATE +postgres_database x y OK_RECREATE +postgres_database y absent OK_RECREATE +postgres_database y x OK_RECREATE +scheduling_policy absent x OK_RECREATE +scheduling_policy absent y OK_RECREATE +scheduling_policy x absent OK_RECREATE +scheduling_policy x y OK_RECREATE +scheduling_policy y absent OK_RECREATE +scheduling_policy y x OK_RECREATE +source_table_full_name absent x OK_RECREATE +source_table_full_name absent y OK_RECREATE +source_table_full_name x absent OK_RECREATE +source_table_full_name x y OK_RECREATE +source_table_full_name y absent OK_RECREATE +source_table_full_name y x OK_RECREATE +synced_table_id x y OK_RECREATE +synced_table_id y x OK_RECREATE +timeseries_key absent x OK_RECREATE +timeseries_key absent y OK_RECREATE +timeseries_key x absent OK_RECREATE +timeseries_key x y OK_RECREATE +timeseries_key y absent OK_RECREATE +timeseries_key y x OK_RECREATE + +=== summary +OK_RECREATE 70 +SUPPRESSED 4 +NOT_OBSERVABLE 6 + +=== not covered: 8 fields under a slice or map +extra_columns[*].column_name +extra_columns[*].column_type +extra_columns[*].compute +extra_columns[*].maintenance +primary_key_columns[*] +type_overrides[*].column_name +type_overrides[*].pg_type +type_overrides[*].size diff --git a/bundle/direct/tests/output/postgres_synced_tables.txt b/bundle/direct/tests/output/postgres_synced_tables.txt index d58581a9a53..e69de29bb2d 100644 --- a/bundle/direct/tests/output/postgres_synced_tables.txt +++ b/bundle/direct/tests/output/postgres_synced_tables.txt @@ -1,96 +0,0 @@ -=== postgres_synced_table.yml.tmpl -accelerated_sync absent->false SUPPRESSED empty -accelerated_sync absent->true OK_RECREATE -accelerated_sync false->absent SUPPRESSED empty -accelerated_sync false->true OK_RECREATE -accelerated_sync true->absent OK_RECREATE -accelerated_sync true->false OK_RECREATE -branch absent->x OK_RECREATE -branch absent->y OK_RECREATE -branch x->absent OK_RECREATE -branch x->y OK_RECREATE -branch y->absent OK_RECREATE -branch y->x OK_RECREATE -create_database_objects_if_missing absent->false SUPPRESSED empty -create_database_objects_if_missing absent->true OK_RECREATE -create_database_objects_if_missing false->absent SUPPRESSED empty -create_database_objects_if_missing false->true OK_RECREATE -create_database_objects_if_missing true->absent OK_RECREATE -create_database_objects_if_missing true->false OK_RECREATE -existing_pipeline_id absent->x OK_RECREATE -existing_pipeline_id absent->y OK_RECREATE -existing_pipeline_id x->absent OK_RECREATE -existing_pipeline_id x->y OK_RECREATE -existing_pipeline_id y->absent OK_RECREATE -existing_pipeline_id y->x OK_RECREATE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -new_pipeline_spec.budget_policy_id absent->x OK_RECREATE -new_pipeline_spec.budget_policy_id absent->y OK_RECREATE -new_pipeline_spec.budget_policy_id x->absent OK_RECREATE -new_pipeline_spec.budget_policy_id x->y OK_RECREATE -new_pipeline_spec.budget_policy_id y->absent OK_RECREATE -new_pipeline_spec.budget_policy_id y->x OK_RECREATE -new_pipeline_spec.pipeline_channel absent->x OK_RECREATE -new_pipeline_spec.pipeline_channel absent->y OK_RECREATE -new_pipeline_spec.pipeline_channel x->absent OK_RECREATE -new_pipeline_spec.pipeline_channel x->y OK_RECREATE -new_pipeline_spec.pipeline_channel y->absent OK_RECREATE -new_pipeline_spec.pipeline_channel y->x OK_RECREATE -new_pipeline_spec.storage_catalog absent->x OK_RECREATE -new_pipeline_spec.storage_catalog absent->y OK_RECREATE -new_pipeline_spec.storage_catalog x->absent OK_RECREATE -new_pipeline_spec.storage_catalog x->y OK_RECREATE -new_pipeline_spec.storage_catalog y->absent OK_RECREATE -new_pipeline_spec.storage_catalog y->x OK_RECREATE -new_pipeline_spec.storage_schema absent->x OK_RECREATE -new_pipeline_spec.storage_schema absent->y OK_RECREATE -new_pipeline_spec.storage_schema x->absent OK_RECREATE -new_pipeline_spec.storage_schema x->y OK_RECREATE -new_pipeline_spec.storage_schema y->absent OK_RECREATE -new_pipeline_spec.storage_schema y->x OK_RECREATE -postgres_database absent->x OK_RECREATE -postgres_database absent->y OK_RECREATE -postgres_database x->absent OK_RECREATE -postgres_database x->y OK_RECREATE -postgres_database y->absent OK_RECREATE -postgres_database y->x OK_RECREATE -scheduling_policy absent->x OK_RECREATE -scheduling_policy absent->y OK_RECREATE -scheduling_policy x->absent OK_RECREATE -scheduling_policy x->y OK_RECREATE -scheduling_policy y->absent OK_RECREATE -scheduling_policy y->x OK_RECREATE -source_table_full_name absent->x OK_RECREATE -source_table_full_name absent->y OK_RECREATE -source_table_full_name x->absent OK_RECREATE -source_table_full_name x->y OK_RECREATE -source_table_full_name y->absent OK_RECREATE -source_table_full_name y->x OK_RECREATE -synced_table_id x->y OK_RECREATE -synced_table_id y->x OK_RECREATE -timeseries_key absent->x OK_RECREATE -timeseries_key absent->y OK_RECREATE -timeseries_key x->absent OK_RECREATE -timeseries_key x->y OK_RECREATE -timeseries_key y->absent OK_RECREATE -timeseries_key y->x OK_RECREATE - -=== summary -OK_RECREATE 70 -SUPPRESSED 4 -NOT_OBSERVABLE 6 - -=== not covered: 8 fields under a slice or map -extra_columns[*].column_name -extra_columns[*].column_type -extra_columns[*].compute -extra_columns[*].maintenance -primary_key_columns[*] -type_overrides[*].column_name -type_overrides[*].pg_type -type_overrides[*].size diff --git a/bundle/direct/tests/output/registered_models.full.txt b/bundle/direct/tests/output/registered_models.full.txt new file mode 100644 index 00000000000..41d7ac99566 --- /dev/null +++ b/bundle/direct/tests/output/registered_models.full.txt @@ -0,0 +1,102 @@ +=== registered_model.yml.tmpl +browse_only absent false SUPPRESSED empty +browse_only absent true OK +browse_only false absent SUPPRESSED empty +browse_only false true OK +browse_only true absent OK +browse_only true false OK +catalog_name absent x OK_RECREATE +catalog_name absent y OK_RECREATE +catalog_name x absent OK_RECREATE +catalog_name x y OK_RECREATE +catalog_name y absent OK_RECREATE +catalog_name y x OK_RECREATE +comment absent x OK +comment absent y OK +comment x absent OK +comment x y OK +comment y absent OK +comment y x OK +created_at 1 2 OK +created_at 1 absent OK +created_at 2 1 OK +created_at 2 absent OK +created_at absent 1 OK +created_at absent 2 OK +created_by absent x OK +created_by absent y OK +created_by x absent OK +created_by x y OK +created_by y absent OK +created_by y x OK +full_name absent x POST_DEPLOY_DRIFT full_name +full_name absent y POST_DEPLOY_DRIFT full_name +full_name x absent OK +full_name x y POST_DEPLOY_DRIFT full_name +full_name y absent OK +full_name y x POST_DEPLOY_DRIFT full_name +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +metastore_id absent x OK +metastore_id absent y OK +metastore_id x absent OK +metastore_id x y OK +metastore_id y absent OK +metastore_id y x OK +name absent x OK_RECREATE +name absent y OK_RECREATE +name x absent OK_RECREATE +name x y OK_RECREATE +name y absent OK_RECREATE +name y x OK_RECREATE +owner absent x POST_DEPLOY_DRIFT owner +owner absent y POST_DEPLOY_DRIFT owner +owner x absent OK +owner x y POST_DEPLOY_DRIFT owner +owner y absent OK +owner y x POST_DEPLOY_DRIFT owner +schema_name absent x OK_RECREATE +schema_name absent y OK_RECREATE +schema_name x absent OK_RECREATE +schema_name x y OK_RECREATE +schema_name y absent OK_RECREATE +schema_name y x OK_RECREATE +storage_location absent x OK_RECREATE +storage_location absent y OK_RECREATE +storage_location x absent OK_RECREATE +storage_location x y OK_RECREATE +storage_location y absent OK_RECREATE +storage_location y x OK_RECREATE +updated_at 1 2 OK +updated_at 1 absent OK +updated_at 2 1 OK +updated_at 2 absent OK +updated_at absent 1 OK +updated_at absent 2 OK +updated_by absent x OK +updated_by absent y OK +updated_by x absent OK +updated_by x y OK +updated_by y absent OK +updated_by y x OK + +=== summary +OK 44 +OK_RECREATE 24 +SUPPRESSED 2 +NOT_OBSERVABLE 6 +POST_DEPLOY_DRIFT 8 + +=== not covered: 8 fields under a slice or map +aliases[*].alias_name +aliases[*].catalog_name +aliases[*].id +aliases[*].model_name +aliases[*].schema_name +aliases[*].version_num +grants[*].principal +grants[*].privileges[*] diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index d8f4e1d717f..fa73ab0a15f 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,102 +1,9 @@ === registered_model.yml.tmpl -browse_only absent->false SUPPRESSED empty -browse_only absent->true OK -browse_only false->absent SUPPRESSED empty -browse_only false->true OK -browse_only true->absent OK -browse_only true->false OK -catalog_name absent->x OK_RECREATE -catalog_name absent->y OK_RECREATE -catalog_name x->absent OK_RECREATE -catalog_name x->y OK_RECREATE -catalog_name y->absent OK_RECREATE -catalog_name y->x OK_RECREATE -comment absent->x OK -comment absent->y OK -comment x->absent OK -comment x->y OK -comment y->absent OK -comment y->x OK -created_at 1->2 OK -created_at 1->absent OK -created_at 2->1 OK -created_at 2->absent OK -created_at absent->1 OK -created_at absent->2 OK -created_by absent->x OK -created_by absent->y OK -created_by x->absent OK -created_by x->y OK -created_by y->absent OK -created_by y->x OK -full_name absent->x DRIFT full_name -full_name absent->y DRIFT full_name -full_name x->absent OK -full_name x->y DRIFT full_name -full_name y->absent OK -full_name y->x DRIFT full_name -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -metastore_id absent->x OK -metastore_id absent->y OK -metastore_id x->absent OK -metastore_id x->y OK -metastore_id y->absent OK -metastore_id y->x OK -name absent->x OK_RECREATE -name absent->y OK_RECREATE -name x->absent OK_RECREATE -name x->y OK_RECREATE -name y->absent OK_RECREATE -name y->x OK_RECREATE -owner absent->x DRIFT owner -owner absent->y DRIFT owner -owner x->absent OK -owner x->y DRIFT owner -owner y->absent OK -owner y->x DRIFT owner -schema_name absent->x OK_RECREATE -schema_name absent->y OK_RECREATE -schema_name x->absent OK_RECREATE -schema_name x->y OK_RECREATE -schema_name y->absent OK_RECREATE -schema_name y->x OK_RECREATE -storage_location absent->x OK_RECREATE -storage_location absent->y OK_RECREATE -storage_location x->absent OK_RECREATE -storage_location x->y OK_RECREATE -storage_location y->absent OK_RECREATE -storage_location y->x OK_RECREATE -updated_at 1->2 OK -updated_at 1->absent OK -updated_at 2->1 OK -updated_at 2->absent OK -updated_at absent->1 OK -updated_at absent->2 OK -updated_by absent->x OK -updated_by absent->y OK -updated_by x->absent OK -updated_by x->y OK -updated_by y->absent OK -updated_by y->x OK - -=== summary -OK 44 -OK_RECREATE 24 -SUPPRESSED 2 -NOT_OBSERVABLE 6 -DRIFT 8 - -=== not covered: 8 fields under a slice or map -aliases[*].alias_name -aliases[*].catalog_name -aliases[*].id -aliases[*].model_name -aliases[*].schema_name -aliases[*].version_num -grants[*].principal -grants[*].privileges[*] +full_name absent x POST_DEPLOY_DRIFT full_name +full_name absent y POST_DEPLOY_DRIFT full_name +full_name x y POST_DEPLOY_DRIFT full_name +full_name y x POST_DEPLOY_DRIFT full_name +owner absent x POST_DEPLOY_DRIFT owner +owner absent y POST_DEPLOY_DRIFT owner +owner x y POST_DEPLOY_DRIFT owner +owner y x POST_DEPLOY_DRIFT owner diff --git a/bundle/direct/tests/output/schemas.full.txt b/bundle/direct/tests/output/schemas.full.txt new file mode 100644 index 00000000000..ed9615e586b --- /dev/null +++ b/bundle/direct/tests/output/schemas.full.txt @@ -0,0 +1,80 @@ +=== schema.yml.tmpl +comment absent x OK +comment absent y OK +comment x absent OK +comment x y OK +comment y absent OK +comment y x OK +custom_max_retention_hours 1 2 OK +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 1 OK +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours absent 1 OK +custom_max_retention_hours absent 2 OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK_RECREATE +name y x OK_RECREATE +storage_root SKIPPED needs an external location + +=== schema_empty_grants.yml.tmpl +comment absent x OK +comment absent y OK +comment x absent OK +comment x y OK +comment y absent OK +comment y x OK +custom_max_retention_hours 1 2 OK +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 1 OK +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours absent 1 OK +custom_max_retention_hours absent 2 OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK_RECREATE +name y x OK_RECREATE +storage_root SKIPPED needs an external location + +=== schema_uppercase_name.yml.tmpl +comment absent x OK +comment absent y OK +comment x absent OK +comment x y OK +comment y absent OK +comment y x OK +custom_max_retention_hours 1 2 OK +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 1 OK +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours absent 1 OK +custom_max_retention_hours absent 2 OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK_RECREATE +name y x OK_RECREATE +storage_root SKIPPED needs an external location + +=== summary +OK 30 +OK_RECREATE 6 +NOT_OBSERVABLE 18 +POST_DEPLOY_DRIFT 6 +SKIPPED 3 + +=== not covered: 3 fields under a slice or map +grants[*].principal +grants[*].privileges[*] +properties.* diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index ab091a892b6..dd6de49f92d 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -1,80 +1,11 @@ === schema.yml.tmpl -comment absent->x OK -comment absent->y OK -comment x->absent OK -comment x->y OK -comment y->absent OK -comment y->x OK -custom_max_retention_hours 1->2 OK -custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours -custom_max_retention_hours 2->1 OK -custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours -custom_max_retention_hours absent->1 OK -custom_max_retention_hours absent->2 OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK_RECREATE -name y->x OK_RECREATE -storage_root - SKIPPED needs an external location +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours === schema_empty_grants.yml.tmpl -comment absent->x OK -comment absent->y OK -comment x->absent OK -comment x->y OK -comment y->absent OK -comment y->x OK -custom_max_retention_hours 1->2 OK -custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours -custom_max_retention_hours 2->1 OK -custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours -custom_max_retention_hours absent->1 OK -custom_max_retention_hours absent->2 OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK_RECREATE -name y->x OK_RECREATE -storage_root - SKIPPED needs an external location +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours === schema_uppercase_name.yml.tmpl -comment absent->x OK -comment absent->y OK -comment x->absent OK -comment x->y OK -comment y->absent OK -comment y->x OK -custom_max_retention_hours 1->2 OK -custom_max_retention_hours 1->absent DRIFT custom_max_retention_hours -custom_max_retention_hours 2->1 OK -custom_max_retention_hours 2->absent DRIFT custom_max_retention_hours -custom_max_retention_hours absent->1 OK -custom_max_retention_hours absent->2 OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK_RECREATE -name y->x OK_RECREATE -storage_root - SKIPPED needs an external location - -=== summary -OK 30 -OK_RECREATE 6 -NOT_OBSERVABLE 18 -DRIFT 6 -SKIPPED 3 - -=== not covered: 3 fields under a slice or map -grants[*].principal -grants[*].privileges[*] -properties.* +custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours diff --git a/bundle/direct/tests/output/secret_scopes.full.txt b/bundle/direct/tests/output/secret_scopes.full.txt new file mode 100644 index 00000000000..60b81a6f21e --- /dev/null +++ b/bundle/direct/tests/output/secret_scopes.full.txt @@ -0,0 +1,49 @@ +=== secret_scope.yml.tmpl +backend_type absent x OK_RECREATE +backend_type absent y OK_RECREATE +backend_type x absent OK_RECREATE +backend_type x y OK_RECREATE +backend_type y absent OK_RECREATE +backend_type y x OK_RECREATE +keyvault_metadata.dns_name x y OK_RECREATE +keyvault_metadata.dns_name y x OK_RECREATE +keyvault_metadata.resource_id x y OK_RECREATE +keyvault_metadata.resource_id y x OK_RECREATE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK_RECREATE +name y x OK_RECREATE + +=== secret_scope_default_backend_type.yml.tmpl +backend_type absent x OK_RECREATE +backend_type absent y OK_RECREATE +backend_type x absent OK_RECREATE +backend_type x y OK_RECREATE +backend_type y absent OK_RECREATE +backend_type y x OK_RECREATE +keyvault_metadata.dns_name x y OK_RECREATE +keyvault_metadata.dns_name y x OK_RECREATE +keyvault_metadata.resource_id x y OK_RECREATE +keyvault_metadata.resource_id y x OK_RECREATE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK_RECREATE +name y x OK_RECREATE + +=== summary +OK_RECREATE 24 +NOT_OBSERVABLE 12 + +=== not covered: 4 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name diff --git a/bundle/direct/tests/output/secret_scopes.txt b/bundle/direct/tests/output/secret_scopes.txt index 3bf71901e54..e69de29bb2d 100644 --- a/bundle/direct/tests/output/secret_scopes.txt +++ b/bundle/direct/tests/output/secret_scopes.txt @@ -1,49 +0,0 @@ -=== secret_scope.yml.tmpl -backend_type absent->x OK_RECREATE -backend_type absent->y OK_RECREATE -backend_type x->absent OK_RECREATE -backend_type x->y OK_RECREATE -backend_type y->absent OK_RECREATE -backend_type y->x OK_RECREATE -keyvault_metadata.dns_name x->y OK_RECREATE -keyvault_metadata.dns_name y->x OK_RECREATE -keyvault_metadata.resource_id x->y OK_RECREATE -keyvault_metadata.resource_id y->x OK_RECREATE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK_RECREATE -name y->x OK_RECREATE - -=== secret_scope_default_backend_type.yml.tmpl -backend_type absent->x OK_RECREATE -backend_type absent->y OK_RECREATE -backend_type x->absent OK_RECREATE -backend_type x->y OK_RECREATE -backend_type y->absent OK_RECREATE -backend_type y->x OK_RECREATE -keyvault_metadata.dns_name x->y OK_RECREATE -keyvault_metadata.dns_name y->x OK_RECREATE -keyvault_metadata.resource_id x->y OK_RECREATE -keyvault_metadata.resource_id y->x OK_RECREATE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK_RECREATE -name y->x OK_RECREATE - -=== summary -OK_RECREATE 24 -NOT_OBSERVABLE 12 - -=== not covered: 4 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name diff --git a/bundle/direct/tests/output/sql_warehouses.full.txt b/bundle/direct/tests/output/sql_warehouses.full.txt new file mode 100644 index 00000000000..97f038d50ea --- /dev/null +++ b/bundle/direct/tests/output/sql_warehouses.full.txt @@ -0,0 +1,111 @@ +=== sql_warehouse.yml.tmpl +auto_stop_mins 1 2 OK +auto_stop_mins 1 absent OK +auto_stop_mins 2 1 OK +auto_stop_mins 2 absent OK +auto_stop_mins absent 1 OK +auto_stop_mins absent 2 OK +channel.dbsql_version absent x OK +channel.dbsql_version absent y OK +channel.dbsql_version x absent OK +channel.dbsql_version x y OK +channel.dbsql_version y absent OK +channel.dbsql_version y x OK +channel.name absent x OK +channel.name absent y OK +channel.name x absent OK +channel.name x y OK +channel.name y absent OK +channel.name y x OK +cluster_size absent x OK +cluster_size absent y OK +cluster_size x absent OK +cluster_size x y OK +cluster_size y absent OK +cluster_size y x OK +creator_name absent x OK +creator_name absent y OK +creator_name x absent OK +creator_name x y OK +creator_name y absent OK +creator_name y x OK +enable_photon absent false SUPPRESSED empty +enable_photon absent true OK +enable_photon false absent SUPPRESSED empty +enable_photon false true OK +enable_photon true absent OK +enable_photon true false OK +enable_serverless_compute absent false SUPPRESSED empty +enable_serverless_compute absent true OK +enable_serverless_compute false absent SUPPRESSED empty +enable_serverless_compute false true OK +enable_serverless_compute true absent OK +enable_serverless_compute true false OK +instance_profile_arn absent x OK +instance_profile_arn absent y OK +instance_profile_arn x absent OK +instance_profile_arn x y OK +instance_profile_arn y absent OK +instance_profile_arn y x OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +lifecycle.started absent false OK +lifecycle.started absent true OK +lifecycle.started false absent OK +lifecycle.started false true OK +lifecycle.started true absent OK +lifecycle.started true false OK +max_num_clusters 1 2 OK +max_num_clusters 1 absent OK +max_num_clusters 2 1 OK +max_num_clusters 2 absent OK +max_num_clusters absent 1 OK +max_num_clusters absent 2 OK +min_num_clusters 1 2 OK +min_num_clusters 1 absent OK +min_num_clusters 2 1 OK +min_num_clusters 2 absent OK +min_num_clusters absent 1 OK +min_num_clusters absent 2 OK +name absent x OK +name absent y OK +name x absent POST_DEPLOY_DRIFT name +name x y OK +name y absent POST_DEPLOY_DRIFT name +name y x OK +spot_instance_policy absent x OK +spot_instance_policy absent y OK +spot_instance_policy x absent OK +spot_instance_policy x y OK +spot_instance_policy y absent OK +spot_instance_policy y x OK +warehouse_type absent x OK +warehouse_type absent y OK +warehouse_type x absent OK +warehouse_type x y OK +warehouse_type y absent OK +warehouse_type y x OK + +=== summary +OK 78 +SUPPRESSED 4 +NOT_OBSERVABLE 12 +POST_DEPLOY_DRIFT 2 + +=== not covered: 6 fields under a slice or map +permissions[*].group_name +permissions[*].level +permissions[*].service_principal_name +permissions[*].user_name +tags.custom_tags[*].key +tags.custom_tags[*].value diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index e63cf93d130..70a9c6c9e5f 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -1,111 +1,3 @@ === sql_warehouse.yml.tmpl -auto_stop_mins 1->2 OK -auto_stop_mins 1->absent OK -auto_stop_mins 2->1 OK -auto_stop_mins 2->absent OK -auto_stop_mins absent->1 OK -auto_stop_mins absent->2 OK -channel.dbsql_version absent->x OK -channel.dbsql_version absent->y OK -channel.dbsql_version x->absent OK -channel.dbsql_version x->y OK -channel.dbsql_version y->absent OK -channel.dbsql_version y->x OK -channel.name absent->x OK -channel.name absent->y OK -channel.name x->absent OK -channel.name x->y OK -channel.name y->absent OK -channel.name y->x OK -cluster_size absent->x OK -cluster_size absent->y OK -cluster_size x->absent OK -cluster_size x->y OK -cluster_size y->absent OK -cluster_size y->x OK -creator_name absent->x OK -creator_name absent->y OK -creator_name x->absent OK -creator_name x->y OK -creator_name y->absent OK -creator_name y->x OK -enable_photon absent->false SUPPRESSED empty -enable_photon absent->true OK -enable_photon false->absent SUPPRESSED empty -enable_photon false->true OK -enable_photon true->absent OK -enable_photon true->false OK -enable_serverless_compute absent->false SUPPRESSED empty -enable_serverless_compute absent->true OK -enable_serverless_compute false->absent SUPPRESSED empty -enable_serverless_compute false->true OK -enable_serverless_compute true->absent OK -enable_serverless_compute true->false OK -instance_profile_arn absent->x OK -instance_profile_arn absent->y OK -instance_profile_arn x->absent OK -instance_profile_arn x->y OK -instance_profile_arn y->absent OK -instance_profile_arn y->x OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -lifecycle.started absent->false OK -lifecycle.started absent->true OK -lifecycle.started false->absent OK -lifecycle.started false->true OK -lifecycle.started true->absent OK -lifecycle.started true->false OK -max_num_clusters 1->2 OK -max_num_clusters 1->absent OK -max_num_clusters 2->1 OK -max_num_clusters 2->absent OK -max_num_clusters absent->1 OK -max_num_clusters absent->2 OK -min_num_clusters 1->2 OK -min_num_clusters 1->absent OK -min_num_clusters 2->1 OK -min_num_clusters 2->absent OK -min_num_clusters absent->1 OK -min_num_clusters absent->2 OK -name absent->x OK -name absent->y OK -name x->absent DRIFT name -name x->y OK -name y->absent DRIFT name -name y->x OK -spot_instance_policy absent->x OK -spot_instance_policy absent->y OK -spot_instance_policy x->absent OK -spot_instance_policy x->y OK -spot_instance_policy y->absent OK -spot_instance_policy y->x OK -warehouse_type absent->x OK -warehouse_type absent->y OK -warehouse_type x->absent OK -warehouse_type x->y OK -warehouse_type y->absent OK -warehouse_type y->x OK - -=== summary -OK 78 -SUPPRESSED 4 -NOT_OBSERVABLE 12 -DRIFT 2 - -=== not covered: 6 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -tags.custom_tags[*].key -tags.custom_tags[*].value +name x absent POST_DEPLOY_DRIFT name +name y absent POST_DEPLOY_DRIFT name diff --git a/bundle/direct/tests/output/volumes.full.txt b/bundle/direct/tests/output/volumes.full.txt new file mode 100644 index 00000000000..6ff0a0b407d --- /dev/null +++ b/bundle/direct/tests/output/volumes.full.txt @@ -0,0 +1,51 @@ +=== volume.yml.tmpl +catalog_name x y OK_RECREATE +catalog_name y x OK_RECREATE +comment absent x OK +comment absent y OK +comment x absent OK +comment x y OK +comment y absent OK +comment y x OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK +name y x OK +schema_name x y OK_RECREATE +schema_name y x OK_RECREATE +storage_location SKIPPED needs an external location + +=== volume_external.yml.tmpl +catalog_name x y OK_RECREATE +catalog_name y x OK_RECREATE +comment absent x OK +comment absent y OK +comment x absent OK +comment x y OK +comment y absent OK +comment y x OK +lifecycle.prevent_destroy absent false NOT_OBSERVABLE +lifecycle.prevent_destroy absent true NOT_OBSERVABLE +lifecycle.prevent_destroy false absent NOT_OBSERVABLE +lifecycle.prevent_destroy false true NOT_OBSERVABLE +lifecycle.prevent_destroy true absent NOT_OBSERVABLE +lifecycle.prevent_destroy true false NOT_OBSERVABLE +name x y OK +name y x OK +schema_name x y OK_RECREATE +schema_name y x OK_RECREATE +storage_location SKIPPED needs an external location + +=== summary +OK 16 +OK_RECREATE 8 +NOT_OBSERVABLE 12 +SKIPPED 2 + +=== not covered: 2 fields under a slice or map +grants[*].principal +grants[*].privileges[*] diff --git a/bundle/direct/tests/output/volumes.txt b/bundle/direct/tests/output/volumes.txt index 26d4252283b..e69de29bb2d 100644 --- a/bundle/direct/tests/output/volumes.txt +++ b/bundle/direct/tests/output/volumes.txt @@ -1,51 +0,0 @@ -=== volume.yml.tmpl -catalog_name x->y OK_RECREATE -catalog_name y->x OK_RECREATE -comment absent->x OK -comment absent->y OK -comment x->absent OK -comment x->y OK -comment y->absent OK -comment y->x OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK -name y->x OK -schema_name x->y OK_RECREATE -schema_name y->x OK_RECREATE -storage_location - SKIPPED needs an external location - -=== volume_external.yml.tmpl -catalog_name x->y OK_RECREATE -catalog_name y->x OK_RECREATE -comment absent->x OK -comment absent->y OK -comment x->absent OK -comment x->y OK -comment y->absent OK -comment y->x OK -lifecycle.prevent_destroy absent->false NOT_OBSERVABLE -lifecycle.prevent_destroy absent->true NOT_OBSERVABLE -lifecycle.prevent_destroy false->absent NOT_OBSERVABLE -lifecycle.prevent_destroy false->true NOT_OBSERVABLE -lifecycle.prevent_destroy true->absent NOT_OBSERVABLE -lifecycle.prevent_destroy true->false NOT_OBSERVABLE -name x->y OK -name y->x OK -schema_name x->y OK_RECREATE -schema_name y->x OK_RECREATE -storage_location - SKIPPED needs an external location - -=== summary -OK 16 -OK_RECREATE 8 -NOT_OBSERVABLE 12 -SKIPPED 2 - -=== not covered: 2 fields under a slice or map -grants[*].principal -grants[*].privileges[*] diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index c75fbe65f9d..03050835a6f 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -34,19 +34,22 @@ const ( verdictNotObservable verdict = "NOT_OBSERVABLE" // The config changed, the field diff exists, but the plan produced no action at all. verdictNoPlan verdict = "NO_PLAN" - // The apply succeeded but a later plan still proposes the same change to this - // field, so deploying never converges. - verdictDrift verdict = "DRIFT" - // The field itself converged, but some other node of the resource is still drifted. - // Sub-resources are stripped before planning, so nothing should reach this: it is a - // guard against silently blaming a field for drift that is not its own. - verdictDriftChild verdict = "DRIFT_CHILD" + // The apply succeeded, but the plan taken straight afterwards still proposes the same + // change to this field, so deploying never converges. + verdictDrift verdict = "POST_DEPLOY_DRIFT" + // The field itself converged, but some other node of the resource is still drifted + // post-deploy. Sub-resources are stripped before planning, so nothing should reach + // this: it guards against blaming a field for drift that is not its own. + verdictDriftChild verdict = "POST_DEPLOY_DRIFT_CHILD" // The backend rejected the value. Usually the value library needs a valid // value for this field rather than the generic per-kind default. verdictBackendError verdict = "BACKEND_ERROR" // Apply failed for a reason that is not an API rejection. verdictDeployError verdict = "DEPLOY_ERROR" + // The operation did not finish inside the per-operation deadline. Recorded rather + // than waited out: an app rename, for one, blocks on the old name leaving DELETING. + verdictTimeout verdict = "TIMEOUT" // Planning itself failed. verdictPlanError verdict = "PLAN_ERROR" @@ -76,13 +79,31 @@ func (v verdict) leavesResourceUsable() bool { } } +// reasonEmpty is the planner's own reason for dropping a change whose old and new values +// are both empty -- an unset bool against an explicit false, say. Nothing the user +// expressed is lost, so it reads like OK and belongs in the full report only. +const reasonEmpty = "empty" + +// isProblem reports whether a result is something a person should look at. The committed +// report lists only these; the .full.txt companion lists everything. +func (r result) isProblem() bool { + switch r.verdict { + case verdictOK, verdictRecreate, verdictNotObservable, verdictSkipped: + return false + case verdictSuppressed: + return r.detail != reasonEmpty + default: + return true + } +} + // result is one line of the report. type result struct { - config string - field string - transition string - verdict verdict - detail string + config string + field string + from, to string + verdict verdict + detail string } // report accumulates results for one resource type. @@ -94,22 +115,28 @@ type report struct { func (r *report) add(res result) { r.results = append(r.results, res) } -// render produces the golden body: one line per transition, grouped by config and -// field, plus a trailing summary of what was not covered. -func (r *report) render() string { +// render produces a report body. Problems-only is the committed form: one line per +// finding and nothing else, so a diff is always a change in behaviour. The full form +// adds the passing rows, the summary, and what was not covered. +func (r *report) render(problemsOnly bool) string { var sb strings.Builder slices.SortStableFunc(r.results, func(a, b result) int { return cmp.Or( strings.Compare(a.config, b.config), strings.Compare(a.field, b.field), - strings.Compare(a.transition, b.transition), + strings.Compare(a.from, b.from), + strings.Compare(a.to, b.to), ) }) counts := map[verdict]int{} config := "" for _, res := range r.results { + counts[res.verdict]++ + if problemsOnly && !res.isProblem() { + continue + } if res.config != config { if config != "" { sb.WriteString("\n") @@ -117,19 +144,22 @@ func (r *report) render() string { fmt.Fprintf(&sb, "=== %s\n", res.config) config = res.config } - line := fmt.Sprintf("%-44s %-18s %s", res.field, res.transition, res.verdict) + line := fmt.Sprintf("%-44s %-10s %-10s %s", res.field, res.from, res.to, res.verdict) if res.detail != "" { line += " " + res.detail } sb.WriteString(strings.TrimRight(line, " ") + "\n") - counts[res.verdict]++ + } + + if problemsOnly { + return sb.String() } sb.WriteString("\n=== summary\n") for _, v := range []verdict{ verdictOK, verdictRecreate, verdictSuppressed, verdictNotObservable, verdictNoPlan, verdictDrift, verdictDriftChild, - verdictBackendError, verdictDeployError, verdictPlanError, + verdictBackendError, verdictDeployError, verdictTimeout, verdictPlanError, verdictUnsettable, verdictSkipped, verdictBaseError, } { if counts[v] > 0 { @@ -152,11 +182,19 @@ func (r *report) render() string { // Cloud runs go to a separate, uncommitted file: which values a real backend accepts // depends on the workspace, so those results are for reading, not for diffing. func (r *report) write(t testutil.TestingT) { - name := reportPath(r.resourceType + ".txt") - body := r.render() + suffix := ".txt" + if isCloud() { + suffix = "." + cloudName() + ".txt" + } + name := reportPath(r.resourceType + suffix) + body := r.render(true) + + // The full form is never compared: it is a local aid for reading a run, and it moves + // whenever a passing row moves. + writeReport(t, reportPath(r.resourceType+strings.TrimSuffix(suffix, ".txt")+".full.txt"), r.render(false)) if isCloud() { - writeReport(t, reportPath(r.resourceType+"."+cloudName()+".txt"), body) + writeReport(t, name, body) return } diff --git a/bundle/direct/tests/testdata/fields/apps.yml b/bundle/direct/tests/testdata/fields/apps.yml index 51f0cd657b7..1b073246c16 100644 --- a/bundle/direct/tests/testdata/fields/apps.yml +++ b/bundle/direct/tests/testdata/fields/apps.yml @@ -1,13 +1,4 @@ # Value library for apps. See ../README.md. -# -# Every app create provisions compute, so a cloud run costs minutes per rebuild. -slow: true - -skip: - # Renaming an app recreates it, and an app delete is asynchronous: the name stays - # reserved while the old app sits in DELETING, so every rename waits out the delete. - # Recreate-on-rename is covered by acceptance/bundle/resources/apps. - name: rename waits out an asynchronous delete fields: compute_size: [MEDIUM, LARGE] diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml index ff4ec827f1d..ea98c1fe05c 100644 --- a/bundle/direct/tests/testdata/fields/jobs.yml +++ b/bundle/direct/tests/testdata/fields/jobs.yml @@ -1,7 +1,4 @@ # Value library for jobs. See ../README.md. -# jobs is the largest surface here (1300+ observations), so it is slow on cloud. -slow: true - skip: # git_source only validates as a coherent set: a provider, a url, and exactly one of # branch/tag/commit. Moving one of its fields at a time always produces an invalid diff --git a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml index 23c9a962991..16a05a73924 100644 --- a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml +++ b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml @@ -1,8 +1,4 @@ # Value library for model serving endpoints. See ../README.md. -# -# Creating an endpoint provisions serving compute. -slow: true - skip: # The telemetry block is only accepted as a coherent whole, against a catalog and # schema that exist and are writable, which one-field-at-a-time cannot arrange. diff --git a/bundle/direct/tests/testdata/fields/postgres_projects.yml b/bundle/direct/tests/testdata/fields/postgres_projects.yml index 0df79c92b25..b679eb9cd9a 100644 --- a/bundle/direct/tests/testdata/fields/postgres_projects.yml +++ b/bundle/direct/tests/testdata/fields/postgres_projects.yml @@ -1,8 +1,4 @@ # Value library for postgres projects. See ../README.md. -# -# Provisioning a Lakebase project is slow, and it is AWS-only. -slow: true - fields: # project_id is part of the resource's hierarchical name, so a change recreates it. project_id: [pgproj1, pgproj2] diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index d2861547917..c0e872f90d3 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -29,12 +29,6 @@ var absent = dyn.InvalidValue // constrains (enums, ids, cross-referenced names) need an explicit entry, otherwise // every value is rejected and the field reports only BACKEND_ERROR. type fieldValues struct { - // slow marks a resource type whose cloud run is expensive -- one that provisions - // compute, say. It still runs on cloud; it is only dropped under -short, the same - // way CloudSlow narrows acceptance tests, so PR cloud runs stay bounded and the - // nightly covers everything. - slow bool - // skip lists field paths to leave out entirely, with a reason. A key ending in ".*" // skips that whole subtree. skip map[string]string @@ -50,7 +44,7 @@ const fieldsDir = "testdata/fields" // yamlloader rather than a yaml package, so the values arrive as dyn.Value -- the same // representation the bundle config uses, which is what they are written into. func loadFieldValues(resourceType string) (*fieldValues, error) { - fv := &fieldValues{slow: false, skip: map[string]string{}, fields: map[string][]dyn.Value{}} + fv := &fieldValues{skip: map[string]string{}, fields: map[string][]dyn.Value{}} path := filepath.Join(fieldsDir, resourceType+".yml") data, err := os.ReadFile(path) @@ -66,9 +60,6 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { return nil, fmt.Errorf("%s: %w", path, err) } - if v, err := dyn.Get(root, "slow"); err == nil { - fv.slow, _ = v.AsBool() - } if v, err := dyn.Get(root, "skip"); err == nil { m, _ := v.AsMap() for _, pair := range m.Pairs() { @@ -140,8 +131,12 @@ type transition struct { from, to dyn.Value } +// label names the transition for the subtest. Kept free of shell metacharacters so a +// single case can be re-run without quoting: +// +// go test ./bundle/direct/tests -run TestFields/pipelines/.*/dry_run/absent_to_true -v func (t transition) label() string { - return valueLabel(t.from) + "->" + valueLabel(t.to) + return valueLabel(t.from) + "_to_" + valueLabel(t.to) } // transitions returns every ordered pair of the field's values. absent is one of them, From 1a6168071f153943cac47eb2d6539ac9c67cbd3d Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 12:57:06 +0200 Subject: [PATCH 010/118] Ignore the full field reports They were committed by accident: output/.txt is the file worth diffing. Co-authored-by: Isaac --- bundle/direct/tests/output/alerts.full.txt | 201 -- bundle/direct/tests/output/apps.full.txt | 568 ----- bundle/direct/tests/output/catalogs.full.txt | 155 -- .../tests/output/cluster_policies.full.txt | 64 - bundle/direct/tests/output/clusters.full.txt | 777 ------- .../direct/tests/output/dashboards.full.txt | 97 - .../tests/output/database_instances.full.txt | 186 -- .../direct/tests/output/experiments.full.txt | 44 - .../tests/output/external_locations.full.txt | 263 --- .../direct/tests/output/genie_spaces.full.txt | 55 - .../tests/output/instance_pools.full.txt | 162 -- bundle/direct/tests/output/jobs.full.txt | 2055 ----------------- .../output/model_serving_endpoints.full.txt | 334 --- bundle/direct/tests/output/models.full.txt | 28 - bundle/direct/tests/output/pipelines.full.txt | 1406 ----------- .../tests/output/postgres_projects.full.txt | 80 - .../output/postgres_synced_tables.full.txt | 96 - .../tests/output/registered_models.full.txt | 102 - bundle/direct/tests/output/schemas.full.txt | 80 - .../tests/output/secret_scopes.full.txt | 49 - .../tests/output/sql_warehouses.full.txt | 111 - bundle/direct/tests/output/volumes.full.txt | 51 - 22 files changed, 6964 deletions(-) delete mode 100644 bundle/direct/tests/output/alerts.full.txt delete mode 100644 bundle/direct/tests/output/apps.full.txt delete mode 100644 bundle/direct/tests/output/catalogs.full.txt delete mode 100644 bundle/direct/tests/output/cluster_policies.full.txt delete mode 100644 bundle/direct/tests/output/clusters.full.txt delete mode 100644 bundle/direct/tests/output/dashboards.full.txt delete mode 100644 bundle/direct/tests/output/database_instances.full.txt delete mode 100644 bundle/direct/tests/output/experiments.full.txt delete mode 100644 bundle/direct/tests/output/external_locations.full.txt delete mode 100644 bundle/direct/tests/output/genie_spaces.full.txt delete mode 100644 bundle/direct/tests/output/instance_pools.full.txt delete mode 100644 bundle/direct/tests/output/jobs.full.txt delete mode 100644 bundle/direct/tests/output/model_serving_endpoints.full.txt delete mode 100644 bundle/direct/tests/output/models.full.txt delete mode 100644 bundle/direct/tests/output/pipelines.full.txt delete mode 100644 bundle/direct/tests/output/postgres_projects.full.txt delete mode 100644 bundle/direct/tests/output/postgres_synced_tables.full.txt delete mode 100644 bundle/direct/tests/output/registered_models.full.txt delete mode 100644 bundle/direct/tests/output/schemas.full.txt delete mode 100644 bundle/direct/tests/output/secret_scopes.full.txt delete mode 100644 bundle/direct/tests/output/sql_warehouses.full.txt delete mode 100644 bundle/direct/tests/output/volumes.full.txt diff --git a/bundle/direct/tests/output/alerts.full.txt b/bundle/direct/tests/output/alerts.full.txt deleted file mode 100644 index 017fb4e1b4f..00000000000 --- a/bundle/direct/tests/output/alerts.full.txt +++ /dev/null @@ -1,201 +0,0 @@ -=== alert.yml.tmpl -create_time absent x OK -create_time absent y OK -create_time x absent OK -create_time x y OK -create_time y absent OK -create_time y x OK -custom_description absent x OK -custom_description absent y OK -custom_description x absent OK -custom_description x y OK -custom_description y absent OK -custom_description y x OK -custom_summary absent x OK -custom_summary absent y OK -custom_summary x absent OK -custom_summary x y OK -custom_summary y absent OK -custom_summary y x OK -display_name x y OK -display_name y x OK -effective_run_as.service_principal_name absent x OK -effective_run_as.service_principal_name absent y OK -effective_run_as.service_principal_name x absent OK -effective_run_as.service_principal_name x y OK -effective_run_as.service_principal_name y absent OK -effective_run_as.service_principal_name y x OK -effective_run_as.user_name absent x OK -effective_run_as.user_name absent y OK -effective_run_as.user_name x absent OK -effective_run_as.user_name x y OK -effective_run_as.user_name y absent OK -effective_run_as.user_name y x OK -evaluation.comparison_operator x y OK -evaluation.comparison_operator y x OK -evaluation.empty_result_state absent x OK -evaluation.empty_result_state absent y OK -evaluation.empty_result_state x absent OK -evaluation.empty_result_state x y OK -evaluation.empty_result_state y absent OK -evaluation.empty_result_state y x OK -evaluation.last_evaluated_at absent x OK -evaluation.last_evaluated_at absent y OK -evaluation.last_evaluated_at x absent OK -evaluation.last_evaluated_at x y OK -evaluation.last_evaluated_at y absent OK -evaluation.last_evaluated_at y x OK -evaluation.notification.notify_on_ok absent false NOT_OBSERVABLE -evaluation.notification.notify_on_ok absent true OK -evaluation.notification.notify_on_ok false absent SUPPRESSED empty -evaluation.notification.notify_on_ok false true OK -evaluation.notification.notify_on_ok true absent OK -evaluation.notification.notify_on_ok true false OK -evaluation.notification.retrigger_seconds 1 2 OK -evaluation.notification.retrigger_seconds 1 absent OK -evaluation.notification.retrigger_seconds 2 1 OK -evaluation.notification.retrigger_seconds 2 absent OK -evaluation.notification.retrigger_seconds absent 1 OK -evaluation.notification.retrigger_seconds absent 2 OK -evaluation.source.aggregation absent x OK -evaluation.source.aggregation absent y OK -evaluation.source.aggregation x absent OK -evaluation.source.aggregation x y OK -evaluation.source.aggregation y absent OK -evaluation.source.aggregation y x OK -evaluation.source.display absent x OK -evaluation.source.display absent y OK -evaluation.source.display x absent OK -evaluation.source.display x y OK -evaluation.source.display y absent OK -evaluation.source.display y x OK -evaluation.source.name x y OK -evaluation.source.name y x OK -evaluation.state absent x OK -evaluation.state absent y OK -evaluation.state x absent OK -evaluation.state x y OK -evaluation.state y absent OK -evaluation.state y x OK -evaluation.threshold.column.aggregation absent x OK -evaluation.threshold.column.aggregation absent y OK -evaluation.threshold.column.aggregation x absent OK -evaluation.threshold.column.aggregation x y OK -evaluation.threshold.column.aggregation y absent OK -evaluation.threshold.column.aggregation y x OK -evaluation.threshold.column.display absent x OK -evaluation.threshold.column.display absent y OK -evaluation.threshold.column.display x absent OK -evaluation.threshold.column.display x y OK -evaluation.threshold.column.display y absent OK -evaluation.threshold.column.display y x OK -evaluation.threshold.column.name x y OK -evaluation.threshold.column.name y x OK -evaluation.threshold.value.bool_value absent false SUPPRESSED empty -evaluation.threshold.value.bool_value absent true OK -evaluation.threshold.value.bool_value false absent SUPPRESSED empty -evaluation.threshold.value.bool_value false true OK -evaluation.threshold.value.bool_value true absent OK -evaluation.threshold.value.bool_value true false OK -evaluation.threshold.value.double_value 1 2 OK -evaluation.threshold.value.double_value 1 absent OK -evaluation.threshold.value.double_value 2 1 OK -evaluation.threshold.value.double_value 2 absent OK -evaluation.threshold.value.double_value absent 1 OK -evaluation.threshold.value.double_value absent 2 OK -evaluation.threshold.value.string_value absent x OK -evaluation.threshold.value.string_value absent y OK -evaluation.threshold.value.string_value x absent OK -evaluation.threshold.value.string_value x y OK -evaluation.threshold.value.string_value y absent OK -evaluation.threshold.value.string_value y x OK -file_path absent x NOT_OBSERVABLE -file_path absent y NOT_OBSERVABLE -file_path x absent NOT_OBSERVABLE -file_path x y NOT_OBSERVABLE -file_path y absent NOT_OBSERVABLE -file_path y x NOT_OBSERVABLE -id absent x SUPPRESSED spec:output_only -id absent y SUPPRESSED spec:output_only -id x absent SUPPRESSED spec:output_only -id x y SUPPRESSED spec:output_only -id y absent SUPPRESSED spec:output_only -id y x SUPPRESSED spec:output_only -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle_state absent x OK -lifecycle_state absent y OK -lifecycle_state x absent OK -lifecycle_state x y OK -lifecycle_state y absent OK -lifecycle_state y x OK -owner_user_name absent x OK -owner_user_name absent y OK -owner_user_name x absent OK -owner_user_name x y OK -owner_user_name y absent OK -owner_user_name y x OK -parent_path absent x OK -parent_path absent y OK -parent_path x absent OK -parent_path x y OK -parent_path y absent OK -parent_path y x OK -query_text x y OK -query_text y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -run_as_user_name absent x OK -run_as_user_name absent y OK -run_as_user_name x absent OK -run_as_user_name x y OK -run_as_user_name y absent OK -run_as_user_name y x OK -schedule.pause_status absent x OK -schedule.pause_status absent y OK -schedule.pause_status x absent OK -schedule.pause_status x y OK -schedule.pause_status y absent OK -schedule.pause_status y x OK -schedule.quartz_cron_schedule x y OK -schedule.quartz_cron_schedule y x OK -schedule.timezone_id x y OK -schedule.timezone_id y x OK -update_time absent x OK -update_time absent y OK -update_time x absent OK -update_time x y OK -update_time y absent OK -update_time y x OK -warehouse_id x y OK -warehouse_id y x OK - -=== summary -OK 162 -SUPPRESSED 9 -NOT_OBSERVABLE 13 - -=== not covered: 9 fields under a slice or map -evaluation.notification.subscriptions[*].destination_id -evaluation.notification.subscriptions[*].user_email -parameters[*].name -parameters[*].type -parameters[*].value -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name diff --git a/bundle/direct/tests/output/apps.full.txt b/bundle/direct/tests/output/apps.full.txt deleted file mode 100644 index 9d0e3e35fe9..00000000000 --- a/bundle/direct/tests/output/apps.full.txt +++ /dev/null @@ -1,568 +0,0 @@ -=== app.yml.tmpl -active_deployment.create_time absent x OK -active_deployment.create_time absent y OK -active_deployment.create_time x absent OK -active_deployment.create_time x y OK -active_deployment.create_time y absent OK -active_deployment.create_time y x OK -active_deployment.creator absent x OK -active_deployment.creator absent y OK -active_deployment.creator x absent OK -active_deployment.creator x y OK -active_deployment.creator y absent OK -active_deployment.creator y x OK -active_deployment.deployment_artifacts.source_code_path absent x OK -active_deployment.deployment_artifacts.source_code_path absent y OK -active_deployment.deployment_artifacts.source_code_path x absent OK -active_deployment.deployment_artifacts.source_code_path x y OK -active_deployment.deployment_artifacts.source_code_path y absent OK -active_deployment.deployment_artifacts.source_code_path y x OK -active_deployment.deployment_id absent x OK -active_deployment.deployment_id absent y OK -active_deployment.deployment_id x absent OK -active_deployment.deployment_id x y OK -active_deployment.deployment_id y absent OK -active_deployment.deployment_id y x OK -active_deployment.git_source.branch absent x OK -active_deployment.git_source.branch absent y OK -active_deployment.git_source.branch x absent OK -active_deployment.git_source.branch x y OK -active_deployment.git_source.branch y absent OK -active_deployment.git_source.branch y x OK -active_deployment.git_source.commit absent x OK -active_deployment.git_source.commit absent y OK -active_deployment.git_source.commit x absent OK -active_deployment.git_source.commit x y OK -active_deployment.git_source.commit y absent OK -active_deployment.git_source.commit y x OK -active_deployment.git_source.git_repository.auto_deploy absent false OK -active_deployment.git_source.git_repository.auto_deploy absent true OK -active_deployment.git_source.git_repository.auto_deploy false absent OK -active_deployment.git_source.git_repository.auto_deploy false true OK -active_deployment.git_source.git_repository.auto_deploy true absent OK -active_deployment.git_source.git_repository.auto_deploy true false OK -active_deployment.git_source.git_repository.caller_credential_id 1 2 OK -active_deployment.git_source.git_repository.caller_credential_id 1 absent OK -active_deployment.git_source.git_repository.caller_credential_id 2 1 OK -active_deployment.git_source.git_repository.caller_credential_id 2 absent OK -active_deployment.git_source.git_repository.caller_credential_id absent 1 OK -active_deployment.git_source.git_repository.caller_credential_id absent 2 OK -active_deployment.git_source.git_repository.provider x y OK -active_deployment.git_source.git_repository.provider y x OK -active_deployment.git_source.git_repository.url x y OK -active_deployment.git_source.git_repository.url y x OK -active_deployment.git_source.resolved_commit absent x OK -active_deployment.git_source.resolved_commit absent y OK -active_deployment.git_source.resolved_commit x absent OK -active_deployment.git_source.resolved_commit x y OK -active_deployment.git_source.resolved_commit y absent OK -active_deployment.git_source.resolved_commit y x OK -active_deployment.git_source.source_code_path absent x OK -active_deployment.git_source.source_code_path absent y OK -active_deployment.git_source.source_code_path x absent OK -active_deployment.git_source.source_code_path x y OK -active_deployment.git_source.source_code_path y absent OK -active_deployment.git_source.source_code_path y x OK -active_deployment.git_source.tag absent x OK -active_deployment.git_source.tag absent y OK -active_deployment.git_source.tag x absent OK -active_deployment.git_source.tag x y OK -active_deployment.git_source.tag y absent OK -active_deployment.git_source.tag y x OK -active_deployment.mode absent x OK -active_deployment.mode absent y OK -active_deployment.mode x absent OK -active_deployment.mode x y OK -active_deployment.mode y absent OK -active_deployment.mode y x OK -active_deployment.source_code_path absent x OK -active_deployment.source_code_path absent y OK -active_deployment.source_code_path x absent OK -active_deployment.source_code_path x y OK -active_deployment.source_code_path y absent OK -active_deployment.source_code_path y x OK -active_deployment.status.message absent x OK -active_deployment.status.message absent y OK -active_deployment.status.message x absent OK -active_deployment.status.message x y OK -active_deployment.status.message y absent OK -active_deployment.status.message y x OK -active_deployment.status.state absent x OK -active_deployment.status.state absent y OK -active_deployment.status.state x absent OK -active_deployment.status.state x y OK -active_deployment.status.state y absent OK -active_deployment.status.state y x OK -active_deployment.update_time absent x OK -active_deployment.update_time absent y OK -active_deployment.update_time x absent OK -active_deployment.update_time x y OK -active_deployment.update_time y absent OK -active_deployment.update_time y x OK -app_status.message absent x OK -app_status.message absent y OK -app_status.message x absent OK -app_status.message x y OK -app_status.message y absent OK -app_status.message y x OK -app_status.running_instances 1 2 OK -app_status.running_instances 1 absent OK -app_status.running_instances 2 1 OK -app_status.running_instances 2 absent OK -app_status.running_instances absent 1 OK -app_status.running_instances absent 2 OK -app_status.state absent x OK -app_status.state absent y OK -app_status.state x absent OK -app_status.state x y OK -app_status.state y absent OK -app_status.state y x OK -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent POST_DEPLOY_DRIFT budget_policy_id -budget_policy_id x y OK -budget_policy_id y absent POST_DEPLOY_DRIFT budget_policy_id -budget_policy_id y x OK -compute_max_instances 1 2 OK -compute_max_instances 1 absent POST_DEPLOY_DRIFT compute_max_instances -compute_max_instances 2 1 OK -compute_max_instances 2 absent POST_DEPLOY_DRIFT compute_max_instances -compute_max_instances absent 1 OK -compute_max_instances absent 2 OK -compute_min_instances 1 2 OK -compute_min_instances 1 absent POST_DEPLOY_DRIFT compute_min_instances -compute_min_instances 2 1 OK -compute_min_instances 2 absent POST_DEPLOY_DRIFT compute_min_instances -compute_min_instances absent 1 OK -compute_min_instances absent 2 OK -compute_size LARGE MEDIUM OK -compute_size LARGE absent OK -compute_size MEDIUM LARGE OK -compute_size MEDIUM absent OK -compute_size absent LARGE OK -compute_size absent MEDIUM SUPPRESSED remote_already_set -compute_status.active_instances 1 2 OK -compute_status.active_instances 1 absent OK -compute_status.active_instances 2 1 OK -compute_status.active_instances 2 absent OK -compute_status.active_instances absent 1 OK -compute_status.active_instances absent 2 OK -compute_status.message absent x OK -compute_status.message absent y OK -compute_status.message x absent OK -compute_status.message x y OK -compute_status.message y absent OK -compute_status.message y x OK -compute_status.state absent x OK -compute_status.state absent y OK -compute_status.state x absent OK -compute_status.state x y OK -compute_status.state y absent OK -compute_status.state y x OK -create_time absent x OK -create_time absent y OK -create_time x absent OK -create_time x y OK -create_time y absent OK -create_time y x OK -creator absent x OK -creator absent y OK -creator x absent OK -creator x y OK -creator y absent OK -creator y x OK -default_git_source.branch absent x OK -default_git_source.branch absent y OK -default_git_source.branch x absent OK -default_git_source.branch x y OK -default_git_source.branch y absent OK -default_git_source.branch y x OK -default_git_source.commit absent x OK -default_git_source.commit absent y OK -default_git_source.commit x absent OK -default_git_source.commit x y OK -default_git_source.commit y absent OK -default_git_source.commit y x OK -default_git_source.git_repository.auto_deploy absent false OK -default_git_source.git_repository.auto_deploy absent true OK -default_git_source.git_repository.auto_deploy false absent OK -default_git_source.git_repository.auto_deploy false true OK -default_git_source.git_repository.auto_deploy true absent OK -default_git_source.git_repository.auto_deploy true false OK -default_git_source.git_repository.caller_credential_id 1 2 OK -default_git_source.git_repository.caller_credential_id 1 absent OK -default_git_source.git_repository.caller_credential_id 2 1 OK -default_git_source.git_repository.caller_credential_id 2 absent OK -default_git_source.git_repository.caller_credential_id absent 1 OK -default_git_source.git_repository.caller_credential_id absent 2 OK -default_git_source.git_repository.provider x y OK -default_git_source.git_repository.provider y x OK -default_git_source.git_repository.url x y OK -default_git_source.git_repository.url y x OK -default_git_source.resolved_commit absent x OK -default_git_source.resolved_commit absent y OK -default_git_source.resolved_commit x absent OK -default_git_source.resolved_commit x y OK -default_git_source.resolved_commit y absent OK -default_git_source.resolved_commit y x OK -default_git_source.source_code_path absent x OK -default_git_source.source_code_path absent y OK -default_git_source.source_code_path x absent OK -default_git_source.source_code_path x y OK -default_git_source.source_code_path y absent OK -default_git_source.source_code_path y x OK -default_git_source.tag absent x OK -default_git_source.tag absent y OK -default_git_source.tag x absent OK -default_git_source.tag x y OK -default_git_source.tag y absent OK -default_git_source.tag y x OK -default_source_code_path absent x OK -default_source_code_path absent y OK -default_source_code_path x absent OK -default_source_code_path x y OK -default_source_code_path y absent OK -default_source_code_path y x OK -description absent x OK -description absent y OK -description x absent POST_DEPLOY_DRIFT description -description x y OK -description y absent POST_DEPLOY_DRIFT description -description y x OK -effective_budget_policy_id absent x OK -effective_budget_policy_id absent y OK -effective_budget_policy_id x absent OK -effective_budget_policy_id x y OK -effective_budget_policy_id y absent OK -effective_budget_policy_id y x OK -effective_usage_policy_id absent x OK -effective_usage_policy_id absent y OK -effective_usage_policy_id x absent OK -effective_usage_policy_id x y OK -effective_usage_policy_id y absent OK -effective_usage_policy_id y x OK -forward_user_access_token absent false OK -forward_user_access_token absent true OK -forward_user_access_token false absent SUPPRESSED empty -forward_user_access_token false true OK -forward_user_access_token true absent OK -forward_user_access_token true false OK -git_repository.auto_deploy absent false OK -git_repository.auto_deploy absent true OK -git_repository.auto_deploy false absent SUPPRESSED empty -git_repository.auto_deploy false true OK -git_repository.auto_deploy true absent POST_DEPLOY_DRIFT git_repository.auto_deploy -git_repository.auto_deploy true false OK -git_repository.caller_credential_id 1 2 OK -git_repository.caller_credential_id 1 absent POST_DEPLOY_DRIFT git_repository.caller_credential_id -git_repository.caller_credential_id 2 1 OK -git_repository.caller_credential_id 2 absent POST_DEPLOY_DRIFT git_repository.caller_credential_id -git_repository.caller_credential_id absent 1 OK -git_repository.caller_credential_id absent 2 OK -git_repository.provider x y OK -git_repository.provider y x OK -git_repository.url x y OK -git_repository.url y x OK -git_source.branch absent x NOT_OBSERVABLE -git_source.branch absent y NOT_OBSERVABLE -git_source.branch x absent NOT_OBSERVABLE -git_source.branch x y NOT_OBSERVABLE -git_source.branch y absent NOT_OBSERVABLE -git_source.branch y x NOT_OBSERVABLE -git_source.commit absent x NOT_OBSERVABLE -git_source.commit absent y NOT_OBSERVABLE -git_source.commit x absent NOT_OBSERVABLE -git_source.commit x y NOT_OBSERVABLE -git_source.commit y absent NOT_OBSERVABLE -git_source.commit y x NOT_OBSERVABLE -git_source.git_repository.auto_deploy absent false NOT_OBSERVABLE -git_source.git_repository.auto_deploy absent true NOT_OBSERVABLE -git_source.git_repository.auto_deploy false absent NOT_OBSERVABLE -git_source.git_repository.auto_deploy false true NOT_OBSERVABLE -git_source.git_repository.auto_deploy true absent NOT_OBSERVABLE -git_source.git_repository.auto_deploy true false NOT_OBSERVABLE -git_source.git_repository.caller_credential_id 1 2 NOT_OBSERVABLE -git_source.git_repository.caller_credential_id 1 absent NOT_OBSERVABLE -git_source.git_repository.caller_credential_id 2 1 NOT_OBSERVABLE -git_source.git_repository.caller_credential_id 2 absent NOT_OBSERVABLE -git_source.git_repository.caller_credential_id absent 1 NOT_OBSERVABLE -git_source.git_repository.caller_credential_id absent 2 NOT_OBSERVABLE -git_source.git_repository.provider x y NOT_OBSERVABLE -git_source.git_repository.provider y x NOT_OBSERVABLE -git_source.git_repository.url x y NOT_OBSERVABLE -git_source.git_repository.url y x NOT_OBSERVABLE -git_source.resolved_commit absent x NOT_OBSERVABLE -git_source.resolved_commit absent y NOT_OBSERVABLE -git_source.resolved_commit x absent NOT_OBSERVABLE -git_source.resolved_commit x y NOT_OBSERVABLE -git_source.resolved_commit y absent NOT_OBSERVABLE -git_source.resolved_commit y x NOT_OBSERVABLE -git_source.source_code_path absent x NOT_OBSERVABLE -git_source.source_code_path absent y NOT_OBSERVABLE -git_source.source_code_path x absent NOT_OBSERVABLE -git_source.source_code_path x y NOT_OBSERVABLE -git_source.source_code_path y absent NOT_OBSERVABLE -git_source.source_code_path y x NOT_OBSERVABLE -git_source.tag absent x NOT_OBSERVABLE -git_source.tag absent y NOT_OBSERVABLE -git_source.tag x absent NOT_OBSERVABLE -git_source.tag x y NOT_OBSERVABLE -git_source.tag y absent NOT_OBSERVABLE -git_source.tag y x NOT_OBSERVABLE -id absent x SUPPRESSED spec:output_only -id absent y SUPPRESSED spec:output_only -id x absent SUPPRESSED spec:output_only -id x y SUPPRESSED spec:output_only -id y absent SUPPRESSED spec:output_only -id y x SUPPRESSED spec:output_only -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle.started absent false NOT_OBSERVABLE -lifecycle.started absent true OK -lifecycle.started false absent OK -lifecycle.started false true OK -lifecycle.started true absent OK -lifecycle.started true false OK -name x y OK_RECREATE -name y x DEPLOY_ERROR cannot recreate resources.apps.foo: timed out: app is deleting, retrying create -oauth2_app_client_id absent x OK -oauth2_app_client_id absent y OK -oauth2_app_client_id x absent OK -oauth2_app_client_id x y OK -oauth2_app_client_id y absent OK -oauth2_app_client_id y x OK -oauth2_app_integration_id absent x OK -oauth2_app_integration_id absent y OK -oauth2_app_integration_id x absent OK -oauth2_app_integration_id x y OK -oauth2_app_integration_id y absent OK -oauth2_app_integration_id y x OK -pending_deployment.create_time absent x OK -pending_deployment.create_time absent y OK -pending_deployment.create_time x absent OK -pending_deployment.create_time x y OK -pending_deployment.create_time y absent OK -pending_deployment.create_time y x OK -pending_deployment.creator absent x OK -pending_deployment.creator absent y OK -pending_deployment.creator x absent OK -pending_deployment.creator x y OK -pending_deployment.creator y absent OK -pending_deployment.creator y x OK -pending_deployment.deployment_artifacts.source_code_path absent x OK -pending_deployment.deployment_artifacts.source_code_path absent y OK -pending_deployment.deployment_artifacts.source_code_path x absent OK -pending_deployment.deployment_artifacts.source_code_path x y OK -pending_deployment.deployment_artifacts.source_code_path y absent OK -pending_deployment.deployment_artifacts.source_code_path y x OK -pending_deployment.deployment_id absent x OK -pending_deployment.deployment_id absent y OK -pending_deployment.deployment_id x absent OK -pending_deployment.deployment_id x y OK -pending_deployment.deployment_id y absent OK -pending_deployment.deployment_id y x OK -pending_deployment.git_source.branch absent x OK -pending_deployment.git_source.branch absent y OK -pending_deployment.git_source.branch x absent OK -pending_deployment.git_source.branch x y OK -pending_deployment.git_source.branch y absent OK -pending_deployment.git_source.branch y x OK -pending_deployment.git_source.commit absent x OK -pending_deployment.git_source.commit absent y OK -pending_deployment.git_source.commit x absent OK -pending_deployment.git_source.commit x y OK -pending_deployment.git_source.commit y absent OK -pending_deployment.git_source.commit y x OK -pending_deployment.git_source.git_repository.auto_deploy absent false OK -pending_deployment.git_source.git_repository.auto_deploy absent true OK -pending_deployment.git_source.git_repository.auto_deploy false absent OK -pending_deployment.git_source.git_repository.auto_deploy false true OK -pending_deployment.git_source.git_repository.auto_deploy true absent OK -pending_deployment.git_source.git_repository.auto_deploy true false OK -pending_deployment.git_source.git_repository.caller_credential_id 1 2 OK -pending_deployment.git_source.git_repository.caller_credential_id 1 absent OK -pending_deployment.git_source.git_repository.caller_credential_id 2 1 OK -pending_deployment.git_source.git_repository.caller_credential_id 2 absent OK -pending_deployment.git_source.git_repository.caller_credential_id absent 1 OK -pending_deployment.git_source.git_repository.caller_credential_id absent 2 OK -pending_deployment.git_source.git_repository.provider x y OK -pending_deployment.git_source.git_repository.provider y x OK -pending_deployment.git_source.git_repository.url x y OK -pending_deployment.git_source.git_repository.url y x OK -pending_deployment.git_source.resolved_commit absent x OK -pending_deployment.git_source.resolved_commit absent y OK -pending_deployment.git_source.resolved_commit x absent OK -pending_deployment.git_source.resolved_commit x y OK -pending_deployment.git_source.resolved_commit y absent OK -pending_deployment.git_source.resolved_commit y x OK -pending_deployment.git_source.source_code_path absent x OK -pending_deployment.git_source.source_code_path absent y OK -pending_deployment.git_source.source_code_path x absent OK -pending_deployment.git_source.source_code_path x y OK -pending_deployment.git_source.source_code_path y absent OK -pending_deployment.git_source.source_code_path y x OK -pending_deployment.git_source.tag absent x OK -pending_deployment.git_source.tag absent y OK -pending_deployment.git_source.tag x absent OK -pending_deployment.git_source.tag x y OK -pending_deployment.git_source.tag y absent OK -pending_deployment.git_source.tag y x OK -pending_deployment.mode absent x OK -pending_deployment.mode absent y OK -pending_deployment.mode x absent OK -pending_deployment.mode x y OK -pending_deployment.mode y absent OK -pending_deployment.mode y x OK -pending_deployment.source_code_path absent x OK -pending_deployment.source_code_path absent y OK -pending_deployment.source_code_path x absent OK -pending_deployment.source_code_path x y OK -pending_deployment.source_code_path y absent OK -pending_deployment.source_code_path y x OK -pending_deployment.status.message absent x OK -pending_deployment.status.message absent y OK -pending_deployment.status.message x absent OK -pending_deployment.status.message x y OK -pending_deployment.status.message y absent OK -pending_deployment.status.message y x OK -pending_deployment.status.state absent x OK -pending_deployment.status.state absent y OK -pending_deployment.status.state x absent OK -pending_deployment.status.state x y OK -pending_deployment.status.state y absent OK -pending_deployment.status.state y x OK -pending_deployment.update_time absent x OK -pending_deployment.update_time absent y OK -pending_deployment.update_time x absent OK -pending_deployment.update_time x y OK -pending_deployment.update_time y absent OK -pending_deployment.update_time y x OK -service_principal_client_id absent x OK -service_principal_client_id absent y OK -service_principal_client_id x absent OK -service_principal_client_id x y OK -service_principal_client_id y absent OK -service_principal_client_id y x OK -service_principal_id 1 2 OK -service_principal_id 1 absent OK -service_principal_id 2 1 OK -service_principal_id 2 absent OK -service_principal_id absent 1 OK -service_principal_id absent 2 OK -service_principal_name absent x OK -service_principal_name absent y OK -service_principal_name x absent OK -service_principal_name x y OK -service_principal_name y absent OK -service_principal_name y x OK -source_code_path absent x SUPPRESSED no active deployment -source_code_path absent y SUPPRESSED no active deployment -source_code_path x absent SUPPRESSED no active deployment -source_code_path x y SUPPRESSED no active deployment -source_code_path y absent SUPPRESSED no active deployment -source_code_path y x SUPPRESSED no active deployment -space absent x OK -space absent y OK -space x absent OK -space x y OK -space y absent OK -space y x OK -thumbnail_url absent x OK -thumbnail_url absent y OK -thumbnail_url x absent OK -thumbnail_url x y OK -thumbnail_url y absent OK -thumbnail_url y x OK -update_time absent x OK -update_time absent y OK -update_time x absent OK -update_time x y OK -update_time y absent OK -update_time y x OK -updater absent x OK -updater absent y OK -updater x absent OK -updater x y OK -updater y absent OK -updater y x OK -url absent x SUPPRESSED spec:output_only -url absent y SUPPRESSED spec:output_only -url x absent SUPPRESSED spec:output_only -url x y SUPPRESSED spec:output_only -url y absent SUPPRESSED spec:output_only -url y x SUPPRESSED spec:output_only -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent POST_DEPLOY_DRIFT usage_policy_id -usage_policy_id x y OK -usage_policy_id y absent POST_DEPLOY_DRIFT usage_policy_id -usage_policy_id y x OK - -=== summary -OK 413 -OK_RECREATE 1 -SUPPRESSED 21 -NOT_OBSERVABLE 59 -POST_DEPLOY_DRIFT 13 -DEPLOY_ERROR 1 - -=== not covered: 49 fields under a slice or map -active_deployment.command[*] -active_deployment.env_vars[*].name -active_deployment.env_vars[*].value -active_deployment.env_vars[*].value_from -config.command[*] -config.env[*].name -config.env[*].value -config.env[*].value_from -effective_user_api_scopes[*] -pending_deployment.command[*] -pending_deployment.env_vars[*].name -pending_deployment.env_vars[*].value -pending_deployment.env_vars[*].value_from -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -resources[*].app.name -resources[*].app.permission -resources[*].database.database_name -resources[*].database.instance_name -resources[*].database.permission -resources[*].description -resources[*].experiment.experiment_id -resources[*].experiment.permission -resources[*].genie_space.name -resources[*].genie_space.permission -resources[*].genie_space.space_id -resources[*].job.id -resources[*].job.permission -resources[*].name -resources[*].postgres.branch -resources[*].postgres.database -resources[*].postgres.permission -resources[*].secret.key -resources[*].secret.permission -resources[*].secret.scope -resources[*].serving_endpoint.name -resources[*].serving_endpoint.permission -resources[*].sql_warehouse.id -resources[*].sql_warehouse.permission -resources[*].uc_securable.permission -resources[*].uc_securable.securable_full_name -resources[*].uc_securable.securable_kind -resources[*].uc_securable.securable_type -telemetry_export_destinations[*].unity_catalog.logs_table -telemetry_export_destinations[*].unity_catalog.metrics_table -telemetry_export_destinations[*].unity_catalog.traces_table -user_api_scopes[*] diff --git a/bundle/direct/tests/output/catalogs.full.txt b/bundle/direct/tests/output/catalogs.full.txt deleted file mode 100644 index 0bffe092068..00000000000 --- a/bundle/direct/tests/output/catalogs.full.txt +++ /dev/null @@ -1,155 +0,0 @@ -=== catalog.yml.tmpl -comment absent x OK -comment absent y OK -comment x absent OK -comment x y OK -comment y absent OK -comment y x OK -connection_name absent x OK_RECREATE -connection_name absent y OK_RECREATE -connection_name x absent OK_RECREATE -connection_name x y OK_RECREATE -connection_name y absent OK_RECREATE -connection_name y x OK_RECREATE -custom_max_retention_hours 1 2 OK -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 1 OK -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours absent 1 OK -custom_max_retention_hours absent 2 OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y x OK -managed_encryption_settings.azure_encryption_settings.azure_tenant_id x y OK -managed_encryption_settings.azure_encryption_settings.azure_tenant_id y x OK -managed_encryption_settings.azure_key_vault_key_id absent x OK -managed_encryption_settings.azure_key_vault_key_id absent y OK -managed_encryption_settings.azure_key_vault_key_id x absent OK -managed_encryption_settings.azure_key_vault_key_id x y OK -managed_encryption_settings.azure_key_vault_key_id y absent OK -managed_encryption_settings.azure_key_vault_key_id y x OK -managed_encryption_settings.customer_managed_key_id absent x OK -managed_encryption_settings.customer_managed_key_id absent y OK -managed_encryption_settings.customer_managed_key_id x absent OK -managed_encryption_settings.customer_managed_key_id x y OK -managed_encryption_settings.customer_managed_key_id y absent OK -managed_encryption_settings.customer_managed_key_id y x OK -name x y OK -name y x OK -provider_name absent x OK_RECREATE -provider_name absent y OK_RECREATE -provider_name x absent OK_RECREATE -provider_name x y OK_RECREATE -provider_name y absent OK_RECREATE -provider_name y x OK_RECREATE -share_name absent x OK_RECREATE -share_name absent y OK_RECREATE -share_name x absent OK_RECREATE -share_name x y OK_RECREATE -share_name y absent OK_RECREATE -share_name y x OK_RECREATE -storage_root absent x OK_RECREATE -storage_root absent y OK_RECREATE -storage_root x absent OK_RECREATE -storage_root x y OK_RECREATE -storage_root y absent OK_RECREATE -storage_root y x OK_RECREATE - -=== catalog_optional_fields.yml.tmpl -comment absent x OK -comment absent y OK -comment x absent OK -comment x y OK -comment y absent OK -comment y x OK -connection_name absent x OK_RECREATE -connection_name absent y OK_RECREATE -connection_name x absent OK_RECREATE -connection_name x y OK_RECREATE -connection_name y absent OK_RECREATE -connection_name y x OK_RECREATE -custom_max_retention_hours 1 2 OK -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 1 OK -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours absent 1 OK -custom_max_retention_hours absent 2 OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id absent y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id x y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id y x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent x OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id absent y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id x y OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y absent OK -managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id y x OK -managed_encryption_settings.azure_encryption_settings.azure_tenant_id x y OK -managed_encryption_settings.azure_encryption_settings.azure_tenant_id y x OK -managed_encryption_settings.azure_key_vault_key_id absent x OK -managed_encryption_settings.azure_key_vault_key_id absent y OK -managed_encryption_settings.azure_key_vault_key_id x absent OK -managed_encryption_settings.azure_key_vault_key_id x y OK -managed_encryption_settings.azure_key_vault_key_id y absent OK -managed_encryption_settings.azure_key_vault_key_id y x OK -managed_encryption_settings.customer_managed_key_id absent x OK -managed_encryption_settings.customer_managed_key_id absent y OK -managed_encryption_settings.customer_managed_key_id x absent OK -managed_encryption_settings.customer_managed_key_id x y OK -managed_encryption_settings.customer_managed_key_id y absent OK -managed_encryption_settings.customer_managed_key_id y x OK -name x y OK -name y x OK -provider_name absent x OK_RECREATE -provider_name absent y OK_RECREATE -provider_name x absent OK_RECREATE -provider_name x y OK_RECREATE -provider_name y absent OK_RECREATE -provider_name y x OK_RECREATE -share_name absent x OK_RECREATE -share_name absent y OK_RECREATE -share_name x absent OK_RECREATE -share_name x y OK_RECREATE -share_name y absent OK_RECREATE -share_name y x OK_RECREATE -storage_root absent x OK_RECREATE -storage_root absent y OK_RECREATE -storage_root x absent OK_RECREATE -storage_root x y OK_RECREATE -storage_root y absent OK_RECREATE -storage_root y x OK_RECREATE - -=== summary -OK 76 -OK_RECREATE 48 -NOT_OBSERVABLE 12 -POST_DEPLOY_DRIFT 4 - -=== not covered: 4 fields under a slice or map -grants[*].principal -grants[*].privileges[*] -options.* -properties.* diff --git a/bundle/direct/tests/output/cluster_policies.full.txt b/bundle/direct/tests/output/cluster_policies.full.txt deleted file mode 100644 index eab334d21b9..00000000000 --- a/bundle/direct/tests/output/cluster_policies.full.txt +++ /dev/null @@ -1,64 +0,0 @@ -=== cluster_policy.yml.tmpl -definition absent x OK -definition absent y OK -definition x absent OK -definition x y OK -definition y absent OK -definition y x OK -description absent x OK -description absent y OK -description x absent OK -description x y OK -description y absent OK -description y x OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -max_clusters_per_user 1 2 OK -max_clusters_per_user 1 absent OK -max_clusters_per_user 2 1 OK -max_clusters_per_user 2 absent OK -max_clusters_per_user absent 1 OK -max_clusters_per_user absent 2 OK -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -policy_family_definition_overrides absent x OK -policy_family_definition_overrides absent y OK -policy_family_definition_overrides x absent OK -policy_family_definition_overrides x y OK -policy_family_definition_overrides y absent OK -policy_family_definition_overrides y x OK -policy_family_id absent x OK -policy_family_id absent y OK -policy_family_id x absent OK -policy_family_id x y OK -policy_family_id y absent OK -policy_family_id y x OK - -=== summary -OK 36 -NOT_OBSERVABLE 6 - -=== not covered: 15 fields under a slice or map -libraries[*].cran.package -libraries[*].cran.repo -libraries[*].egg -libraries[*].jar -libraries[*].maven.coordinates -libraries[*].maven.exclusions[*] -libraries[*].maven.repo -libraries[*].pypi.package -libraries[*].pypi.repo -libraries[*].requirements -libraries[*].whl -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name diff --git a/bundle/direct/tests/output/clusters.full.txt b/bundle/direct/tests/output/clusters.full.txt deleted file mode 100644 index b0e7a9e505a..00000000000 --- a/bundle/direct/tests/output/clusters.full.txt +++ /dev/null @@ -1,777 +0,0 @@ -=== cluster.yml.tmpl -apply_policy_default_values absent false SUPPRESSED empty -apply_policy_default_values absent true OK -apply_policy_default_values false absent SUPPRESSED remote_already_set -apply_policy_default_values false true OK -apply_policy_default_values true absent OK -apply_policy_default_values true false OK -autoscale.max_workers 1 2 OK -autoscale.max_workers 1 absent OK -autoscale.max_workers 2 1 OK -autoscale.max_workers 2 absent OK -autoscale.max_workers absent 1 OK -autoscale.max_workers absent 2 OK -autoscale.min_workers 1 2 OK -autoscale.min_workers 1 absent OK -autoscale.min_workers 2 1 OK -autoscale.min_workers 2 absent OK -autoscale.min_workers absent 1 OK -autoscale.min_workers absent 2 OK -autotermination_minutes 1 2 OK -autotermination_minutes 1 absent OK -autotermination_minutes 2 1 OK -autotermination_minutes 2 absent OK -autotermination_minutes absent 1 OK -autotermination_minutes absent 2 OK -aws_attributes.availability absent x OK -aws_attributes.availability absent y OK -aws_attributes.availability x absent OK -aws_attributes.availability x y OK -aws_attributes.availability y absent OK -aws_attributes.availability y x OK -aws_attributes.ebs_volume_count 1 2 OK -aws_attributes.ebs_volume_count 1 absent OK -aws_attributes.ebs_volume_count 2 1 OK -aws_attributes.ebs_volume_count 2 absent OK -aws_attributes.ebs_volume_count absent 1 OK -aws_attributes.ebs_volume_count absent 2 OK -aws_attributes.ebs_volume_iops 1 2 OK -aws_attributes.ebs_volume_iops 1 absent OK -aws_attributes.ebs_volume_iops 2 1 OK -aws_attributes.ebs_volume_iops 2 absent OK -aws_attributes.ebs_volume_iops absent 1 OK -aws_attributes.ebs_volume_iops absent 2 OK -aws_attributes.ebs_volume_size 1 2 OK -aws_attributes.ebs_volume_size 1 absent OK -aws_attributes.ebs_volume_size 2 1 OK -aws_attributes.ebs_volume_size 2 absent OK -aws_attributes.ebs_volume_size absent 1 OK -aws_attributes.ebs_volume_size absent 2 OK -aws_attributes.ebs_volume_throughput 1 2 OK -aws_attributes.ebs_volume_throughput 1 absent OK -aws_attributes.ebs_volume_throughput 2 1 OK -aws_attributes.ebs_volume_throughput 2 absent OK -aws_attributes.ebs_volume_throughput absent 1 OK -aws_attributes.ebs_volume_throughput absent 2 OK -aws_attributes.ebs_volume_type absent x OK -aws_attributes.ebs_volume_type absent y OK -aws_attributes.ebs_volume_type x absent OK -aws_attributes.ebs_volume_type x y OK -aws_attributes.ebs_volume_type y absent OK -aws_attributes.ebs_volume_type y x OK -aws_attributes.first_on_demand 1 2 OK -aws_attributes.first_on_demand 1 absent OK -aws_attributes.first_on_demand 2 1 OK -aws_attributes.first_on_demand 2 absent OK -aws_attributes.first_on_demand absent 1 OK -aws_attributes.first_on_demand absent 2 OK -aws_attributes.instance_profile_arn absent x OK -aws_attributes.instance_profile_arn absent y OK -aws_attributes.instance_profile_arn x absent OK -aws_attributes.instance_profile_arn x y OK -aws_attributes.instance_profile_arn y absent OK -aws_attributes.instance_profile_arn y x OK -aws_attributes.spot_bid_price_percent 1 2 OK -aws_attributes.spot_bid_price_percent 1 absent OK -aws_attributes.spot_bid_price_percent 2 1 OK -aws_attributes.spot_bid_price_percent 2 absent OK -aws_attributes.spot_bid_price_percent absent 1 OK -aws_attributes.spot_bid_price_percent absent 2 OK -aws_attributes.zone_id absent x OK -aws_attributes.zone_id absent y OK -aws_attributes.zone_id x absent OK -aws_attributes.zone_id x y OK -aws_attributes.zone_id y absent OK -aws_attributes.zone_id y x OK -azure_attributes.availability absent x OK -azure_attributes.availability absent y OK -azure_attributes.availability x absent OK -azure_attributes.availability x y OK -azure_attributes.availability y absent OK -azure_attributes.availability y x OK -azure_attributes.capacity_reservation_group absent x OK -azure_attributes.capacity_reservation_group absent y OK -azure_attributes.capacity_reservation_group x absent OK -azure_attributes.capacity_reservation_group x y OK -azure_attributes.capacity_reservation_group y absent OK -azure_attributes.capacity_reservation_group y x OK -azure_attributes.first_on_demand 1 2 OK -azure_attributes.first_on_demand 1 absent OK -azure_attributes.first_on_demand 2 1 OK -azure_attributes.first_on_demand 2 absent OK -azure_attributes.first_on_demand absent 1 OK -azure_attributes.first_on_demand absent 2 OK -azure_attributes.log_analytics_info.log_analytics_primary_key absent x OK -azure_attributes.log_analytics_info.log_analytics_primary_key absent y OK -azure_attributes.log_analytics_info.log_analytics_primary_key x absent OK -azure_attributes.log_analytics_info.log_analytics_primary_key x y OK -azure_attributes.log_analytics_info.log_analytics_primary_key y absent OK -azure_attributes.log_analytics_info.log_analytics_primary_key y x OK -azure_attributes.log_analytics_info.log_analytics_workspace_id absent x OK -azure_attributes.log_analytics_info.log_analytics_workspace_id absent y OK -azure_attributes.log_analytics_info.log_analytics_workspace_id x absent OK -azure_attributes.log_analytics_info.log_analytics_workspace_id x y OK -azure_attributes.log_analytics_info.log_analytics_workspace_id y absent OK -azure_attributes.log_analytics_info.log_analytics_workspace_id y x OK -azure_attributes.spot_bid_max_price 1 2 OK -azure_attributes.spot_bid_max_price 1 absent OK -azure_attributes.spot_bid_max_price 2 1 OK -azure_attributes.spot_bid_max_price 2 absent OK -azure_attributes.spot_bid_max_price absent 1 OK -azure_attributes.spot_bid_max_price absent 2 OK -cluster_log_conf.dbfs.destination x y OK -cluster_log_conf.dbfs.destination y x OK -cluster_log_conf.s3.canned_acl absent x OK -cluster_log_conf.s3.canned_acl absent y OK -cluster_log_conf.s3.canned_acl x absent OK -cluster_log_conf.s3.canned_acl x y OK -cluster_log_conf.s3.canned_acl y absent OK -cluster_log_conf.s3.canned_acl y x OK -cluster_log_conf.s3.destination x y OK -cluster_log_conf.s3.destination y x OK -cluster_log_conf.s3.enable_encryption absent false SUPPRESSED empty -cluster_log_conf.s3.enable_encryption absent true OK -cluster_log_conf.s3.enable_encryption false absent SUPPRESSED empty -cluster_log_conf.s3.enable_encryption false true OK -cluster_log_conf.s3.enable_encryption true absent OK -cluster_log_conf.s3.enable_encryption true false OK -cluster_log_conf.s3.encryption_type absent x OK -cluster_log_conf.s3.encryption_type absent y OK -cluster_log_conf.s3.encryption_type x absent OK -cluster_log_conf.s3.encryption_type x y OK -cluster_log_conf.s3.encryption_type y absent OK -cluster_log_conf.s3.encryption_type y x OK -cluster_log_conf.s3.endpoint absent x OK -cluster_log_conf.s3.endpoint absent y OK -cluster_log_conf.s3.endpoint x absent OK -cluster_log_conf.s3.endpoint x y OK -cluster_log_conf.s3.endpoint y absent OK -cluster_log_conf.s3.endpoint y x OK -cluster_log_conf.s3.kms_key absent x OK -cluster_log_conf.s3.kms_key absent y OK -cluster_log_conf.s3.kms_key x absent OK -cluster_log_conf.s3.kms_key x y OK -cluster_log_conf.s3.kms_key y absent OK -cluster_log_conf.s3.kms_key y x OK -cluster_log_conf.s3.region absent x OK -cluster_log_conf.s3.region absent y OK -cluster_log_conf.s3.region x absent OK -cluster_log_conf.s3.region x y OK -cluster_log_conf.s3.region y absent OK -cluster_log_conf.s3.region y x OK -cluster_log_conf.volumes.destination x y OK -cluster_log_conf.volumes.destination y x OK -cluster_name absent x OK -cluster_name absent y OK -cluster_name x absent OK -cluster_name x y OK -cluster_name y absent OK -cluster_name y x OK -data_security_mode absent x OK -data_security_mode absent y OK -data_security_mode x absent OK -data_security_mode x y OK -data_security_mode y absent OK -data_security_mode y x OK -dependency_mode absent x OK -dependency_mode absent y OK -dependency_mode x absent OK -dependency_mode x y OK -dependency_mode y absent OK -dependency_mode y x OK -docker_image.basic_auth.password absent x OK -docker_image.basic_auth.password absent y OK -docker_image.basic_auth.password x absent OK -docker_image.basic_auth.password x y OK -docker_image.basic_auth.password y absent OK -docker_image.basic_auth.password y x OK -docker_image.basic_auth.username absent x OK -docker_image.basic_auth.username absent y OK -docker_image.basic_auth.username x absent OK -docker_image.basic_auth.username x y OK -docker_image.basic_auth.username y absent OK -docker_image.basic_auth.username y x OK -docker_image.url absent x OK -docker_image.url absent y OK -docker_image.url x absent OK -docker_image.url x y OK -docker_image.url y absent OK -docker_image.url y x OK -driver_instance_pool_id absent x OK -driver_instance_pool_id absent y OK -driver_instance_pool_id x absent OK -driver_instance_pool_id x y OK -driver_instance_pool_id y absent OK -driver_instance_pool_id y x OK -driver_node_type_id absent x OK -driver_node_type_id absent y OK -driver_node_type_id x absent OK -driver_node_type_id x y OK -driver_node_type_id y absent OK -driver_node_type_id y x OK -enable_elastic_disk absent false SUPPRESSED remote_already_set -enable_elastic_disk absent true OK -enable_elastic_disk false absent SUPPRESSED empty -enable_elastic_disk false true OK -enable_elastic_disk true absent OK -enable_elastic_disk true false OK -enable_local_disk_encryption absent false SUPPRESSED empty -enable_local_disk_encryption absent true OK -enable_local_disk_encryption false absent SUPPRESSED empty -enable_local_disk_encryption false true OK -enable_local_disk_encryption true absent OK -enable_local_disk_encryption true false OK -gcp_attributes.availability absent x OK -gcp_attributes.availability absent y OK -gcp_attributes.availability x absent OK -gcp_attributes.availability x y OK -gcp_attributes.availability y absent OK -gcp_attributes.availability y x OK -gcp_attributes.boot_disk_size 1 2 OK -gcp_attributes.boot_disk_size 1 absent OK -gcp_attributes.boot_disk_size 2 1 OK -gcp_attributes.boot_disk_size 2 absent OK -gcp_attributes.boot_disk_size absent 1 OK -gcp_attributes.boot_disk_size absent 2 OK -gcp_attributes.confidential_compute_type absent x OK -gcp_attributes.confidential_compute_type absent y OK -gcp_attributes.confidential_compute_type x absent OK -gcp_attributes.confidential_compute_type x y OK -gcp_attributes.confidential_compute_type y absent OK -gcp_attributes.confidential_compute_type y x OK -gcp_attributes.first_on_demand 1 2 OK -gcp_attributes.first_on_demand 1 absent OK -gcp_attributes.first_on_demand 2 1 OK -gcp_attributes.first_on_demand 2 absent OK -gcp_attributes.first_on_demand absent 1 OK -gcp_attributes.first_on_demand absent 2 OK -gcp_attributes.google_service_account absent x OK -gcp_attributes.google_service_account absent y OK -gcp_attributes.google_service_account x absent OK -gcp_attributes.google_service_account x y OK -gcp_attributes.google_service_account y absent OK -gcp_attributes.google_service_account y x OK -gcp_attributes.local_ssd_count 1 2 OK -gcp_attributes.local_ssd_count 1 absent OK -gcp_attributes.local_ssd_count 2 1 OK -gcp_attributes.local_ssd_count 2 absent OK -gcp_attributes.local_ssd_count absent 1 OK -gcp_attributes.local_ssd_count absent 2 OK -gcp_attributes.use_preemptible_executors absent false SUPPRESSED empty -gcp_attributes.use_preemptible_executors absent true OK -gcp_attributes.use_preemptible_executors false absent SUPPRESSED empty -gcp_attributes.use_preemptible_executors false true OK -gcp_attributes.use_preemptible_executors true absent OK -gcp_attributes.use_preemptible_executors true false OK -gcp_attributes.zone_id absent x OK -gcp_attributes.zone_id absent y OK -gcp_attributes.zone_id x absent OK -gcp_attributes.zone_id x y OK -gcp_attributes.zone_id y absent OK -gcp_attributes.zone_id y x OK -instance_pool_id absent x OK -instance_pool_id absent y OK -instance_pool_id x absent OK -instance_pool_id x y OK -instance_pool_id y absent OK -instance_pool_id y x OK -is_single_node absent false SUPPRESSED empty -is_single_node absent true OK -is_single_node false absent SUPPRESSED empty -is_single_node false true OK -is_single_node true absent OK -is_single_node true false OK -kind absent x OK -kind absent y OK -kind x absent OK -kind x y OK -kind y absent OK -kind y x OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle.started absent false OK -lifecycle.started absent true OK -lifecycle.started false absent OK -lifecycle.started false true OK -lifecycle.started true absent OK -lifecycle.started true false OK -node_type_id absent x OK -node_type_id absent y OK -node_type_id x absent OK -node_type_id x y OK -node_type_id y absent OK -node_type_id y x OK -num_workers 1 2 OK -num_workers 1 absent OK -num_workers 2 1 OK -num_workers 2 absent OK -num_workers absent 1 OK -num_workers absent 2 OK -policy_id absent x OK -policy_id absent y OK -policy_id x absent OK -policy_id x y OK -policy_id y absent OK -policy_id y x OK -remote_disk_throughput 1 2 OK -remote_disk_throughput 1 absent OK -remote_disk_throughput 2 1 OK -remote_disk_throughput 2 absent OK -remote_disk_throughput absent 1 OK -remote_disk_throughput absent 2 OK -runtime_engine absent x OK -runtime_engine absent y OK -runtime_engine x absent OK -runtime_engine x y OK -runtime_engine y absent OK -runtime_engine y x OK -single_user_name absent x OK -single_user_name absent y OK -single_user_name x absent OK -single_user_name x y OK -single_user_name y absent OK -single_user_name y x OK -spark_version absent x OK -spark_version absent y OK -spark_version x absent OK -spark_version x y OK -spark_version y absent OK -spark_version y x OK -total_initial_remote_disk_size 1 2 OK -total_initial_remote_disk_size 1 absent OK -total_initial_remote_disk_size 2 1 OK -total_initial_remote_disk_size 2 absent OK -total_initial_remote_disk_size absent 1 OK -total_initial_remote_disk_size absent 2 OK -use_ml_runtime absent false SUPPRESSED empty -use_ml_runtime absent true OK -use_ml_runtime false absent SUPPRESSED empty -use_ml_runtime false true OK -use_ml_runtime true absent OK -use_ml_runtime true false OK -workload_type.clients.jobs absent false OK -workload_type.clients.jobs absent true OK -workload_type.clients.jobs false absent SUPPRESSED empty -workload_type.clients.jobs false true OK -workload_type.clients.jobs true absent OK -workload_type.clients.jobs true false OK -workload_type.clients.notebooks absent false SUPPRESSED empty -workload_type.clients.notebooks absent true OK -workload_type.clients.notebooks false absent SUPPRESSED empty -workload_type.clients.notebooks false true OK -workload_type.clients.notebooks true absent OK -workload_type.clients.notebooks true false OK - -=== cluster_apply_policy_default_values.yml.tmpl -apply_policy_default_values absent false SUPPRESSED empty -apply_policy_default_values absent true OK -apply_policy_default_values false absent SUPPRESSED remote_already_set -apply_policy_default_values false true OK -apply_policy_default_values true absent OK -apply_policy_default_values true false OK -autoscale.max_workers 1 2 OK -autoscale.max_workers 1 absent OK -autoscale.max_workers 2 1 OK -autoscale.max_workers 2 absent OK -autoscale.max_workers absent 1 OK -autoscale.max_workers absent 2 OK -autoscale.min_workers 1 2 OK -autoscale.min_workers 1 absent OK -autoscale.min_workers 2 1 OK -autoscale.min_workers 2 absent OK -autoscale.min_workers absent 1 OK -autoscale.min_workers absent 2 OK -autotermination_minutes 1 2 OK -autotermination_minutes 1 absent OK -autotermination_minutes 2 1 OK -autotermination_minutes 2 absent OK -autotermination_minutes absent 1 OK -autotermination_minutes absent 2 OK -aws_attributes.availability absent x OK -aws_attributes.availability absent y OK -aws_attributes.availability x absent OK -aws_attributes.availability x y OK -aws_attributes.availability y absent OK -aws_attributes.availability y x OK -aws_attributes.ebs_volume_count 1 2 OK -aws_attributes.ebs_volume_count 1 absent OK -aws_attributes.ebs_volume_count 2 1 OK -aws_attributes.ebs_volume_count 2 absent OK -aws_attributes.ebs_volume_count absent 1 OK -aws_attributes.ebs_volume_count absent 2 OK -aws_attributes.ebs_volume_iops 1 2 OK -aws_attributes.ebs_volume_iops 1 absent OK -aws_attributes.ebs_volume_iops 2 1 OK -aws_attributes.ebs_volume_iops 2 absent OK -aws_attributes.ebs_volume_iops absent 1 OK -aws_attributes.ebs_volume_iops absent 2 OK -aws_attributes.ebs_volume_size 1 2 OK -aws_attributes.ebs_volume_size 1 absent OK -aws_attributes.ebs_volume_size 2 1 OK -aws_attributes.ebs_volume_size 2 absent OK -aws_attributes.ebs_volume_size absent 1 OK -aws_attributes.ebs_volume_size absent 2 OK -aws_attributes.ebs_volume_throughput 1 2 OK -aws_attributes.ebs_volume_throughput 1 absent OK -aws_attributes.ebs_volume_throughput 2 1 OK -aws_attributes.ebs_volume_throughput 2 absent OK -aws_attributes.ebs_volume_throughput absent 1 OK -aws_attributes.ebs_volume_throughput absent 2 OK -aws_attributes.ebs_volume_type absent x OK -aws_attributes.ebs_volume_type absent y OK -aws_attributes.ebs_volume_type x absent OK -aws_attributes.ebs_volume_type x y OK -aws_attributes.ebs_volume_type y absent OK -aws_attributes.ebs_volume_type y x OK -aws_attributes.first_on_demand 1 2 OK -aws_attributes.first_on_demand 1 absent OK -aws_attributes.first_on_demand 2 1 OK -aws_attributes.first_on_demand 2 absent OK -aws_attributes.first_on_demand absent 1 OK -aws_attributes.first_on_demand absent 2 OK -aws_attributes.instance_profile_arn absent x OK -aws_attributes.instance_profile_arn absent y OK -aws_attributes.instance_profile_arn x absent OK -aws_attributes.instance_profile_arn x y OK -aws_attributes.instance_profile_arn y absent OK -aws_attributes.instance_profile_arn y x OK -aws_attributes.spot_bid_price_percent 1 2 OK -aws_attributes.spot_bid_price_percent 1 absent OK -aws_attributes.spot_bid_price_percent 2 1 OK -aws_attributes.spot_bid_price_percent 2 absent OK -aws_attributes.spot_bid_price_percent absent 1 OK -aws_attributes.spot_bid_price_percent absent 2 OK -aws_attributes.zone_id absent x OK -aws_attributes.zone_id absent y OK -aws_attributes.zone_id x absent OK -aws_attributes.zone_id x y OK -aws_attributes.zone_id y absent OK -aws_attributes.zone_id y x OK -azure_attributes.availability absent x OK -azure_attributes.availability absent y OK -azure_attributes.availability x absent OK -azure_attributes.availability x y OK -azure_attributes.availability y absent OK -azure_attributes.availability y x OK -azure_attributes.capacity_reservation_group absent x OK -azure_attributes.capacity_reservation_group absent y OK -azure_attributes.capacity_reservation_group x absent OK -azure_attributes.capacity_reservation_group x y OK -azure_attributes.capacity_reservation_group y absent OK -azure_attributes.capacity_reservation_group y x OK -azure_attributes.first_on_demand 1 2 OK -azure_attributes.first_on_demand 1 absent OK -azure_attributes.first_on_demand 2 1 OK -azure_attributes.first_on_demand 2 absent OK -azure_attributes.first_on_demand absent 1 OK -azure_attributes.first_on_demand absent 2 OK -azure_attributes.log_analytics_info.log_analytics_primary_key absent x OK -azure_attributes.log_analytics_info.log_analytics_primary_key absent y OK -azure_attributes.log_analytics_info.log_analytics_primary_key x absent OK -azure_attributes.log_analytics_info.log_analytics_primary_key x y OK -azure_attributes.log_analytics_info.log_analytics_primary_key y absent OK -azure_attributes.log_analytics_info.log_analytics_primary_key y x OK -azure_attributes.log_analytics_info.log_analytics_workspace_id absent x OK -azure_attributes.log_analytics_info.log_analytics_workspace_id absent y OK -azure_attributes.log_analytics_info.log_analytics_workspace_id x absent OK -azure_attributes.log_analytics_info.log_analytics_workspace_id x y OK -azure_attributes.log_analytics_info.log_analytics_workspace_id y absent OK -azure_attributes.log_analytics_info.log_analytics_workspace_id y x OK -azure_attributes.spot_bid_max_price 1 2 OK -azure_attributes.spot_bid_max_price 1 absent OK -azure_attributes.spot_bid_max_price 2 1 OK -azure_attributes.spot_bid_max_price 2 absent OK -azure_attributes.spot_bid_max_price absent 1 OK -azure_attributes.spot_bid_max_price absent 2 OK -cluster_log_conf.dbfs.destination x y OK -cluster_log_conf.dbfs.destination y x OK -cluster_log_conf.s3.canned_acl absent x OK -cluster_log_conf.s3.canned_acl absent y OK -cluster_log_conf.s3.canned_acl x absent OK -cluster_log_conf.s3.canned_acl x y OK -cluster_log_conf.s3.canned_acl y absent OK -cluster_log_conf.s3.canned_acl y x OK -cluster_log_conf.s3.destination x y OK -cluster_log_conf.s3.destination y x OK -cluster_log_conf.s3.enable_encryption absent false SUPPRESSED empty -cluster_log_conf.s3.enable_encryption absent true OK -cluster_log_conf.s3.enable_encryption false absent SUPPRESSED empty -cluster_log_conf.s3.enable_encryption false true OK -cluster_log_conf.s3.enable_encryption true absent OK -cluster_log_conf.s3.enable_encryption true false OK -cluster_log_conf.s3.encryption_type absent x OK -cluster_log_conf.s3.encryption_type absent y OK -cluster_log_conf.s3.encryption_type x absent OK -cluster_log_conf.s3.encryption_type x y OK -cluster_log_conf.s3.encryption_type y absent OK -cluster_log_conf.s3.encryption_type y x OK -cluster_log_conf.s3.endpoint absent x OK -cluster_log_conf.s3.endpoint absent y OK -cluster_log_conf.s3.endpoint x absent OK -cluster_log_conf.s3.endpoint x y OK -cluster_log_conf.s3.endpoint y absent OK -cluster_log_conf.s3.endpoint y x OK -cluster_log_conf.s3.kms_key absent x OK -cluster_log_conf.s3.kms_key absent y OK -cluster_log_conf.s3.kms_key x absent OK -cluster_log_conf.s3.kms_key x y OK -cluster_log_conf.s3.kms_key y absent OK -cluster_log_conf.s3.kms_key y x OK -cluster_log_conf.s3.region absent x OK -cluster_log_conf.s3.region absent y OK -cluster_log_conf.s3.region x absent OK -cluster_log_conf.s3.region x y OK -cluster_log_conf.s3.region y absent OK -cluster_log_conf.s3.region y x OK -cluster_log_conf.volumes.destination x y OK -cluster_log_conf.volumes.destination y x OK -cluster_name absent x OK -cluster_name absent y OK -cluster_name x absent OK -cluster_name x y OK -cluster_name y absent OK -cluster_name y x OK -data_security_mode absent x OK -data_security_mode absent y OK -data_security_mode x absent OK -data_security_mode x y OK -data_security_mode y absent OK -data_security_mode y x OK -dependency_mode absent x OK -dependency_mode absent y OK -dependency_mode x absent OK -dependency_mode x y OK -dependency_mode y absent OK -dependency_mode y x OK -docker_image.basic_auth.password absent x OK -docker_image.basic_auth.password absent y OK -docker_image.basic_auth.password x absent OK -docker_image.basic_auth.password x y OK -docker_image.basic_auth.password y absent OK -docker_image.basic_auth.password y x OK -docker_image.basic_auth.username absent x OK -docker_image.basic_auth.username absent y OK -docker_image.basic_auth.username x absent OK -docker_image.basic_auth.username x y OK -docker_image.basic_auth.username y absent OK -docker_image.basic_auth.username y x OK -docker_image.url absent x OK -docker_image.url absent y OK -docker_image.url x absent OK -docker_image.url x y OK -docker_image.url y absent OK -docker_image.url y x OK -driver_instance_pool_id absent x OK -driver_instance_pool_id absent y OK -driver_instance_pool_id x absent OK -driver_instance_pool_id x y OK -driver_instance_pool_id y absent OK -driver_instance_pool_id y x OK -driver_node_type_id absent x OK -driver_node_type_id absent y OK -driver_node_type_id x absent OK -driver_node_type_id x y OK -driver_node_type_id y absent OK -driver_node_type_id y x OK -enable_elastic_disk absent false SUPPRESSED remote_already_set -enable_elastic_disk absent true OK -enable_elastic_disk false absent SUPPRESSED empty -enable_elastic_disk false true OK -enable_elastic_disk true absent OK -enable_elastic_disk true false OK -enable_local_disk_encryption absent false SUPPRESSED empty -enable_local_disk_encryption absent true OK -enable_local_disk_encryption false absent SUPPRESSED empty -enable_local_disk_encryption false true OK -enable_local_disk_encryption true absent OK -enable_local_disk_encryption true false OK -gcp_attributes.availability absent x OK -gcp_attributes.availability absent y OK -gcp_attributes.availability x absent OK -gcp_attributes.availability x y OK -gcp_attributes.availability y absent OK -gcp_attributes.availability y x OK -gcp_attributes.boot_disk_size 1 2 OK -gcp_attributes.boot_disk_size 1 absent OK -gcp_attributes.boot_disk_size 2 1 OK -gcp_attributes.boot_disk_size 2 absent OK -gcp_attributes.boot_disk_size absent 1 OK -gcp_attributes.boot_disk_size absent 2 OK -gcp_attributes.confidential_compute_type absent x OK -gcp_attributes.confidential_compute_type absent y OK -gcp_attributes.confidential_compute_type x absent OK -gcp_attributes.confidential_compute_type x y OK -gcp_attributes.confidential_compute_type y absent OK -gcp_attributes.confidential_compute_type y x OK -gcp_attributes.first_on_demand 1 2 OK -gcp_attributes.first_on_demand 1 absent OK -gcp_attributes.first_on_demand 2 1 OK -gcp_attributes.first_on_demand 2 absent OK -gcp_attributes.first_on_demand absent 1 OK -gcp_attributes.first_on_demand absent 2 OK -gcp_attributes.google_service_account absent x OK -gcp_attributes.google_service_account absent y OK -gcp_attributes.google_service_account x absent OK -gcp_attributes.google_service_account x y OK -gcp_attributes.google_service_account y absent OK -gcp_attributes.google_service_account y x OK -gcp_attributes.local_ssd_count 1 2 OK -gcp_attributes.local_ssd_count 1 absent OK -gcp_attributes.local_ssd_count 2 1 OK -gcp_attributes.local_ssd_count 2 absent OK -gcp_attributes.local_ssd_count absent 1 OK -gcp_attributes.local_ssd_count absent 2 OK -gcp_attributes.use_preemptible_executors absent false SUPPRESSED empty -gcp_attributes.use_preemptible_executors absent true OK -gcp_attributes.use_preemptible_executors false absent SUPPRESSED empty -gcp_attributes.use_preemptible_executors false true OK -gcp_attributes.use_preemptible_executors true absent OK -gcp_attributes.use_preemptible_executors true false OK -gcp_attributes.zone_id absent x OK -gcp_attributes.zone_id absent y OK -gcp_attributes.zone_id x absent OK -gcp_attributes.zone_id x y OK -gcp_attributes.zone_id y absent OK -gcp_attributes.zone_id y x OK -instance_pool_id absent x OK -instance_pool_id absent y OK -instance_pool_id x absent OK -instance_pool_id x y OK -instance_pool_id y absent OK -instance_pool_id y x OK -is_single_node absent false SUPPRESSED empty -is_single_node absent true OK -is_single_node false absent SUPPRESSED empty -is_single_node false true OK -is_single_node true absent OK -is_single_node true false OK -kind absent x OK -kind absent y OK -kind x absent OK -kind x y OK -kind y absent OK -kind y x OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle.started absent false OK -lifecycle.started absent true OK -lifecycle.started false absent OK -lifecycle.started false true OK -lifecycle.started true absent OK -lifecycle.started true false OK -node_type_id absent x OK -node_type_id absent y OK -node_type_id x absent OK -node_type_id x y OK -node_type_id y absent OK -node_type_id y x OK -num_workers 1 2 OK -num_workers 1 absent OK -num_workers 2 1 OK -num_workers 2 absent OK -num_workers absent 1 OK -num_workers absent 2 OK -policy_id absent x OK -policy_id absent y OK -policy_id x absent OK -policy_id x y OK -policy_id y absent OK -policy_id y x OK -remote_disk_throughput 1 2 OK -remote_disk_throughput 1 absent OK -remote_disk_throughput 2 1 OK -remote_disk_throughput 2 absent OK -remote_disk_throughput absent 1 OK -remote_disk_throughput absent 2 OK -runtime_engine absent x OK -runtime_engine absent y OK -runtime_engine x absent OK -runtime_engine x y OK -runtime_engine y absent OK -runtime_engine y x OK -single_user_name absent x OK -single_user_name absent y OK -single_user_name x absent OK -single_user_name x y OK -single_user_name y absent OK -single_user_name y x OK -spark_version absent x OK -spark_version absent y OK -spark_version x absent OK -spark_version x y OK -spark_version y absent OK -spark_version y x OK -total_initial_remote_disk_size 1 2 OK -total_initial_remote_disk_size 1 absent OK -total_initial_remote_disk_size 2 1 OK -total_initial_remote_disk_size 2 absent OK -total_initial_remote_disk_size absent 1 OK -total_initial_remote_disk_size absent 2 OK -use_ml_runtime absent false SUPPRESSED empty -use_ml_runtime absent true OK -use_ml_runtime false absent SUPPRESSED empty -use_ml_runtime false true OK -use_ml_runtime true absent OK -use_ml_runtime true false OK -workload_type.clients.jobs absent false OK -workload_type.clients.jobs absent true OK -workload_type.clients.jobs false absent SUPPRESSED empty -workload_type.clients.jobs false true OK -workload_type.clients.jobs true absent OK -workload_type.clients.jobs true false OK -workload_type.clients.notebooks absent false SUPPRESSED empty -workload_type.clients.notebooks absent true OK -workload_type.clients.notebooks false absent SUPPRESSED empty -workload_type.clients.notebooks false true OK -workload_type.clients.notebooks true absent OK -workload_type.clients.notebooks true false OK - -=== summary -OK 686 -SUPPRESSED 34 -NOT_OBSERVABLE 24 - -=== not covered: 23 fields under a slice or map -custom_tags.* -driver_node_type_flexibility.alternate_node_type_ids[*] -init_scripts[*].abfss.destination -init_scripts[*].dbfs.destination -init_scripts[*].file.destination -init_scripts[*].gcs.destination -init_scripts[*].s3.canned_acl -init_scripts[*].s3.destination -init_scripts[*].s3.enable_encryption -init_scripts[*].s3.encryption_type -init_scripts[*].s3.endpoint -init_scripts[*].s3.kms_key -init_scripts[*].s3.region -init_scripts[*].volumes.destination -init_scripts[*].workspace.destination -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -spark_conf.* -spark_env_vars.* -ssh_public_keys[*] -worker_node_type_flexibility.alternate_node_type_ids[*] diff --git a/bundle/direct/tests/output/dashboards.full.txt b/bundle/direct/tests/output/dashboards.full.txt deleted file mode 100644 index 932c9014db4..00000000000 --- a/bundle/direct/tests/output/dashboards.full.txt +++ /dev/null @@ -1,97 +0,0 @@ -=== dashboard.yml.tmpl -create_time absent x OK -create_time absent y OK -create_time x absent OK -create_time x y OK -create_time y absent OK -create_time y x OK -dashboard_id absent x OK -dashboard_id absent y OK -dashboard_id x absent OK -dashboard_id x y OK -dashboard_id y absent OK -dashboard_id y x OK -dataset_catalog absent x OK -dataset_catalog absent y OK -dataset_catalog x absent OK -dataset_catalog x y OK -dataset_catalog y absent OK -dataset_catalog y x OK -dataset_schema absent x OK -dataset_schema absent y OK -dataset_schema x absent OK -dataset_schema x y OK -dataset_schema y absent OK -dataset_schema y x OK -display_name absent x OK -display_name absent y OK -display_name x absent POST_DEPLOY_DRIFT display_name -display_name x y OK -display_name y absent POST_DEPLOY_DRIFT display_name -display_name y x OK -embed_credentials absent false NOT_OBSERVABLE -embed_credentials absent true OK -embed_credentials false absent SUPPRESSED empty -embed_credentials false true OK -embed_credentials true absent OK -embed_credentials true false OK -etag absent x SUPPRESSED custom -etag absent y SUPPRESSED custom -etag x absent SUPPRESSED custom -etag x y SUPPRESSED custom -etag y absent SUPPRESSED custom -etag y x SUPPRESSED custom -file_path absent x NOT_OBSERVABLE -file_path absent y NOT_OBSERVABLE -file_path x absent NOT_OBSERVABLE -file_path x y NOT_OBSERVABLE -file_path y absent NOT_OBSERVABLE -file_path y x NOT_OBSERVABLE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle_state absent x OK -lifecycle_state absent y OK -lifecycle_state x absent OK -lifecycle_state x y OK -lifecycle_state y absent OK -lifecycle_state y x OK -parent_path absent x POST_DEPLOY_DRIFT parent_path -parent_path absent y POST_DEPLOY_DRIFT parent_path -parent_path x absent POST_DEPLOY_DRIFT parent_path -parent_path x y POST_DEPLOY_DRIFT parent_path -parent_path y absent POST_DEPLOY_DRIFT parent_path -parent_path y x POST_DEPLOY_DRIFT parent_path -path absent x OK -path absent y OK -path x absent OK -path x y OK -path y absent OK -path y x OK -update_time absent x OK -update_time absent y OK -update_time x absent OK -update_time x y OK -update_time y absent OK -update_time y x OK -warehouse_id absent x OK -warehouse_id absent y OK -warehouse_id x absent OK -warehouse_id x y OK -warehouse_id y absent OK -warehouse_id y x OK - -=== summary -OK 56 -SUPPRESSED 7 -NOT_OBSERVABLE 13 -POST_DEPLOY_DRIFT 8 - -=== not covered: 4 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name diff --git a/bundle/direct/tests/output/database_instances.full.txt b/bundle/direct/tests/output/database_instances.full.txt deleted file mode 100644 index 2832da52b1a..00000000000 --- a/bundle/direct/tests/output/database_instances.full.txt +++ /dev/null @@ -1,186 +0,0 @@ -=== database_instance.yml.tmpl -capacity absent x POST_DEPLOY_DRIFT capacity -capacity absent y POST_DEPLOY_DRIFT capacity -capacity x absent SUPPRESSED remote_already_set -capacity x y POST_DEPLOY_DRIFT capacity -capacity y absent SUPPRESSED remote_already_set -capacity y x POST_DEPLOY_DRIFT capacity -creation_time absent x POST_DEPLOY_DRIFT capacity -creation_time absent y POST_DEPLOY_DRIFT capacity -creation_time x absent POST_DEPLOY_DRIFT capacity -creation_time x y POST_DEPLOY_DRIFT capacity -creation_time y absent POST_DEPLOY_DRIFT capacity -creation_time y x POST_DEPLOY_DRIFT capacity -creator absent x POST_DEPLOY_DRIFT capacity -creator absent y POST_DEPLOY_DRIFT capacity -creator x absent POST_DEPLOY_DRIFT capacity -creator x y POST_DEPLOY_DRIFT capacity -creator y absent POST_DEPLOY_DRIFT capacity -creator y x POST_DEPLOY_DRIFT capacity -effective_capacity absent x POST_DEPLOY_DRIFT capacity -effective_capacity absent y POST_DEPLOY_DRIFT capacity -effective_capacity x absent POST_DEPLOY_DRIFT capacity -effective_capacity x y POST_DEPLOY_DRIFT capacity -effective_capacity y absent POST_DEPLOY_DRIFT capacity -effective_capacity y x POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login absent false SUPPRESSED empty -effective_enable_pg_native_login absent true POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login false absent POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login false true POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login true false POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries absent false SUPPRESSED empty -effective_enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries false absent POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity -effective_node_count 1 2 POST_DEPLOY_DRIFT capacity -effective_node_count 1 absent POST_DEPLOY_DRIFT capacity -effective_node_count 2 1 POST_DEPLOY_DRIFT capacity -effective_node_count 2 absent POST_DEPLOY_DRIFT capacity -effective_node_count absent 1 POST_DEPLOY_DRIFT capacity -effective_node_count absent 2 POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days 1 2 POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days 1 absent POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days 2 1 POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days 2 absent POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days absent 1 POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days absent 2 POST_DEPLOY_DRIFT capacity -effective_stopped absent false SUPPRESSED empty -effective_stopped absent true POST_DEPLOY_DRIFT capacity -effective_stopped false absent POST_DEPLOY_DRIFT capacity -effective_stopped false true POST_DEPLOY_DRIFT capacity -effective_stopped true absent POST_DEPLOY_DRIFT capacity -effective_stopped true false POST_DEPLOY_DRIFT capacity -effective_usage_policy_id absent x POST_DEPLOY_DRIFT capacity -effective_usage_policy_id absent y POST_DEPLOY_DRIFT capacity -effective_usage_policy_id x absent POST_DEPLOY_DRIFT capacity -effective_usage_policy_id x y POST_DEPLOY_DRIFT capacity -effective_usage_policy_id y absent POST_DEPLOY_DRIFT capacity -effective_usage_policy_id y x POST_DEPLOY_DRIFT capacity -enable_pg_native_login absent false SUPPRESSED empty -enable_pg_native_login absent true POST_DEPLOY_DRIFT capacity -enable_pg_native_login false absent POST_DEPLOY_DRIFT capacity -enable_pg_native_login false true POST_DEPLOY_DRIFT capacity -enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity -enable_pg_native_login true false POST_DEPLOY_DRIFT capacity -enable_readable_secondaries absent false SUPPRESSED empty -enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity -enable_readable_secondaries false absent POST_DEPLOY_DRIFT capacity -enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity -enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity -enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK_RECREATE -name y x OK_RECREATE -node_count 1 2 POST_DEPLOY_DRIFT capacity -node_count 1 absent POST_DEPLOY_DRIFT capacity -node_count 2 1 POST_DEPLOY_DRIFT capacity -node_count 2 absent POST_DEPLOY_DRIFT capacity -node_count absent 1 POST_DEPLOY_DRIFT capacity -node_count absent 2 POST_DEPLOY_DRIFT capacity -parent_instance_ref.branch_time absent x OK_RECREATE -parent_instance_ref.branch_time absent y OK_RECREATE -parent_instance_ref.branch_time x absent OK_RECREATE -parent_instance_ref.branch_time x y OK_RECREATE -parent_instance_ref.branch_time y absent OK_RECREATE -parent_instance_ref.branch_time y x OK_RECREATE -parent_instance_ref.effective_lsn absent x OK_RECREATE -parent_instance_ref.effective_lsn absent y OK_RECREATE -parent_instance_ref.effective_lsn x absent OK_RECREATE -parent_instance_ref.effective_lsn x y OK_RECREATE -parent_instance_ref.effective_lsn y absent OK_RECREATE -parent_instance_ref.effective_lsn y x OK_RECREATE -parent_instance_ref.lsn absent x OK_RECREATE -parent_instance_ref.lsn absent y OK_RECREATE -parent_instance_ref.lsn x absent OK_RECREATE -parent_instance_ref.lsn x y OK_RECREATE -parent_instance_ref.lsn y absent OK_RECREATE -parent_instance_ref.lsn y x OK_RECREATE -parent_instance_ref.name absent x OK_RECREATE -parent_instance_ref.name absent y OK_RECREATE -parent_instance_ref.name x absent OK_RECREATE -parent_instance_ref.name x y OK_RECREATE -parent_instance_ref.name y absent OK_RECREATE -parent_instance_ref.name y x OK_RECREATE -parent_instance_ref.uid absent x OK_RECREATE -parent_instance_ref.uid absent y OK_RECREATE -parent_instance_ref.uid x absent OK_RECREATE -parent_instance_ref.uid x y OK_RECREATE -parent_instance_ref.uid y absent OK_RECREATE -parent_instance_ref.uid y x OK_RECREATE -pg_version absent x POST_DEPLOY_DRIFT capacity -pg_version absent y POST_DEPLOY_DRIFT capacity -pg_version x absent POST_DEPLOY_DRIFT capacity -pg_version x y POST_DEPLOY_DRIFT capacity -pg_version y absent POST_DEPLOY_DRIFT capacity -pg_version y x POST_DEPLOY_DRIFT capacity -read_only_dns absent x POST_DEPLOY_DRIFT capacity -read_only_dns absent y POST_DEPLOY_DRIFT capacity -read_only_dns x absent POST_DEPLOY_DRIFT capacity -read_only_dns x y POST_DEPLOY_DRIFT capacity -read_only_dns y absent POST_DEPLOY_DRIFT capacity -read_only_dns y x POST_DEPLOY_DRIFT capacity -read_write_dns absent x POST_DEPLOY_DRIFT capacity -read_write_dns absent y POST_DEPLOY_DRIFT capacity -read_write_dns x absent POST_DEPLOY_DRIFT capacity -read_write_dns x y POST_DEPLOY_DRIFT capacity -read_write_dns y absent POST_DEPLOY_DRIFT capacity -read_write_dns y x POST_DEPLOY_DRIFT capacity -retention_window_in_days 1 2 POST_DEPLOY_DRIFT capacity -retention_window_in_days 1 absent POST_DEPLOY_DRIFT capacity -retention_window_in_days 2 1 POST_DEPLOY_DRIFT capacity -retention_window_in_days 2 absent POST_DEPLOY_DRIFT capacity -retention_window_in_days absent 1 POST_DEPLOY_DRIFT capacity -retention_window_in_days absent 2 POST_DEPLOY_DRIFT capacity -state absent x POST_DEPLOY_DRIFT capacity -state absent y POST_DEPLOY_DRIFT capacity -state x absent POST_DEPLOY_DRIFT capacity -state x y POST_DEPLOY_DRIFT capacity -state y absent POST_DEPLOY_DRIFT capacity -state y x POST_DEPLOY_DRIFT capacity -stopped absent false SUPPRESSED empty -stopped absent true POST_DEPLOY_DRIFT capacity -stopped false absent POST_DEPLOY_DRIFT capacity -stopped false true POST_DEPLOY_DRIFT capacity -stopped true absent POST_DEPLOY_DRIFT capacity -stopped true false POST_DEPLOY_DRIFT capacity -uid absent x POST_DEPLOY_DRIFT capacity -uid absent y POST_DEPLOY_DRIFT capacity -uid x absent POST_DEPLOY_DRIFT capacity -uid x y POST_DEPLOY_DRIFT capacity -uid y absent POST_DEPLOY_DRIFT capacity -uid y x POST_DEPLOY_DRIFT capacity -usage_policy_id absent x POST_DEPLOY_DRIFT capacity -usage_policy_id absent y POST_DEPLOY_DRIFT capacity -usage_policy_id x absent POST_DEPLOY_DRIFT capacity -usage_policy_id x y POST_DEPLOY_DRIFT capacity -usage_policy_id y absent POST_DEPLOY_DRIFT capacity -usage_policy_id y x POST_DEPLOY_DRIFT capacity - -=== summary -OK_RECREATE 32 -SUPPRESSED 8 -NOT_OBSERVABLE 6 -POST_DEPLOY_DRIFT 118 - -=== not covered: 13 fields under a slice or map -child_instance_refs[*].branch_time -child_instance_refs[*].effective_lsn -child_instance_refs[*].lsn -child_instance_refs[*].name -child_instance_refs[*].uid -custom_tags[*].key -custom_tags[*].value -effective_custom_tags[*].key -effective_custom_tags[*].value -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name diff --git a/bundle/direct/tests/output/experiments.full.txt b/bundle/direct/tests/output/experiments.full.txt deleted file mode 100644 index 7ea68aa3b68..00000000000 --- a/bundle/direct/tests/output/experiments.full.txt +++ /dev/null @@ -1,44 +0,0 @@ -=== experiment.yml.tmpl -artifact_location absent x OK_RECREATE -artifact_location absent y OK_RECREATE -artifact_location x absent OK_RECREATE -artifact_location x y OK_RECREATE -artifact_location y absent OK_RECREATE -artifact_location y x OK_RECREATE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK -name y x OK -trace_location.uc_trace_location.catalog x y OK_RECREATE -trace_location.uc_trace_location.catalog y x OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix absent x OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix absent y OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix x absent OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix x y OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix y absent OK_RECREATE -trace_location.uc_trace_location.effective_table_prefix y x OK_RECREATE -trace_location.uc_trace_location.schema x y OK_RECREATE -trace_location.uc_trace_location.schema y x OK_RECREATE -trace_location.uc_trace_location.table_prefix absent x OK_RECREATE -trace_location.uc_trace_location.table_prefix absent y OK_RECREATE -trace_location.uc_trace_location.table_prefix x absent OK_RECREATE -trace_location.uc_trace_location.table_prefix x y OK_RECREATE -trace_location.uc_trace_location.table_prefix y absent OK_RECREATE -trace_location.uc_trace_location.table_prefix y x OK_RECREATE - -=== summary -OK 2 -OK_RECREATE 22 -NOT_OBSERVABLE 6 - -=== not covered: 6 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -tags[*].key -tags[*].value diff --git a/bundle/direct/tests/output/external_locations.full.txt b/bundle/direct/tests/output/external_locations.full.txt deleted file mode 100644 index e2302246bd5..00000000000 --- a/bundle/direct/tests/output/external_locations.full.txt +++ /dev/null @@ -1,263 +0,0 @@ -=== external_location.yml.tmpl -comment absent x OK -comment absent y OK -comment x absent OK -comment x y OK -comment y absent OK -comment y x OK -credential_name x y OK_RECREATE -credential_name y x OK_RECREATE -effective_enable_file_events absent false SUPPRESSED empty -effective_enable_file_events absent true OK -effective_enable_file_events false absent SUPPRESSED empty -effective_enable_file_events false true OK -effective_enable_file_events true absent OK -effective_enable_file_events true false OK -effective_file_event_queue.managed_aqs.managed_resource_id absent x OK -effective_file_event_queue.managed_aqs.managed_resource_id absent y OK -effective_file_event_queue.managed_aqs.managed_resource_id x absent OK -effective_file_event_queue.managed_aqs.managed_resource_id x y OK -effective_file_event_queue.managed_aqs.managed_resource_id y absent OK -effective_file_event_queue.managed_aqs.managed_resource_id y x OK -effective_file_event_queue.managed_aqs.queue_url absent x OK -effective_file_event_queue.managed_aqs.queue_url absent y OK -effective_file_event_queue.managed_aqs.queue_url x absent OK -effective_file_event_queue.managed_aqs.queue_url x y OK -effective_file_event_queue.managed_aqs.queue_url y absent OK -effective_file_event_queue.managed_aqs.queue_url y x OK -effective_file_event_queue.managed_aqs.resource_group absent x OK -effective_file_event_queue.managed_aqs.resource_group absent y OK -effective_file_event_queue.managed_aqs.resource_group x absent OK -effective_file_event_queue.managed_aqs.resource_group x y OK -effective_file_event_queue.managed_aqs.resource_group y absent OK -effective_file_event_queue.managed_aqs.resource_group y x OK -effective_file_event_queue.managed_aqs.subscription_id absent x OK -effective_file_event_queue.managed_aqs.subscription_id absent y OK -effective_file_event_queue.managed_aqs.subscription_id x absent OK -effective_file_event_queue.managed_aqs.subscription_id x y OK -effective_file_event_queue.managed_aqs.subscription_id y absent OK -effective_file_event_queue.managed_aqs.subscription_id y x OK -effective_file_event_queue.managed_pubsub.managed_resource_id absent x OK -effective_file_event_queue.managed_pubsub.managed_resource_id absent y OK -effective_file_event_queue.managed_pubsub.managed_resource_id x absent OK -effective_file_event_queue.managed_pubsub.managed_resource_id x y OK -effective_file_event_queue.managed_pubsub.managed_resource_id y absent OK -effective_file_event_queue.managed_pubsub.managed_resource_id y x OK -effective_file_event_queue.managed_pubsub.subscription_name absent x OK -effective_file_event_queue.managed_pubsub.subscription_name absent y OK -effective_file_event_queue.managed_pubsub.subscription_name x absent OK -effective_file_event_queue.managed_pubsub.subscription_name x y OK -effective_file_event_queue.managed_pubsub.subscription_name y absent OK -effective_file_event_queue.managed_pubsub.subscription_name y x OK -effective_file_event_queue.managed_sqs.managed_resource_id absent x OK -effective_file_event_queue.managed_sqs.managed_resource_id absent y OK -effective_file_event_queue.managed_sqs.managed_resource_id x absent OK -effective_file_event_queue.managed_sqs.managed_resource_id x y OK -effective_file_event_queue.managed_sqs.managed_resource_id y absent OK -effective_file_event_queue.managed_sqs.managed_resource_id y x OK -effective_file_event_queue.managed_sqs.queue_url absent x OK -effective_file_event_queue.managed_sqs.queue_url absent y OK -effective_file_event_queue.managed_sqs.queue_url x absent OK -effective_file_event_queue.managed_sqs.queue_url x y OK -effective_file_event_queue.managed_sqs.queue_url y absent OK -effective_file_event_queue.managed_sqs.queue_url y x OK -effective_file_event_queue.provided_aqs.managed_resource_id absent x OK -effective_file_event_queue.provided_aqs.managed_resource_id absent y OK -effective_file_event_queue.provided_aqs.managed_resource_id x absent OK -effective_file_event_queue.provided_aqs.managed_resource_id x y OK -effective_file_event_queue.provided_aqs.managed_resource_id y absent OK -effective_file_event_queue.provided_aqs.managed_resource_id y x OK -effective_file_event_queue.provided_aqs.queue_url absent x OK -effective_file_event_queue.provided_aqs.queue_url absent y OK -effective_file_event_queue.provided_aqs.queue_url x absent OK -effective_file_event_queue.provided_aqs.queue_url x y OK -effective_file_event_queue.provided_aqs.queue_url y absent OK -effective_file_event_queue.provided_aqs.queue_url y x OK -effective_file_event_queue.provided_aqs.resource_group absent x OK -effective_file_event_queue.provided_aqs.resource_group absent y OK -effective_file_event_queue.provided_aqs.resource_group x absent OK -effective_file_event_queue.provided_aqs.resource_group x y OK -effective_file_event_queue.provided_aqs.resource_group y absent OK -effective_file_event_queue.provided_aqs.resource_group y x OK -effective_file_event_queue.provided_aqs.subscription_id absent x OK -effective_file_event_queue.provided_aqs.subscription_id absent y OK -effective_file_event_queue.provided_aqs.subscription_id x absent OK -effective_file_event_queue.provided_aqs.subscription_id x y OK -effective_file_event_queue.provided_aqs.subscription_id y absent OK -effective_file_event_queue.provided_aqs.subscription_id y x OK -effective_file_event_queue.provided_pubsub.managed_resource_id absent x OK -effective_file_event_queue.provided_pubsub.managed_resource_id absent y OK -effective_file_event_queue.provided_pubsub.managed_resource_id x absent OK -effective_file_event_queue.provided_pubsub.managed_resource_id x y OK -effective_file_event_queue.provided_pubsub.managed_resource_id y absent OK -effective_file_event_queue.provided_pubsub.managed_resource_id y x OK -effective_file_event_queue.provided_pubsub.subscription_name absent x OK -effective_file_event_queue.provided_pubsub.subscription_name absent y OK -effective_file_event_queue.provided_pubsub.subscription_name x absent OK -effective_file_event_queue.provided_pubsub.subscription_name x y OK -effective_file_event_queue.provided_pubsub.subscription_name y absent OK -effective_file_event_queue.provided_pubsub.subscription_name y x OK -effective_file_event_queue.provided_sqs.managed_resource_id absent x OK -effective_file_event_queue.provided_sqs.managed_resource_id absent y OK -effective_file_event_queue.provided_sqs.managed_resource_id x absent OK -effective_file_event_queue.provided_sqs.managed_resource_id x y OK -effective_file_event_queue.provided_sqs.managed_resource_id y absent OK -effective_file_event_queue.provided_sqs.managed_resource_id y x OK -effective_file_event_queue.provided_sqs.queue_url absent x OK -effective_file_event_queue.provided_sqs.queue_url absent y OK -effective_file_event_queue.provided_sqs.queue_url x absent OK -effective_file_event_queue.provided_sqs.queue_url x y OK -effective_file_event_queue.provided_sqs.queue_url y absent OK -effective_file_event_queue.provided_sqs.queue_url y x OK -enable_file_events absent false SUPPRESSED empty -enable_file_events absent true OK -enable_file_events false absent SUPPRESSED empty -enable_file_events false true OK -enable_file_events true absent OK -enable_file_events true false OK -encryption_details.sse_encryption_details.algorithm absent x OK_RECREATE -encryption_details.sse_encryption_details.algorithm absent y OK_RECREATE -encryption_details.sse_encryption_details.algorithm x absent OK_RECREATE -encryption_details.sse_encryption_details.algorithm x y OK_RECREATE -encryption_details.sse_encryption_details.algorithm y absent OK_RECREATE -encryption_details.sse_encryption_details.algorithm y x OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn absent x OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn absent y OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn x absent OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn x y OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn y absent OK_RECREATE -encryption_details.sse_encryption_details.aws_kms_key_arn y x OK_RECREATE -fallback absent false SUPPRESSED empty -fallback absent true OK -fallback false absent SUPPRESSED empty -fallback false true OK -fallback true absent OK -fallback true false OK -file_event_queue.managed_aqs.managed_resource_id absent x OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id absent y OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id x absent OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id x y OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id y absent OK_RECREATE -file_event_queue.managed_aqs.managed_resource_id y x OK_RECREATE -file_event_queue.managed_aqs.queue_url absent x OK_RECREATE -file_event_queue.managed_aqs.queue_url absent y OK_RECREATE -file_event_queue.managed_aqs.queue_url x absent OK_RECREATE -file_event_queue.managed_aqs.queue_url x y OK_RECREATE -file_event_queue.managed_aqs.queue_url y absent OK_RECREATE -file_event_queue.managed_aqs.queue_url y x OK_RECREATE -file_event_queue.managed_aqs.resource_group absent x OK_RECREATE -file_event_queue.managed_aqs.resource_group absent y OK_RECREATE -file_event_queue.managed_aqs.resource_group x absent OK_RECREATE -file_event_queue.managed_aqs.resource_group x y OK_RECREATE -file_event_queue.managed_aqs.resource_group y absent OK_RECREATE -file_event_queue.managed_aqs.resource_group y x OK_RECREATE -file_event_queue.managed_aqs.subscription_id absent x OK_RECREATE -file_event_queue.managed_aqs.subscription_id absent y OK_RECREATE -file_event_queue.managed_aqs.subscription_id x absent OK_RECREATE -file_event_queue.managed_aqs.subscription_id x y OK_RECREATE -file_event_queue.managed_aqs.subscription_id y absent OK_RECREATE -file_event_queue.managed_aqs.subscription_id y x OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id absent x OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id absent y OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id x absent OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id x y OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id y absent OK_RECREATE -file_event_queue.managed_pubsub.managed_resource_id y x OK_RECREATE -file_event_queue.managed_pubsub.subscription_name absent x OK_RECREATE -file_event_queue.managed_pubsub.subscription_name absent y OK_RECREATE -file_event_queue.managed_pubsub.subscription_name x absent OK_RECREATE -file_event_queue.managed_pubsub.subscription_name x y OK_RECREATE -file_event_queue.managed_pubsub.subscription_name y absent OK_RECREATE -file_event_queue.managed_pubsub.subscription_name y x OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id absent x OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id absent y OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id x absent OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id x y OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id y absent OK_RECREATE -file_event_queue.managed_sqs.managed_resource_id y x OK_RECREATE -file_event_queue.managed_sqs.queue_url absent x OK_RECREATE -file_event_queue.managed_sqs.queue_url absent y OK_RECREATE -file_event_queue.managed_sqs.queue_url x absent OK_RECREATE -file_event_queue.managed_sqs.queue_url x y OK_RECREATE -file_event_queue.managed_sqs.queue_url y absent OK_RECREATE -file_event_queue.managed_sqs.queue_url y x OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id absent x OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id absent y OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id x absent OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id x y OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id y absent OK_RECREATE -file_event_queue.provided_aqs.managed_resource_id y x OK_RECREATE -file_event_queue.provided_aqs.queue_url absent x OK_RECREATE -file_event_queue.provided_aqs.queue_url absent y OK_RECREATE -file_event_queue.provided_aqs.queue_url x absent OK_RECREATE -file_event_queue.provided_aqs.queue_url x y OK_RECREATE -file_event_queue.provided_aqs.queue_url y absent OK_RECREATE -file_event_queue.provided_aqs.queue_url y x OK_RECREATE -file_event_queue.provided_aqs.resource_group absent x OK_RECREATE -file_event_queue.provided_aqs.resource_group absent y OK_RECREATE -file_event_queue.provided_aqs.resource_group x absent OK_RECREATE -file_event_queue.provided_aqs.resource_group x y OK_RECREATE -file_event_queue.provided_aqs.resource_group y absent OK_RECREATE -file_event_queue.provided_aqs.resource_group y x OK_RECREATE -file_event_queue.provided_aqs.subscription_id absent x OK_RECREATE -file_event_queue.provided_aqs.subscription_id absent y OK_RECREATE -file_event_queue.provided_aqs.subscription_id x absent OK_RECREATE -file_event_queue.provided_aqs.subscription_id x y OK_RECREATE -file_event_queue.provided_aqs.subscription_id y absent OK_RECREATE -file_event_queue.provided_aqs.subscription_id y x OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id absent x OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id absent y OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id x absent OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id x y OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id y absent OK_RECREATE -file_event_queue.provided_pubsub.managed_resource_id y x OK_RECREATE -file_event_queue.provided_pubsub.subscription_name absent x OK_RECREATE -file_event_queue.provided_pubsub.subscription_name absent y OK_RECREATE -file_event_queue.provided_pubsub.subscription_name x absent OK_RECREATE -file_event_queue.provided_pubsub.subscription_name x y OK_RECREATE -file_event_queue.provided_pubsub.subscription_name y absent OK_RECREATE -file_event_queue.provided_pubsub.subscription_name y x OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id absent x OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id absent y OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id x absent OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id x y OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id y absent OK_RECREATE -file_event_queue.provided_sqs.managed_resource_id y x OK_RECREATE -file_event_queue.provided_sqs.queue_url absent x OK_RECREATE -file_event_queue.provided_sqs.queue_url absent y OK_RECREATE -file_event_queue.provided_sqs.queue_url x absent OK_RECREATE -file_event_queue.provided_sqs.queue_url x y OK_RECREATE -file_event_queue.provided_sqs.queue_url y absent OK_RECREATE -file_event_queue.provided_sqs.queue_url y x OK_RECREATE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK -name y x OK -read_only absent false SUPPRESSED empty -read_only absent true OK -read_only false absent SUPPRESSED empty -read_only false true OK -read_only true absent OK -read_only true false OK -skip_validation absent false SUPPRESSED empty -skip_validation absent true OK -skip_validation false absent SUPPRESSED empty -skip_validation false true OK -skip_validation true absent OK -skip_validation true false OK -url x y OK -url y x OK - -=== summary -OK 126 -OK_RECREATE 110 -SUPPRESSED 10 -NOT_OBSERVABLE 6 - -=== not covered: 2 fields under a slice or map -grants[*].principal -grants[*].privileges[*] diff --git a/bundle/direct/tests/output/genie_spaces.full.txt b/bundle/direct/tests/output/genie_spaces.full.txt deleted file mode 100644 index 39ff81224f2..00000000000 --- a/bundle/direct/tests/output/genie_spaces.full.txt +++ /dev/null @@ -1,55 +0,0 @@ -=== genie_space.yml.tmpl -description absent x OK -description absent y OK -description x absent POST_DEPLOY_DRIFT description -description x y OK -description y absent POST_DEPLOY_DRIFT description -description y x OK -etag absent x SUPPRESSED custom -etag absent y SUPPRESSED custom -etag x absent SUPPRESSED custom -etag x y SUPPRESSED custom -etag y absent SUPPRESSED custom -etag y x SUPPRESSED custom -file_path absent x NOT_OBSERVABLE -file_path absent y NOT_OBSERVABLE -file_path x absent NOT_OBSERVABLE -file_path x y NOT_OBSERVABLE -file_path y absent NOT_OBSERVABLE -file_path y x NOT_OBSERVABLE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -parent_path absent x POST_DEPLOY_DRIFT parent_path -parent_path absent y POST_DEPLOY_DRIFT parent_path -parent_path x absent POST_DEPLOY_DRIFT parent_path -parent_path x y POST_DEPLOY_DRIFT parent_path -parent_path y absent POST_DEPLOY_DRIFT parent_path -parent_path y x POST_DEPLOY_DRIFT parent_path -title absent x OK -title absent y OK -title x absent POST_DEPLOY_DRIFT title -title x y OK -title y absent POST_DEPLOY_DRIFT title -title y x OK -warehouse_id absent x OK -warehouse_id absent y OK -warehouse_id x absent POST_DEPLOY_DRIFT warehouse_id -warehouse_id x y OK -warehouse_id y absent POST_DEPLOY_DRIFT warehouse_id -warehouse_id y x OK - -=== summary -OK 12 -SUPPRESSED 6 -NOT_OBSERVABLE 12 -POST_DEPLOY_DRIFT 12 - -=== not covered: 4 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name diff --git a/bundle/direct/tests/output/instance_pools.full.txt b/bundle/direct/tests/output/instance_pools.full.txt deleted file mode 100644 index e3621fca80f..00000000000 --- a/bundle/direct/tests/output/instance_pools.full.txt +++ /dev/null @@ -1,162 +0,0 @@ -=== instance_pool.yml.tmpl -aws_attributes.availability absent x OK -aws_attributes.availability absent y OK -aws_attributes.availability x absent OK -aws_attributes.availability x y OK -aws_attributes.availability y absent OK -aws_attributes.availability y x OK -aws_attributes.instance_profile_arn absent x OK -aws_attributes.instance_profile_arn absent y OK -aws_attributes.instance_profile_arn x absent OK -aws_attributes.instance_profile_arn x y OK -aws_attributes.instance_profile_arn y absent OK -aws_attributes.instance_profile_arn y x OK -aws_attributes.spot_bid_price_percent 1 2 OK -aws_attributes.spot_bid_price_percent 1 absent OK -aws_attributes.spot_bid_price_percent 2 1 OK -aws_attributes.spot_bid_price_percent 2 absent OK -aws_attributes.spot_bid_price_percent absent 1 OK -aws_attributes.spot_bid_price_percent absent 2 OK -aws_attributes.zone_id absent x OK -aws_attributes.zone_id absent y OK -aws_attributes.zone_id x absent OK -aws_attributes.zone_id x y OK -aws_attributes.zone_id y absent OK -aws_attributes.zone_id y x OK -azure_attributes.availability absent x OK -azure_attributes.availability absent y OK -azure_attributes.availability x absent OK -azure_attributes.availability x y OK -azure_attributes.availability y absent OK -azure_attributes.availability y x OK -azure_attributes.capacity_reservation_group absent x OK -azure_attributes.capacity_reservation_group absent y OK -azure_attributes.capacity_reservation_group x absent OK -azure_attributes.capacity_reservation_group x y OK -azure_attributes.capacity_reservation_group y absent OK -azure_attributes.capacity_reservation_group y x OK -azure_attributes.spot_bid_max_price 1 2 OK -azure_attributes.spot_bid_max_price 1 absent OK -azure_attributes.spot_bid_max_price 2 1 OK -azure_attributes.spot_bid_max_price 2 absent OK -azure_attributes.spot_bid_max_price absent 1 OK -azure_attributes.spot_bid_max_price absent 2 OK -disk_spec.disk_count 1 2 OK_RECREATE -disk_spec.disk_count 1 absent OK_RECREATE -disk_spec.disk_count 2 1 OK_RECREATE -disk_spec.disk_count 2 absent OK_RECREATE -disk_spec.disk_count absent 1 OK_RECREATE -disk_spec.disk_count absent 2 OK_RECREATE -disk_spec.disk_iops 1 2 OK_RECREATE -disk_spec.disk_iops 1 absent OK_RECREATE -disk_spec.disk_iops 2 1 OK_RECREATE -disk_spec.disk_iops 2 absent OK_RECREATE -disk_spec.disk_iops absent 1 OK_RECREATE -disk_spec.disk_iops absent 2 OK_RECREATE -disk_spec.disk_size 1 2 OK_RECREATE -disk_spec.disk_size 1 absent OK_RECREATE -disk_spec.disk_size 2 1 OK_RECREATE -disk_spec.disk_size 2 absent OK_RECREATE -disk_spec.disk_size absent 1 OK_RECREATE -disk_spec.disk_size absent 2 OK_RECREATE -disk_spec.disk_throughput 1 2 OK_RECREATE -disk_spec.disk_throughput 1 absent OK_RECREATE -disk_spec.disk_throughput 2 1 OK_RECREATE -disk_spec.disk_throughput 2 absent OK_RECREATE -disk_spec.disk_throughput absent 1 OK_RECREATE -disk_spec.disk_throughput absent 2 OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type absent x OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type absent y OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type x absent OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type x y OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type y absent OK_RECREATE -disk_spec.disk_type.azure_disk_volume_type y x OK_RECREATE -disk_spec.disk_type.ebs_volume_type absent x OK_RECREATE -disk_spec.disk_type.ebs_volume_type absent y OK_RECREATE -disk_spec.disk_type.ebs_volume_type x absent OK_RECREATE -disk_spec.disk_type.ebs_volume_type x y OK_RECREATE -disk_spec.disk_type.ebs_volume_type y absent OK_RECREATE -disk_spec.disk_type.ebs_volume_type y x OK_RECREATE -enable_elastic_disk absent false SUPPRESSED empty -enable_elastic_disk absent true POST_DEPLOY_DRIFT enable_elastic_disk -enable_elastic_disk false absent SUPPRESSED remote_already_set -enable_elastic_disk false true POST_DEPLOY_DRIFT enable_elastic_disk -enable_elastic_disk true absent SUPPRESSED remote_already_set -enable_elastic_disk true false OK -gcp_attributes.gcp_availability absent x OK -gcp_attributes.gcp_availability absent y OK -gcp_attributes.gcp_availability x absent OK -gcp_attributes.gcp_availability x y OK -gcp_attributes.gcp_availability y absent OK -gcp_attributes.gcp_availability y x OK -gcp_attributes.local_ssd_count 1 2 OK -gcp_attributes.local_ssd_count 1 absent OK -gcp_attributes.local_ssd_count 2 1 OK -gcp_attributes.local_ssd_count 2 absent OK -gcp_attributes.local_ssd_count absent 1 OK -gcp_attributes.local_ssd_count absent 2 OK -gcp_attributes.zone_id absent x OK -gcp_attributes.zone_id absent y OK -gcp_attributes.zone_id x absent OK -gcp_attributes.zone_id x y OK -gcp_attributes.zone_id y absent OK -gcp_attributes.zone_id y x OK -idle_instance_autotermination_minutes 1 2 OK -idle_instance_autotermination_minutes 1 absent OK -idle_instance_autotermination_minutes 2 1 OK -idle_instance_autotermination_minutes 2 absent OK -idle_instance_autotermination_minutes absent 1 OK -idle_instance_autotermination_minutes absent 2 OK -instance_pool_name x y OK -instance_pool_name y x OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -max_capacity 1 2 OK -max_capacity 1 absent OK -max_capacity 2 1 OK -max_capacity 2 absent OK -max_capacity absent 1 OK -max_capacity absent 2 OK -min_idle_instances 1 2 OK -min_idle_instances 1 absent OK -min_idle_instances 2 1 OK -min_idle_instances 2 absent OK -min_idle_instances absent 1 OK -min_idle_instances absent 2 OK -node_type_id x y OK -node_type_id y x OK -remote_disk_throughput 1 2 OK -remote_disk_throughput 1 absent OK -remote_disk_throughput 2 1 OK -remote_disk_throughput 2 absent OK -remote_disk_throughput absent 1 OK -remote_disk_throughput absent 2 OK -total_initial_remote_disk_size 1 2 OK -total_initial_remote_disk_size 1 absent OK -total_initial_remote_disk_size 2 1 OK -total_initial_remote_disk_size 2 absent OK -total_initial_remote_disk_size absent 1 OK -total_initial_remote_disk_size absent 2 OK - -=== summary -OK 95 -OK_RECREATE 36 -SUPPRESSED 3 -NOT_OBSERVABLE 6 -POST_DEPLOY_DRIFT 2 - -=== not covered: 10 fields under a slice or map -custom_tags.* -node_type_flexibility.alternate_node_type_ids[*] -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -preloaded_docker_images[*].basic_auth.password -preloaded_docker_images[*].basic_auth.username -preloaded_docker_images[*].url -preloaded_spark_versions[*] diff --git a/bundle/direct/tests/output/jobs.full.txt b/bundle/direct/tests/output/jobs.full.txt deleted file mode 100644 index 9974341023d..00000000000 --- a/bundle/direct/tests/output/jobs.full.txt +++ /dev/null @@ -1,2055 +0,0 @@ -=== job.yml.tmpl -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent OK -budget_policy_id x y OK -budget_policy_id y absent OK -budget_policy_id y x OK -continuous.pause_status absent x OK -continuous.pause_status absent y OK -continuous.pause_status x absent OK -continuous.pause_status x y OK -continuous.pause_status y absent OK -continuous.pause_status y x OK -continuous.task_retry_mode absent x OK -continuous.task_retry_mode absent y OK -continuous.task_retry_mode x absent OK -continuous.task_retry_mode x y OK -continuous.task_retry_mode y absent OK -continuous.task_retry_mode y x OK -deployment.deployment_id absent x OK -deployment.deployment_id absent y OK -deployment.deployment_id x absent OK -deployment.deployment_id x y OK -deployment.deployment_id y absent OK -deployment.deployment_id y x OK -deployment.kind x y OK -deployment.kind y x OK -deployment.metadata_file_path absent x OK -deployment.metadata_file_path absent y OK -deployment.metadata_file_path x absent OK -deployment.metadata_file_path x y OK -deployment.metadata_file_path y absent OK -deployment.metadata_file_path y x OK -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x absent NOT_OBSERVABLE -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y absent NOT_OBSERVABLE -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -description absent x OK -description absent y OK -description x absent OK -description x y OK -description y absent OK -description y x OK -edit_mode absent x OK -edit_mode absent y OK -edit_mode x absent OK -edit_mode x y OK -edit_mode y absent OK -edit_mode y x OK -email_notifications.no_alert_for_skipped_runs absent false OK -email_notifications.no_alert_for_skipped_runs absent true OK -email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false true OK -email_notifications.no_alert_for_skipped_runs true absent OK -email_notifications.no_alert_for_skipped_runs true false OK -format MULTI_TASK SINGLE_TASK OK -format MULTI_TASK absent OK -format SINGLE_TASK MULTI_TASK OK -format SINGLE_TASK absent OK -format absent MULTI_TASK OK -format absent SINGLE_TASK OK -git_source.git_branch SKIPPED needs a coherent git_source block -git_source.git_commit SKIPPED needs a coherent git_source block -git_source.git_provider SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block -git_source.git_tag SKIPPED needs a coherent git_source block -git_source.git_url SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -max_concurrent_runs 1 5 OK -max_concurrent_runs 1 absent OK -max_concurrent_runs 5 1 OK -max_concurrent_runs 5 absent OK -max_concurrent_runs absent 1 OK -max_concurrent_runs absent 5 OK -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -notification_settings.no_alert_for_canceled_runs absent false OK -notification_settings.no_alert_for_canceled_runs absent true OK -notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false true OK -notification_settings.no_alert_for_canceled_runs true absent OK -notification_settings.no_alert_for_canceled_runs true false OK -notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent true OK -notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false true OK -notification_settings.no_alert_for_skipped_runs true absent OK -notification_settings.no_alert_for_skipped_runs true false OK -parent_path absent x OK -parent_path absent y OK -parent_path x absent OK -parent_path x y OK -parent_path y absent OK -parent_path y x OK -performance_target PERFORMANCE_OPTIMIZED STANDARD OK -performance_target PERFORMANCE_OPTIMIZED absent OK -performance_target STANDARD PERFORMANCE_OPTIMIZED OK -performance_target STANDARD absent OK -performance_target absent PERFORMANCE_OPTIMIZED OK -performance_target absent STANDARD OK -queue.enabled false true OK -queue.enabled true false OK -run_as.group_name absent x OK -run_as.group_name absent y OK -run_as.group_name x absent OK -run_as.group_name x y OK -run_as.group_name y absent OK -run_as.group_name y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -schedule.pause_status absent x OK -schedule.pause_status absent y OK -schedule.pause_status x absent OK -schedule.pause_status x y OK -schedule.pause_status y absent OK -schedule.pause_status y x OK -schedule.quartz_cron_expression x y OK -schedule.quartz_cron_expression y x OK -schedule.sql_condition.sql_query_id x y OK -schedule.sql_condition.sql_query_id y x OK -schedule.sql_condition.trigger_mode absent x OK -schedule.sql_condition.trigger_mode absent y OK -schedule.sql_condition.trigger_mode x absent OK -schedule.sql_condition.trigger_mode x y OK -schedule.sql_condition.trigger_mode y absent OK -schedule.sql_condition.trigger_mode y x OK -schedule.sql_condition.warehouse_id x y OK -schedule.sql_condition.warehouse_id y x OK -schedule.timezone_id x y OK -schedule.timezone_id y x OK -timeout_seconds 1200 600 OK -timeout_seconds 1200 absent OK -timeout_seconds 600 1200 OK -timeout_seconds 600 absent OK -timeout_seconds absent 1200 OK -timeout_seconds absent 600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK -trigger.file_arrival.url x y OK -trigger.file_arrival.url y x OK -trigger.file_arrival.wait_after_last_change_seconds 1 2 OK -trigger.file_arrival.wait_after_last_change_seconds 1 absent OK -trigger.file_arrival.wait_after_last_change_seconds 2 1 OK -trigger.file_arrival.wait_after_last_change_seconds 2 absent OK -trigger.file_arrival.wait_after_last_change_seconds absent 1 OK -trigger.file_arrival.wait_after_last_change_seconds absent 2 OK -trigger.model.condition x y OK -trigger.model.condition y x OK -trigger.model.min_time_between_triggers_seconds 1 2 OK -trigger.model.min_time_between_triggers_seconds 1 absent OK -trigger.model.min_time_between_triggers_seconds 2 1 OK -trigger.model.min_time_between_triggers_seconds 2 absent OK -trigger.model.min_time_between_triggers_seconds absent 1 OK -trigger.model.min_time_between_triggers_seconds absent 2 OK -trigger.model.securable_name absent x OK -trigger.model.securable_name absent y OK -trigger.model.securable_name x absent OK -trigger.model.securable_name x y OK -trigger.model.securable_name y absent OK -trigger.model.securable_name y x OK -trigger.model.wait_after_last_change_seconds 1 2 OK -trigger.model.wait_after_last_change_seconds 1 absent OK -trigger.model.wait_after_last_change_seconds 2 1 OK -trigger.model.wait_after_last_change_seconds 2 absent OK -trigger.model.wait_after_last_change_seconds absent 1 OK -trigger.model.wait_after_last_change_seconds absent 2 OK -trigger.pause_status absent x OK -trigger.pause_status absent y OK -trigger.pause_status x absent OK -trigger.pause_status x y OK -trigger.pause_status y absent OK -trigger.pause_status y x OK -trigger.periodic.interval 1 2 OK -trigger.periodic.interval 2 1 OK -trigger.periodic.unit x y OK -trigger.periodic.unit y x OK -trigger.sql_condition.sql_query_id x y OK -trigger.sql_condition.sql_query_id y x OK -trigger.sql_condition.trigger_mode absent x OK -trigger.sql_condition.trigger_mode absent y OK -trigger.sql_condition.trigger_mode x absent OK -trigger.sql_condition.trigger_mode x y OK -trigger.sql_condition.trigger_mode y absent OK -trigger.sql_condition.trigger_mode y x OK -trigger.sql_condition.warehouse_id x y OK -trigger.sql_condition.warehouse_id y x OK -trigger.table_update.condition absent x OK -trigger.table_update.condition absent y OK -trigger.table_update.condition x absent OK -trigger.table_update.condition x y OK -trigger.table_update.condition y absent OK -trigger.table_update.condition y x OK -trigger.table_update.min_time_between_triggers_seconds 1 2 OK -trigger.table_update.min_time_between_triggers_seconds 1 absent OK -trigger.table_update.min_time_between_triggers_seconds 2 1 OK -trigger.table_update.min_time_between_triggers_seconds 2 absent OK -trigger.table_update.min_time_between_triggers_seconds absent 1 OK -trigger.table_update.min_time_between_triggers_seconds absent 2 OK -trigger.table_update.wait_after_last_change_seconds 1 2 OK -trigger.table_update.wait_after_last_change_seconds 1 absent OK -trigger.table_update.wait_after_last_change_seconds 2 1 OK -trigger.table_update.wait_after_last_change_seconds 2 absent OK -trigger.table_update.wait_after_last_change_seconds absent 1 OK -trigger.table_update.wait_after_last_change_seconds absent 2 OK -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent OK -usage_policy_id x y OK -usage_policy_id y absent OK -usage_policy_id y x OK - -=== job_apply_policy_default_values_for_each_task.yml.tmpl -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent OK -budget_policy_id x y OK -budget_policy_id y absent OK -budget_policy_id y x OK -continuous.pause_status absent x OK -continuous.pause_status absent y OK -continuous.pause_status x absent OK -continuous.pause_status x y OK -continuous.pause_status y absent OK -continuous.pause_status y x OK -continuous.task_retry_mode absent x OK -continuous.task_retry_mode absent y OK -continuous.task_retry_mode x absent OK -continuous.task_retry_mode x y OK -continuous.task_retry_mode y absent OK -continuous.task_retry_mode y x OK -deployment.deployment_id absent x OK -deployment.deployment_id absent y OK -deployment.deployment_id x absent OK -deployment.deployment_id x y OK -deployment.deployment_id y absent OK -deployment.deployment_id y x OK -deployment.kind x y OK -deployment.kind y x OK -deployment.metadata_file_path absent x OK -deployment.metadata_file_path absent y OK -deployment.metadata_file_path x absent OK -deployment.metadata_file_path x y OK -deployment.metadata_file_path y absent OK -deployment.metadata_file_path y x OK -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x absent NOT_OBSERVABLE -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y absent NOT_OBSERVABLE -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -description absent x OK -description absent y OK -description x absent OK -description x y OK -description y absent OK -description y x OK -edit_mode absent x OK -edit_mode absent y OK -edit_mode x absent OK -edit_mode x y OK -edit_mode y absent OK -edit_mode y x OK -email_notifications.no_alert_for_skipped_runs absent false OK -email_notifications.no_alert_for_skipped_runs absent true OK -email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false true OK -email_notifications.no_alert_for_skipped_runs true absent OK -email_notifications.no_alert_for_skipped_runs true false OK -format MULTI_TASK SINGLE_TASK OK -format MULTI_TASK absent OK -format SINGLE_TASK MULTI_TASK OK -format SINGLE_TASK absent OK -format absent MULTI_TASK OK -format absent SINGLE_TASK OK -git_source.git_branch SKIPPED needs a coherent git_source block -git_source.git_commit SKIPPED needs a coherent git_source block -git_source.git_provider SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block -git_source.git_tag SKIPPED needs a coherent git_source block -git_source.git_url SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -max_concurrent_runs 1 5 OK -max_concurrent_runs 1 absent OK -max_concurrent_runs 5 1 OK -max_concurrent_runs 5 absent OK -max_concurrent_runs absent 1 OK -max_concurrent_runs absent 5 OK -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -notification_settings.no_alert_for_canceled_runs absent false OK -notification_settings.no_alert_for_canceled_runs absent true OK -notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false true OK -notification_settings.no_alert_for_canceled_runs true absent OK -notification_settings.no_alert_for_canceled_runs true false OK -notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent true OK -notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false true OK -notification_settings.no_alert_for_skipped_runs true absent OK -notification_settings.no_alert_for_skipped_runs true false OK -parent_path absent x OK -parent_path absent y OK -parent_path x absent OK -parent_path x y OK -parent_path y absent OK -parent_path y x OK -performance_target PERFORMANCE_OPTIMIZED STANDARD OK -performance_target PERFORMANCE_OPTIMIZED absent OK -performance_target STANDARD PERFORMANCE_OPTIMIZED OK -performance_target STANDARD absent OK -performance_target absent PERFORMANCE_OPTIMIZED OK -performance_target absent STANDARD OK -queue.enabled false true OK -queue.enabled true false OK -run_as.group_name absent x OK -run_as.group_name absent y OK -run_as.group_name x absent OK -run_as.group_name x y OK -run_as.group_name y absent OK -run_as.group_name y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -schedule.pause_status absent x OK -schedule.pause_status absent y OK -schedule.pause_status x absent OK -schedule.pause_status x y OK -schedule.pause_status y absent OK -schedule.pause_status y x OK -schedule.quartz_cron_expression x y OK -schedule.quartz_cron_expression y x OK -schedule.sql_condition.sql_query_id x y OK -schedule.sql_condition.sql_query_id y x OK -schedule.sql_condition.trigger_mode absent x OK -schedule.sql_condition.trigger_mode absent y OK -schedule.sql_condition.trigger_mode x absent OK -schedule.sql_condition.trigger_mode x y OK -schedule.sql_condition.trigger_mode y absent OK -schedule.sql_condition.trigger_mode y x OK -schedule.sql_condition.warehouse_id x y OK -schedule.sql_condition.warehouse_id y x OK -schedule.timezone_id x y OK -schedule.timezone_id y x OK -timeout_seconds 1200 600 OK -timeout_seconds 1200 absent OK -timeout_seconds 600 1200 OK -timeout_seconds 600 absent OK -timeout_seconds absent 1200 OK -timeout_seconds absent 600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK -trigger.file_arrival.url x y OK -trigger.file_arrival.url y x OK -trigger.file_arrival.wait_after_last_change_seconds 1 2 OK -trigger.file_arrival.wait_after_last_change_seconds 1 absent OK -trigger.file_arrival.wait_after_last_change_seconds 2 1 OK -trigger.file_arrival.wait_after_last_change_seconds 2 absent OK -trigger.file_arrival.wait_after_last_change_seconds absent 1 OK -trigger.file_arrival.wait_after_last_change_seconds absent 2 OK -trigger.model.condition x y OK -trigger.model.condition y x OK -trigger.model.min_time_between_triggers_seconds 1 2 OK -trigger.model.min_time_between_triggers_seconds 1 absent OK -trigger.model.min_time_between_triggers_seconds 2 1 OK -trigger.model.min_time_between_triggers_seconds 2 absent OK -trigger.model.min_time_between_triggers_seconds absent 1 OK -trigger.model.min_time_between_triggers_seconds absent 2 OK -trigger.model.securable_name absent x OK -trigger.model.securable_name absent y OK -trigger.model.securable_name x absent OK -trigger.model.securable_name x y OK -trigger.model.securable_name y absent OK -trigger.model.securable_name y x OK -trigger.model.wait_after_last_change_seconds 1 2 OK -trigger.model.wait_after_last_change_seconds 1 absent OK -trigger.model.wait_after_last_change_seconds 2 1 OK -trigger.model.wait_after_last_change_seconds 2 absent OK -trigger.model.wait_after_last_change_seconds absent 1 OK -trigger.model.wait_after_last_change_seconds absent 2 OK -trigger.pause_status absent x OK -trigger.pause_status absent y OK -trigger.pause_status x absent OK -trigger.pause_status x y OK -trigger.pause_status y absent OK -trigger.pause_status y x OK -trigger.periodic.interval 1 2 OK -trigger.periodic.interval 2 1 OK -trigger.periodic.unit x y OK -trigger.periodic.unit y x OK -trigger.sql_condition.sql_query_id x y OK -trigger.sql_condition.sql_query_id y x OK -trigger.sql_condition.trigger_mode absent x OK -trigger.sql_condition.trigger_mode absent y OK -trigger.sql_condition.trigger_mode x absent OK -trigger.sql_condition.trigger_mode x y OK -trigger.sql_condition.trigger_mode y absent OK -trigger.sql_condition.trigger_mode y x OK -trigger.sql_condition.warehouse_id x y OK -trigger.sql_condition.warehouse_id y x OK -trigger.table_update.condition absent x OK -trigger.table_update.condition absent y OK -trigger.table_update.condition x absent OK -trigger.table_update.condition x y OK -trigger.table_update.condition y absent OK -trigger.table_update.condition y x OK -trigger.table_update.min_time_between_triggers_seconds 1 2 OK -trigger.table_update.min_time_between_triggers_seconds 1 absent OK -trigger.table_update.min_time_between_triggers_seconds 2 1 OK -trigger.table_update.min_time_between_triggers_seconds 2 absent OK -trigger.table_update.min_time_between_triggers_seconds absent 1 OK -trigger.table_update.min_time_between_triggers_seconds absent 2 OK -trigger.table_update.wait_after_last_change_seconds 1 2 OK -trigger.table_update.wait_after_last_change_seconds 1 absent OK -trigger.table_update.wait_after_last_change_seconds 2 1 OK -trigger.table_update.wait_after_last_change_seconds 2 absent OK -trigger.table_update.wait_after_last_change_seconds absent 1 OK -trigger.table_update.wait_after_last_change_seconds absent 2 OK -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent OK -usage_policy_id x y OK -usage_policy_id y absent OK -usage_policy_id y x OK - -=== job_apply_policy_default_values_job_cluster.yml.tmpl -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent OK -budget_policy_id x y OK -budget_policy_id y absent OK -budget_policy_id y x OK -continuous.pause_status absent x OK -continuous.pause_status absent y OK -continuous.pause_status x absent OK -continuous.pause_status x y OK -continuous.pause_status y absent OK -continuous.pause_status y x OK -continuous.task_retry_mode absent x OK -continuous.task_retry_mode absent y OK -continuous.task_retry_mode x absent OK -continuous.task_retry_mode x y OK -continuous.task_retry_mode y absent OK -continuous.task_retry_mode y x OK -deployment.deployment_id absent x OK -deployment.deployment_id absent y OK -deployment.deployment_id x absent OK -deployment.deployment_id x y OK -deployment.deployment_id y absent OK -deployment.deployment_id y x OK -deployment.kind x y OK -deployment.kind y x OK -deployment.metadata_file_path absent x OK -deployment.metadata_file_path absent y OK -deployment.metadata_file_path x absent OK -deployment.metadata_file_path x y OK -deployment.metadata_file_path y absent OK -deployment.metadata_file_path y x OK -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x absent NOT_OBSERVABLE -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y absent NOT_OBSERVABLE -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -description absent x OK -description absent y OK -description x absent OK -description x y OK -description y absent OK -description y x OK -edit_mode absent x OK -edit_mode absent y OK -edit_mode x absent OK -edit_mode x y OK -edit_mode y absent OK -edit_mode y x OK -email_notifications.no_alert_for_skipped_runs absent false OK -email_notifications.no_alert_for_skipped_runs absent true OK -email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false true OK -email_notifications.no_alert_for_skipped_runs true absent OK -email_notifications.no_alert_for_skipped_runs true false OK -format MULTI_TASK SINGLE_TASK OK -format MULTI_TASK absent OK -format SINGLE_TASK MULTI_TASK OK -format SINGLE_TASK absent OK -format absent MULTI_TASK OK -format absent SINGLE_TASK OK -git_source.git_branch SKIPPED needs a coherent git_source block -git_source.git_commit SKIPPED needs a coherent git_source block -git_source.git_provider SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block -git_source.git_tag SKIPPED needs a coherent git_source block -git_source.git_url SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -max_concurrent_runs 1 5 OK -max_concurrent_runs 1 absent OK -max_concurrent_runs 5 1 OK -max_concurrent_runs 5 absent OK -max_concurrent_runs absent 1 OK -max_concurrent_runs absent 5 OK -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -notification_settings.no_alert_for_canceled_runs absent false OK -notification_settings.no_alert_for_canceled_runs absent true OK -notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false true OK -notification_settings.no_alert_for_canceled_runs true absent OK -notification_settings.no_alert_for_canceled_runs true false OK -notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent true OK -notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false true OK -notification_settings.no_alert_for_skipped_runs true absent OK -notification_settings.no_alert_for_skipped_runs true false OK -parent_path absent x OK -parent_path absent y OK -parent_path x absent OK -parent_path x y OK -parent_path y absent OK -parent_path y x OK -performance_target PERFORMANCE_OPTIMIZED STANDARD OK -performance_target PERFORMANCE_OPTIMIZED absent OK -performance_target STANDARD PERFORMANCE_OPTIMIZED OK -performance_target STANDARD absent OK -performance_target absent PERFORMANCE_OPTIMIZED OK -performance_target absent STANDARD OK -queue.enabled false true OK -queue.enabled true false OK -run_as.group_name absent x OK -run_as.group_name absent y OK -run_as.group_name x absent OK -run_as.group_name x y OK -run_as.group_name y absent OK -run_as.group_name y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -schedule.pause_status absent x OK -schedule.pause_status absent y OK -schedule.pause_status x absent OK -schedule.pause_status x y OK -schedule.pause_status y absent OK -schedule.pause_status y x OK -schedule.quartz_cron_expression x y OK -schedule.quartz_cron_expression y x OK -schedule.sql_condition.sql_query_id x y OK -schedule.sql_condition.sql_query_id y x OK -schedule.sql_condition.trigger_mode absent x OK -schedule.sql_condition.trigger_mode absent y OK -schedule.sql_condition.trigger_mode x absent OK -schedule.sql_condition.trigger_mode x y OK -schedule.sql_condition.trigger_mode y absent OK -schedule.sql_condition.trigger_mode y x OK -schedule.sql_condition.warehouse_id x y OK -schedule.sql_condition.warehouse_id y x OK -schedule.timezone_id x y OK -schedule.timezone_id y x OK -timeout_seconds 1200 600 OK -timeout_seconds 1200 absent OK -timeout_seconds 600 1200 OK -timeout_seconds 600 absent OK -timeout_seconds absent 1200 OK -timeout_seconds absent 600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK -trigger.file_arrival.url x y OK -trigger.file_arrival.url y x OK -trigger.file_arrival.wait_after_last_change_seconds 1 2 OK -trigger.file_arrival.wait_after_last_change_seconds 1 absent OK -trigger.file_arrival.wait_after_last_change_seconds 2 1 OK -trigger.file_arrival.wait_after_last_change_seconds 2 absent OK -trigger.file_arrival.wait_after_last_change_seconds absent 1 OK -trigger.file_arrival.wait_after_last_change_seconds absent 2 OK -trigger.model.condition x y OK -trigger.model.condition y x OK -trigger.model.min_time_between_triggers_seconds 1 2 OK -trigger.model.min_time_between_triggers_seconds 1 absent OK -trigger.model.min_time_between_triggers_seconds 2 1 OK -trigger.model.min_time_between_triggers_seconds 2 absent OK -trigger.model.min_time_between_triggers_seconds absent 1 OK -trigger.model.min_time_between_triggers_seconds absent 2 OK -trigger.model.securable_name absent x OK -trigger.model.securable_name absent y OK -trigger.model.securable_name x absent OK -trigger.model.securable_name x y OK -trigger.model.securable_name y absent OK -trigger.model.securable_name y x OK -trigger.model.wait_after_last_change_seconds 1 2 OK -trigger.model.wait_after_last_change_seconds 1 absent OK -trigger.model.wait_after_last_change_seconds 2 1 OK -trigger.model.wait_after_last_change_seconds 2 absent OK -trigger.model.wait_after_last_change_seconds absent 1 OK -trigger.model.wait_after_last_change_seconds absent 2 OK -trigger.pause_status absent x OK -trigger.pause_status absent y OK -trigger.pause_status x absent OK -trigger.pause_status x y OK -trigger.pause_status y absent OK -trigger.pause_status y x OK -trigger.periodic.interval 1 2 OK -trigger.periodic.interval 2 1 OK -trigger.periodic.unit x y OK -trigger.periodic.unit y x OK -trigger.sql_condition.sql_query_id x y OK -trigger.sql_condition.sql_query_id y x OK -trigger.sql_condition.trigger_mode absent x OK -trigger.sql_condition.trigger_mode absent y OK -trigger.sql_condition.trigger_mode x absent OK -trigger.sql_condition.trigger_mode x y OK -trigger.sql_condition.trigger_mode y absent OK -trigger.sql_condition.trigger_mode y x OK -trigger.sql_condition.warehouse_id x y OK -trigger.sql_condition.warehouse_id y x OK -trigger.table_update.condition absent x OK -trigger.table_update.condition absent y OK -trigger.table_update.condition x absent OK -trigger.table_update.condition x y OK -trigger.table_update.condition y absent OK -trigger.table_update.condition y x OK -trigger.table_update.min_time_between_triggers_seconds 1 2 OK -trigger.table_update.min_time_between_triggers_seconds 1 absent OK -trigger.table_update.min_time_between_triggers_seconds 2 1 OK -trigger.table_update.min_time_between_triggers_seconds 2 absent OK -trigger.table_update.min_time_between_triggers_seconds absent 1 OK -trigger.table_update.min_time_between_triggers_seconds absent 2 OK -trigger.table_update.wait_after_last_change_seconds 1 2 OK -trigger.table_update.wait_after_last_change_seconds 1 absent OK -trigger.table_update.wait_after_last_change_seconds 2 1 OK -trigger.table_update.wait_after_last_change_seconds 2 absent OK -trigger.table_update.wait_after_last_change_seconds absent 1 OK -trigger.table_update.wait_after_last_change_seconds absent 2 OK -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent OK -usage_policy_id x y OK -usage_policy_id y absent OK -usage_policy_id y x OK - -=== job_apply_policy_default_values_task_cluster.yml.tmpl -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent OK -budget_policy_id x y OK -budget_policy_id y absent OK -budget_policy_id y x OK -continuous.pause_status absent x OK -continuous.pause_status absent y OK -continuous.pause_status x absent OK -continuous.pause_status x y OK -continuous.pause_status y absent OK -continuous.pause_status y x OK -continuous.task_retry_mode absent x OK -continuous.task_retry_mode absent y OK -continuous.task_retry_mode x absent OK -continuous.task_retry_mode x y OK -continuous.task_retry_mode y absent OK -continuous.task_retry_mode y x OK -deployment.deployment_id absent x OK -deployment.deployment_id absent y OK -deployment.deployment_id x absent OK -deployment.deployment_id x y OK -deployment.deployment_id y absent OK -deployment.deployment_id y x OK -deployment.kind x y OK -deployment.kind y x OK -deployment.metadata_file_path absent x OK -deployment.metadata_file_path absent y OK -deployment.metadata_file_path x absent OK -deployment.metadata_file_path x y OK -deployment.metadata_file_path y absent OK -deployment.metadata_file_path y x OK -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x absent NOT_OBSERVABLE -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y absent NOT_OBSERVABLE -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -description absent x OK -description absent y OK -description x absent OK -description x y OK -description y absent OK -description y x OK -edit_mode absent x OK -edit_mode absent y OK -edit_mode x absent OK -edit_mode x y OK -edit_mode y absent OK -edit_mode y x OK -email_notifications.no_alert_for_skipped_runs absent false OK -email_notifications.no_alert_for_skipped_runs absent true OK -email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false true OK -email_notifications.no_alert_for_skipped_runs true absent OK -email_notifications.no_alert_for_skipped_runs true false OK -format MULTI_TASK SINGLE_TASK OK -format MULTI_TASK absent OK -format SINGLE_TASK MULTI_TASK OK -format SINGLE_TASK absent OK -format absent MULTI_TASK OK -format absent SINGLE_TASK OK -git_source.git_branch SKIPPED needs a coherent git_source block -git_source.git_commit SKIPPED needs a coherent git_source block -git_source.git_provider SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block -git_source.git_tag SKIPPED needs a coherent git_source block -git_source.git_url SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -max_concurrent_runs 1 5 OK -max_concurrent_runs 1 absent OK -max_concurrent_runs 5 1 OK -max_concurrent_runs 5 absent OK -max_concurrent_runs absent 1 OK -max_concurrent_runs absent 5 OK -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -notification_settings.no_alert_for_canceled_runs absent false OK -notification_settings.no_alert_for_canceled_runs absent true OK -notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false true OK -notification_settings.no_alert_for_canceled_runs true absent OK -notification_settings.no_alert_for_canceled_runs true false OK -notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent true OK -notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false true OK -notification_settings.no_alert_for_skipped_runs true absent OK -notification_settings.no_alert_for_skipped_runs true false OK -parent_path absent x OK -parent_path absent y OK -parent_path x absent OK -parent_path x y OK -parent_path y absent OK -parent_path y x OK -performance_target PERFORMANCE_OPTIMIZED STANDARD OK -performance_target PERFORMANCE_OPTIMIZED absent OK -performance_target STANDARD PERFORMANCE_OPTIMIZED OK -performance_target STANDARD absent OK -performance_target absent PERFORMANCE_OPTIMIZED OK -performance_target absent STANDARD OK -queue.enabled false true OK -queue.enabled true false OK -run_as.group_name absent x OK -run_as.group_name absent y OK -run_as.group_name x absent OK -run_as.group_name x y OK -run_as.group_name y absent OK -run_as.group_name y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -schedule.pause_status absent x OK -schedule.pause_status absent y OK -schedule.pause_status x absent OK -schedule.pause_status x y OK -schedule.pause_status y absent OK -schedule.pause_status y x OK -schedule.quartz_cron_expression x y OK -schedule.quartz_cron_expression y x OK -schedule.sql_condition.sql_query_id x y OK -schedule.sql_condition.sql_query_id y x OK -schedule.sql_condition.trigger_mode absent x OK -schedule.sql_condition.trigger_mode absent y OK -schedule.sql_condition.trigger_mode x absent OK -schedule.sql_condition.trigger_mode x y OK -schedule.sql_condition.trigger_mode y absent OK -schedule.sql_condition.trigger_mode y x OK -schedule.sql_condition.warehouse_id x y OK -schedule.sql_condition.warehouse_id y x OK -schedule.timezone_id x y OK -schedule.timezone_id y x OK -timeout_seconds 1200 600 OK -timeout_seconds 1200 absent OK -timeout_seconds 600 1200 OK -timeout_seconds 600 absent OK -timeout_seconds absent 1200 OK -timeout_seconds absent 600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK -trigger.file_arrival.url x y OK -trigger.file_arrival.url y x OK -trigger.file_arrival.wait_after_last_change_seconds 1 2 OK -trigger.file_arrival.wait_after_last_change_seconds 1 absent OK -trigger.file_arrival.wait_after_last_change_seconds 2 1 OK -trigger.file_arrival.wait_after_last_change_seconds 2 absent OK -trigger.file_arrival.wait_after_last_change_seconds absent 1 OK -trigger.file_arrival.wait_after_last_change_seconds absent 2 OK -trigger.model.condition x y OK -trigger.model.condition y x OK -trigger.model.min_time_between_triggers_seconds 1 2 OK -trigger.model.min_time_between_triggers_seconds 1 absent OK -trigger.model.min_time_between_triggers_seconds 2 1 OK -trigger.model.min_time_between_triggers_seconds 2 absent OK -trigger.model.min_time_between_triggers_seconds absent 1 OK -trigger.model.min_time_between_triggers_seconds absent 2 OK -trigger.model.securable_name absent x OK -trigger.model.securable_name absent y OK -trigger.model.securable_name x absent OK -trigger.model.securable_name x y OK -trigger.model.securable_name y absent OK -trigger.model.securable_name y x OK -trigger.model.wait_after_last_change_seconds 1 2 OK -trigger.model.wait_after_last_change_seconds 1 absent OK -trigger.model.wait_after_last_change_seconds 2 1 OK -trigger.model.wait_after_last_change_seconds 2 absent OK -trigger.model.wait_after_last_change_seconds absent 1 OK -trigger.model.wait_after_last_change_seconds absent 2 OK -trigger.pause_status absent x OK -trigger.pause_status absent y OK -trigger.pause_status x absent OK -trigger.pause_status x y OK -trigger.pause_status y absent OK -trigger.pause_status y x OK -trigger.periodic.interval 1 2 OK -trigger.periodic.interval 2 1 OK -trigger.periodic.unit x y OK -trigger.periodic.unit y x OK -trigger.sql_condition.sql_query_id x y OK -trigger.sql_condition.sql_query_id y x OK -trigger.sql_condition.trigger_mode absent x OK -trigger.sql_condition.trigger_mode absent y OK -trigger.sql_condition.trigger_mode x absent OK -trigger.sql_condition.trigger_mode x y OK -trigger.sql_condition.trigger_mode y absent OK -trigger.sql_condition.trigger_mode y x OK -trigger.sql_condition.warehouse_id x y OK -trigger.sql_condition.warehouse_id y x OK -trigger.table_update.condition absent x OK -trigger.table_update.condition absent y OK -trigger.table_update.condition x absent OK -trigger.table_update.condition x y OK -trigger.table_update.condition y absent OK -trigger.table_update.condition y x OK -trigger.table_update.min_time_between_triggers_seconds 1 2 OK -trigger.table_update.min_time_between_triggers_seconds 1 absent OK -trigger.table_update.min_time_between_triggers_seconds 2 1 OK -trigger.table_update.min_time_between_triggers_seconds 2 absent OK -trigger.table_update.min_time_between_triggers_seconds absent 1 OK -trigger.table_update.min_time_between_triggers_seconds absent 2 OK -trigger.table_update.wait_after_last_change_seconds 1 2 OK -trigger.table_update.wait_after_last_change_seconds 1 absent OK -trigger.table_update.wait_after_last_change_seconds 2 1 OK -trigger.table_update.wait_after_last_change_seconds 2 absent OK -trigger.table_update.wait_after_last_change_seconds absent 1 OK -trigger.table_update.wait_after_last_change_seconds absent 2 OK -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent OK -usage_policy_id x y OK -usage_policy_id y absent OK -usage_policy_id y x OK - -=== job_with_depends_on.yml.tmpl -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent OK -budget_policy_id x y OK -budget_policy_id y absent OK -budget_policy_id y x OK -continuous.pause_status absent x OK -continuous.pause_status absent y OK -continuous.pause_status x absent OK -continuous.pause_status x y OK -continuous.pause_status y absent OK -continuous.pause_status y x OK -continuous.task_retry_mode absent x OK -continuous.task_retry_mode absent y OK -continuous.task_retry_mode x absent OK -continuous.task_retry_mode x y OK -continuous.task_retry_mode y absent OK -continuous.task_retry_mode y x OK -deployment.deployment_id absent x OK -deployment.deployment_id absent y OK -deployment.deployment_id x absent OK -deployment.deployment_id x y OK -deployment.deployment_id y absent OK -deployment.deployment_id y x OK -deployment.kind x y OK -deployment.kind y x OK -deployment.metadata_file_path absent x OK -deployment.metadata_file_path absent y OK -deployment.metadata_file_path x absent OK -deployment.metadata_file_path x y OK -deployment.metadata_file_path y absent OK -deployment.metadata_file_path y x OK -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x absent NOT_OBSERVABLE -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y absent NOT_OBSERVABLE -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -description absent x OK -description absent y OK -description x absent OK -description x y OK -description y absent OK -description y x OK -edit_mode absent x OK -edit_mode absent y OK -edit_mode x absent OK -edit_mode x y OK -edit_mode y absent OK -edit_mode y x OK -email_notifications.no_alert_for_skipped_runs absent false OK -email_notifications.no_alert_for_skipped_runs absent true OK -email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false true OK -email_notifications.no_alert_for_skipped_runs true absent OK -email_notifications.no_alert_for_skipped_runs true false OK -format MULTI_TASK SINGLE_TASK OK -format MULTI_TASK absent OK -format SINGLE_TASK MULTI_TASK OK -format SINGLE_TASK absent OK -format absent MULTI_TASK OK -format absent SINGLE_TASK OK -git_source.git_branch SKIPPED needs a coherent git_source block -git_source.git_commit SKIPPED needs a coherent git_source block -git_source.git_provider SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block -git_source.git_tag SKIPPED needs a coherent git_source block -git_source.git_url SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -max_concurrent_runs 1 5 OK -max_concurrent_runs 1 absent OK -max_concurrent_runs 5 1 OK -max_concurrent_runs 5 absent OK -max_concurrent_runs absent 1 OK -max_concurrent_runs absent 5 OK -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -notification_settings.no_alert_for_canceled_runs absent false OK -notification_settings.no_alert_for_canceled_runs absent true OK -notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false true OK -notification_settings.no_alert_for_canceled_runs true absent OK -notification_settings.no_alert_for_canceled_runs true false OK -notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent true OK -notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false true OK -notification_settings.no_alert_for_skipped_runs true absent OK -notification_settings.no_alert_for_skipped_runs true false OK -parent_path absent x OK -parent_path absent y OK -parent_path x absent OK -parent_path x y OK -parent_path y absent OK -parent_path y x OK -performance_target PERFORMANCE_OPTIMIZED STANDARD OK -performance_target PERFORMANCE_OPTIMIZED absent OK -performance_target STANDARD PERFORMANCE_OPTIMIZED OK -performance_target STANDARD absent OK -performance_target absent PERFORMANCE_OPTIMIZED OK -performance_target absent STANDARD OK -queue.enabled false true OK -queue.enabled true false OK -run_as.group_name absent x OK -run_as.group_name absent y OK -run_as.group_name x absent OK -run_as.group_name x y OK -run_as.group_name y absent OK -run_as.group_name y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -schedule.pause_status absent x OK -schedule.pause_status absent y OK -schedule.pause_status x absent OK -schedule.pause_status x y OK -schedule.pause_status y absent OK -schedule.pause_status y x OK -schedule.quartz_cron_expression x y OK -schedule.quartz_cron_expression y x OK -schedule.sql_condition.sql_query_id x y OK -schedule.sql_condition.sql_query_id y x OK -schedule.sql_condition.trigger_mode absent x OK -schedule.sql_condition.trigger_mode absent y OK -schedule.sql_condition.trigger_mode x absent OK -schedule.sql_condition.trigger_mode x y OK -schedule.sql_condition.trigger_mode y absent OK -schedule.sql_condition.trigger_mode y x OK -schedule.sql_condition.warehouse_id x y OK -schedule.sql_condition.warehouse_id y x OK -schedule.timezone_id x y OK -schedule.timezone_id y x OK -timeout_seconds 1200 600 OK -timeout_seconds 1200 absent OK -timeout_seconds 600 1200 OK -timeout_seconds 600 absent OK -timeout_seconds absent 1200 OK -timeout_seconds absent 600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK -trigger.file_arrival.url x y OK -trigger.file_arrival.url y x OK -trigger.file_arrival.wait_after_last_change_seconds 1 2 OK -trigger.file_arrival.wait_after_last_change_seconds 1 absent OK -trigger.file_arrival.wait_after_last_change_seconds 2 1 OK -trigger.file_arrival.wait_after_last_change_seconds 2 absent OK -trigger.file_arrival.wait_after_last_change_seconds absent 1 OK -trigger.file_arrival.wait_after_last_change_seconds absent 2 OK -trigger.model.condition x y OK -trigger.model.condition y x OK -trigger.model.min_time_between_triggers_seconds 1 2 OK -trigger.model.min_time_between_triggers_seconds 1 absent OK -trigger.model.min_time_between_triggers_seconds 2 1 OK -trigger.model.min_time_between_triggers_seconds 2 absent OK -trigger.model.min_time_between_triggers_seconds absent 1 OK -trigger.model.min_time_between_triggers_seconds absent 2 OK -trigger.model.securable_name absent x OK -trigger.model.securable_name absent y OK -trigger.model.securable_name x absent OK -trigger.model.securable_name x y OK -trigger.model.securable_name y absent OK -trigger.model.securable_name y x OK -trigger.model.wait_after_last_change_seconds 1 2 OK -trigger.model.wait_after_last_change_seconds 1 absent OK -trigger.model.wait_after_last_change_seconds 2 1 OK -trigger.model.wait_after_last_change_seconds 2 absent OK -trigger.model.wait_after_last_change_seconds absent 1 OK -trigger.model.wait_after_last_change_seconds absent 2 OK -trigger.pause_status absent x OK -trigger.pause_status absent y OK -trigger.pause_status x absent OK -trigger.pause_status x y OK -trigger.pause_status y absent OK -trigger.pause_status y x OK -trigger.periodic.interval 1 2 OK -trigger.periodic.interval 2 1 OK -trigger.periodic.unit x y OK -trigger.periodic.unit y x OK -trigger.sql_condition.sql_query_id x y OK -trigger.sql_condition.sql_query_id y x OK -trigger.sql_condition.trigger_mode absent x OK -trigger.sql_condition.trigger_mode absent y OK -trigger.sql_condition.trigger_mode x absent OK -trigger.sql_condition.trigger_mode x y OK -trigger.sql_condition.trigger_mode y absent OK -trigger.sql_condition.trigger_mode y x OK -trigger.sql_condition.warehouse_id x y OK -trigger.sql_condition.warehouse_id y x OK -trigger.table_update.condition absent x OK -trigger.table_update.condition absent y OK -trigger.table_update.condition x absent OK -trigger.table_update.condition x y OK -trigger.table_update.condition y absent OK -trigger.table_update.condition y x OK -trigger.table_update.min_time_between_triggers_seconds 1 2 OK -trigger.table_update.min_time_between_triggers_seconds 1 absent OK -trigger.table_update.min_time_between_triggers_seconds 2 1 OK -trigger.table_update.min_time_between_triggers_seconds 2 absent OK -trigger.table_update.min_time_between_triggers_seconds absent 1 OK -trigger.table_update.min_time_between_triggers_seconds absent 2 OK -trigger.table_update.wait_after_last_change_seconds 1 2 OK -trigger.table_update.wait_after_last_change_seconds 1 absent OK -trigger.table_update.wait_after_last_change_seconds 2 1 OK -trigger.table_update.wait_after_last_change_seconds 2 absent OK -trigger.table_update.wait_after_last_change_seconds absent 1 OK -trigger.table_update.wait_after_last_change_seconds absent 2 OK -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent OK -usage_policy_id x y OK -usage_policy_id y absent OK -usage_policy_id y x OK - -=== job_with_task.yml.tmpl -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent OK -budget_policy_id x y OK -budget_policy_id y absent OK -budget_policy_id y x OK -continuous.pause_status absent x OK -continuous.pause_status absent y OK -continuous.pause_status x absent OK -continuous.pause_status x y OK -continuous.pause_status y absent OK -continuous.pause_status y x OK -continuous.task_retry_mode absent x OK -continuous.task_retry_mode absent y OK -continuous.task_retry_mode x absent OK -continuous.task_retry_mode x y OK -continuous.task_retry_mode y absent OK -continuous.task_retry_mode y x OK -deployment.deployment_id absent x OK -deployment.deployment_id absent y OK -deployment.deployment_id x absent OK -deployment.deployment_id x y OK -deployment.deployment_id y absent OK -deployment.deployment_id y x OK -deployment.kind x y OK -deployment.kind y x OK -deployment.metadata_file_path absent x OK -deployment.metadata_file_path absent y OK -deployment.metadata_file_path x absent OK -deployment.metadata_file_path x y OK -deployment.metadata_file_path y absent OK -deployment.metadata_file_path y x OK -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x absent NOT_OBSERVABLE -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y absent NOT_OBSERVABLE -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -description absent x OK -description absent y OK -description x absent OK -description x y OK -description y absent OK -description y x OK -edit_mode absent x OK -edit_mode absent y OK -edit_mode x absent OK -edit_mode x y OK -edit_mode y absent OK -edit_mode y x OK -email_notifications.no_alert_for_skipped_runs absent false OK -email_notifications.no_alert_for_skipped_runs absent true OK -email_notifications.no_alert_for_skipped_runs false absent SUPPRESSED empty -email_notifications.no_alert_for_skipped_runs false true OK -email_notifications.no_alert_for_skipped_runs true absent OK -email_notifications.no_alert_for_skipped_runs true false OK -format MULTI_TASK SINGLE_TASK OK -format MULTI_TASK absent OK -format SINGLE_TASK MULTI_TASK OK -format SINGLE_TASK absent OK -format absent MULTI_TASK OK -format absent SINGLE_TASK OK -git_source.git_branch SKIPPED needs a coherent git_source block -git_source.git_commit SKIPPED needs a coherent git_source block -git_source.git_provider SKIPPED needs a coherent git_source block -git_source.git_snapshot.used_commit SKIPPED needs a coherent git_source block -git_source.git_tag SKIPPED needs a coherent git_source block -git_source.git_url SKIPPED needs a coherent git_source block -git_source.job_source.dirty_state SKIPPED needs a coherent git_source block -git_source.job_source.import_from_git_branch SKIPPED needs a coherent git_source block -git_source.job_source.job_config_path SKIPPED needs a coherent git_source block -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -max_concurrent_runs 1 5 OK -max_concurrent_runs 1 absent OK -max_concurrent_runs 5 1 OK -max_concurrent_runs 5 absent OK -max_concurrent_runs absent 1 OK -max_concurrent_runs absent 5 OK -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -notification_settings.no_alert_for_canceled_runs absent false OK -notification_settings.no_alert_for_canceled_runs absent true OK -notification_settings.no_alert_for_canceled_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_canceled_runs false true OK -notification_settings.no_alert_for_canceled_runs true absent OK -notification_settings.no_alert_for_canceled_runs true false OK -notification_settings.no_alert_for_skipped_runs absent false SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs absent true OK -notification_settings.no_alert_for_skipped_runs false absent SUPPRESSED empty -notification_settings.no_alert_for_skipped_runs false true OK -notification_settings.no_alert_for_skipped_runs true absent OK -notification_settings.no_alert_for_skipped_runs true false OK -parent_path absent x OK -parent_path absent y OK -parent_path x absent OK -parent_path x y OK -parent_path y absent OK -parent_path y x OK -performance_target PERFORMANCE_OPTIMIZED STANDARD OK -performance_target PERFORMANCE_OPTIMIZED absent OK -performance_target STANDARD PERFORMANCE_OPTIMIZED OK -performance_target STANDARD absent OK -performance_target absent PERFORMANCE_OPTIMIZED OK -performance_target absent STANDARD OK -queue.enabled false true OK -queue.enabled true false OK -run_as.group_name absent x OK -run_as.group_name absent y OK -run_as.group_name x absent OK -run_as.group_name x y OK -run_as.group_name y absent OK -run_as.group_name y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -schedule.pause_status absent x OK -schedule.pause_status absent y OK -schedule.pause_status x absent OK -schedule.pause_status x y OK -schedule.pause_status y absent OK -schedule.pause_status y x OK -schedule.quartz_cron_expression x y OK -schedule.quartz_cron_expression y x OK -schedule.sql_condition.sql_query_id x y OK -schedule.sql_condition.sql_query_id y x OK -schedule.sql_condition.trigger_mode absent x OK -schedule.sql_condition.trigger_mode absent y OK -schedule.sql_condition.trigger_mode x absent OK -schedule.sql_condition.trigger_mode x y OK -schedule.sql_condition.trigger_mode y absent OK -schedule.sql_condition.trigger_mode y x OK -schedule.sql_condition.warehouse_id x y OK -schedule.sql_condition.warehouse_id y x OK -schedule.timezone_id x y OK -schedule.timezone_id y x OK -timeout_seconds 1200 600 OK -timeout_seconds 1200 absent OK -timeout_seconds 600 1200 OK -timeout_seconds 600 absent OK -timeout_seconds absent 1200 OK -timeout_seconds absent 600 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 2 OK -trigger.file_arrival.min_time_between_triggers_seconds 1 absent OK -trigger.file_arrival.min_time_between_triggers_seconds 2 1 OK -trigger.file_arrival.min_time_between_triggers_seconds 2 absent OK -trigger.file_arrival.min_time_between_triggers_seconds absent 1 OK -trigger.file_arrival.min_time_between_triggers_seconds absent 2 OK -trigger.file_arrival.url x y OK -trigger.file_arrival.url y x OK -trigger.file_arrival.wait_after_last_change_seconds 1 2 OK -trigger.file_arrival.wait_after_last_change_seconds 1 absent OK -trigger.file_arrival.wait_after_last_change_seconds 2 1 OK -trigger.file_arrival.wait_after_last_change_seconds 2 absent OK -trigger.file_arrival.wait_after_last_change_seconds absent 1 OK -trigger.file_arrival.wait_after_last_change_seconds absent 2 OK -trigger.model.condition x y OK -trigger.model.condition y x OK -trigger.model.min_time_between_triggers_seconds 1 2 OK -trigger.model.min_time_between_triggers_seconds 1 absent OK -trigger.model.min_time_between_triggers_seconds 2 1 OK -trigger.model.min_time_between_triggers_seconds 2 absent OK -trigger.model.min_time_between_triggers_seconds absent 1 OK -trigger.model.min_time_between_triggers_seconds absent 2 OK -trigger.model.securable_name absent x OK -trigger.model.securable_name absent y OK -trigger.model.securable_name x absent OK -trigger.model.securable_name x y OK -trigger.model.securable_name y absent OK -trigger.model.securable_name y x OK -trigger.model.wait_after_last_change_seconds 1 2 OK -trigger.model.wait_after_last_change_seconds 1 absent OK -trigger.model.wait_after_last_change_seconds 2 1 OK -trigger.model.wait_after_last_change_seconds 2 absent OK -trigger.model.wait_after_last_change_seconds absent 1 OK -trigger.model.wait_after_last_change_seconds absent 2 OK -trigger.pause_status absent x OK -trigger.pause_status absent y OK -trigger.pause_status x absent OK -trigger.pause_status x y OK -trigger.pause_status y absent OK -trigger.pause_status y x OK -trigger.periodic.interval 1 2 OK -trigger.periodic.interval 2 1 OK -trigger.periodic.unit x y OK -trigger.periodic.unit y x OK -trigger.sql_condition.sql_query_id x y OK -trigger.sql_condition.sql_query_id y x OK -trigger.sql_condition.trigger_mode absent x OK -trigger.sql_condition.trigger_mode absent y OK -trigger.sql_condition.trigger_mode x absent OK -trigger.sql_condition.trigger_mode x y OK -trigger.sql_condition.trigger_mode y absent OK -trigger.sql_condition.trigger_mode y x OK -trigger.sql_condition.warehouse_id x y OK -trigger.sql_condition.warehouse_id y x OK -trigger.table_update.condition absent x OK -trigger.table_update.condition absent y OK -trigger.table_update.condition x absent OK -trigger.table_update.condition x y OK -trigger.table_update.condition y absent OK -trigger.table_update.condition y x OK -trigger.table_update.min_time_between_triggers_seconds 1 2 OK -trigger.table_update.min_time_between_triggers_seconds 1 absent OK -trigger.table_update.min_time_between_triggers_seconds 2 1 OK -trigger.table_update.min_time_between_triggers_seconds 2 absent OK -trigger.table_update.min_time_between_triggers_seconds absent 1 OK -trigger.table_update.min_time_between_triggers_seconds absent 2 OK -trigger.table_update.wait_after_last_change_seconds 1 2 OK -trigger.table_update.wait_after_last_change_seconds 1 absent OK -trigger.table_update.wait_after_last_change_seconds 2 1 OK -trigger.table_update.wait_after_last_change_seconds 2 absent OK -trigger.table_update.wait_after_last_change_seconds absent 1 OK -trigger.table_update.wait_after_last_change_seconds absent 2 OK -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent OK -usage_policy_id x y OK -usage_policy_id y absent OK -usage_policy_id y x OK - -=== summary -OK 1272 -SUPPRESSED 48 -NOT_OBSERVABLE 48 -SKIPPED 54 - -=== not covered: 614 fields under a slice or map -email_notifications.on_duration_warning_threshold_exceeded[*] -email_notifications.on_failure[*] -email_notifications.on_start[*] -email_notifications.on_streaming_backlog_exceeded[*] -email_notifications.on_success[*] -environments[*].environment_key -environments[*].spec.base_environment -environments[*].spec.client -environments[*].spec.dependencies[*] -environments[*].spec.environment_version -environments[*].spec.java_dependencies[*] -git_source.sparse_checkout.patterns[*] -health.rules[*].metric -health.rules[*].op -health.rules[*].value -job_clusters[*].job_cluster_key -job_clusters[*].new_cluster.apply_policy_default_values -job_clusters[*].new_cluster.autoscale.max_workers -job_clusters[*].new_cluster.autoscale.min_workers -job_clusters[*].new_cluster.autotermination_minutes -job_clusters[*].new_cluster.aws_attributes.availability -job_clusters[*].new_cluster.aws_attributes.ebs_volume_count -job_clusters[*].new_cluster.aws_attributes.ebs_volume_iops -job_clusters[*].new_cluster.aws_attributes.ebs_volume_size -job_clusters[*].new_cluster.aws_attributes.ebs_volume_throughput -job_clusters[*].new_cluster.aws_attributes.ebs_volume_type -job_clusters[*].new_cluster.aws_attributes.first_on_demand -job_clusters[*].new_cluster.aws_attributes.instance_profile_arn -job_clusters[*].new_cluster.aws_attributes.spot_bid_price_percent -job_clusters[*].new_cluster.aws_attributes.zone_id -job_clusters[*].new_cluster.azure_attributes.availability -job_clusters[*].new_cluster.azure_attributes.capacity_reservation_group -job_clusters[*].new_cluster.azure_attributes.first_on_demand -job_clusters[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key -job_clusters[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id -job_clusters[*].new_cluster.azure_attributes.spot_bid_max_price -job_clusters[*].new_cluster.cluster_log_conf.dbfs.destination -job_clusters[*].new_cluster.cluster_log_conf.s3.canned_acl -job_clusters[*].new_cluster.cluster_log_conf.s3.destination -job_clusters[*].new_cluster.cluster_log_conf.s3.enable_encryption -job_clusters[*].new_cluster.cluster_log_conf.s3.encryption_type -job_clusters[*].new_cluster.cluster_log_conf.s3.endpoint -job_clusters[*].new_cluster.cluster_log_conf.s3.kms_key -job_clusters[*].new_cluster.cluster_log_conf.s3.region -job_clusters[*].new_cluster.cluster_log_conf.volumes.destination -job_clusters[*].new_cluster.cluster_name -job_clusters[*].new_cluster.custom_tags.* -job_clusters[*].new_cluster.data_security_mode -job_clusters[*].new_cluster.dependency_mode -job_clusters[*].new_cluster.docker_image.basic_auth.password -job_clusters[*].new_cluster.docker_image.basic_auth.username -job_clusters[*].new_cluster.docker_image.url -job_clusters[*].new_cluster.driver_instance_pool_id -job_clusters[*].new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] -job_clusters[*].new_cluster.driver_node_type_id -job_clusters[*].new_cluster.enable_elastic_disk -job_clusters[*].new_cluster.enable_local_disk_encryption -job_clusters[*].new_cluster.gcp_attributes.availability -job_clusters[*].new_cluster.gcp_attributes.boot_disk_size -job_clusters[*].new_cluster.gcp_attributes.confidential_compute_type -job_clusters[*].new_cluster.gcp_attributes.first_on_demand -job_clusters[*].new_cluster.gcp_attributes.google_service_account -job_clusters[*].new_cluster.gcp_attributes.local_ssd_count -job_clusters[*].new_cluster.gcp_attributes.use_preemptible_executors -job_clusters[*].new_cluster.gcp_attributes.zone_id -job_clusters[*].new_cluster.init_scripts[*].abfss.destination -job_clusters[*].new_cluster.init_scripts[*].dbfs.destination -job_clusters[*].new_cluster.init_scripts[*].file.destination -job_clusters[*].new_cluster.init_scripts[*].gcs.destination -job_clusters[*].new_cluster.init_scripts[*].s3.canned_acl -job_clusters[*].new_cluster.init_scripts[*].s3.destination -job_clusters[*].new_cluster.init_scripts[*].s3.enable_encryption -job_clusters[*].new_cluster.init_scripts[*].s3.encryption_type -job_clusters[*].new_cluster.init_scripts[*].s3.endpoint -job_clusters[*].new_cluster.init_scripts[*].s3.kms_key -job_clusters[*].new_cluster.init_scripts[*].s3.region -job_clusters[*].new_cluster.init_scripts[*].volumes.destination -job_clusters[*].new_cluster.init_scripts[*].workspace.destination -job_clusters[*].new_cluster.instance_pool_id -job_clusters[*].new_cluster.is_single_node -job_clusters[*].new_cluster.kind -job_clusters[*].new_cluster.node_type_id -job_clusters[*].new_cluster.num_workers -job_clusters[*].new_cluster.policy_id -job_clusters[*].new_cluster.remote_disk_throughput -job_clusters[*].new_cluster.runtime_engine -job_clusters[*].new_cluster.single_user_name -job_clusters[*].new_cluster.spark_conf.* -job_clusters[*].new_cluster.spark_env_vars.* -job_clusters[*].new_cluster.spark_version -job_clusters[*].new_cluster.ssh_public_keys[*] -job_clusters[*].new_cluster.total_initial_remote_disk_size -job_clusters[*].new_cluster.use_ml_runtime -job_clusters[*].new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] -job_clusters[*].new_cluster.workload_type.clients.jobs -job_clusters[*].new_cluster.workload_type.clients.notebooks -job_clusters[*].serverless_compute_id -parameters[*].default -parameters[*].name -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -tags.* -tasks[*].ai_runtime_task.code_source_path -tasks[*].ai_runtime_task.deployments[*].command_path -tasks[*].ai_runtime_task.deployments[*].compute.accelerator_count -tasks[*].ai_runtime_task.deployments[*].compute.accelerator_type -tasks[*].ai_runtime_task.deployments[*].name -tasks[*].ai_runtime_task.docker_image_url -tasks[*].ai_runtime_task.experiment -tasks[*].ai_runtime_task.mlflow_artifact_location -tasks[*].ai_runtime_task.mlflow_experiment_directory -tasks[*].ai_runtime_task.mlflow_run -tasks[*].alert_task.alert_id -tasks[*].alert_task.subscribers[*].destination_id -tasks[*].alert_task.subscribers[*].user_name -tasks[*].alert_task.warehouse_id -tasks[*].alert_task.workspace_path -tasks[*].clean_rooms_notebook_task.clean_room_name -tasks[*].clean_rooms_notebook_task.etag -tasks[*].clean_rooms_notebook_task.notebook_base_parameters.* -tasks[*].clean_rooms_notebook_task.notebook_name -tasks[*].compute.hardware_accelerator -tasks[*].condition_task.left -tasks[*].condition_task.op -tasks[*].condition_task.right -tasks[*].dashboard_task.dashboard_id -tasks[*].dashboard_task.filters.* -tasks[*].dashboard_task.subscription.custom_subject -tasks[*].dashboard_task.subscription.paused -tasks[*].dashboard_task.subscription.subscribers[*].destination_id -tasks[*].dashboard_task.subscription.subscribers[*].user_name -tasks[*].dashboard_task.warehouse_id -tasks[*].dbt_cloud_task.connection_resource_name -tasks[*].dbt_cloud_task.dbt_cloud_job_id -tasks[*].dbt_platform_task.connection_resource_name -tasks[*].dbt_platform_task.dbt_platform_job_id -tasks[*].dbt_task.catalog -tasks[*].dbt_task.commands[*] -tasks[*].dbt_task.profiles_directory -tasks[*].dbt_task.project_directory -tasks[*].dbt_task.schema -tasks[*].dbt_task.source -tasks[*].dbt_task.warehouse_id -tasks[*].depends_on[*].outcome -tasks[*].depends_on[*].task_key -tasks[*].description -tasks[*].disable_auto_optimization -tasks[*].disabled -tasks[*].email_notifications.no_alert_for_skipped_runs -tasks[*].email_notifications.on_duration_warning_threshold_exceeded[*] -tasks[*].email_notifications.on_failure[*] -tasks[*].email_notifications.on_start[*] -tasks[*].email_notifications.on_streaming_backlog_exceeded[*] -tasks[*].email_notifications.on_success[*] -tasks[*].environment_key -tasks[*].existing_cluster_id -tasks[*].for_each_task.concurrency -tasks[*].for_each_task.inputs -tasks[*].for_each_task.task.ai_runtime_task.code_source_path -tasks[*].for_each_task.task.ai_runtime_task.deployments[*].command_path -tasks[*].for_each_task.task.ai_runtime_task.deployments[*].compute.accelerator_count -tasks[*].for_each_task.task.ai_runtime_task.deployments[*].compute.accelerator_type -tasks[*].for_each_task.task.ai_runtime_task.deployments[*].name -tasks[*].for_each_task.task.ai_runtime_task.docker_image_url -tasks[*].for_each_task.task.ai_runtime_task.experiment -tasks[*].for_each_task.task.ai_runtime_task.mlflow_artifact_location -tasks[*].for_each_task.task.ai_runtime_task.mlflow_experiment_directory -tasks[*].for_each_task.task.ai_runtime_task.mlflow_run -tasks[*].for_each_task.task.alert_task.alert_id -tasks[*].for_each_task.task.alert_task.subscribers[*].destination_id -tasks[*].for_each_task.task.alert_task.subscribers[*].user_name -tasks[*].for_each_task.task.alert_task.warehouse_id -tasks[*].for_each_task.task.alert_task.workspace_path -tasks[*].for_each_task.task.clean_rooms_notebook_task.clean_room_name -tasks[*].for_each_task.task.clean_rooms_notebook_task.etag -tasks[*].for_each_task.task.clean_rooms_notebook_task.notebook_base_parameters.* -tasks[*].for_each_task.task.clean_rooms_notebook_task.notebook_name -tasks[*].for_each_task.task.compute.hardware_accelerator -tasks[*].for_each_task.task.condition_task.left -tasks[*].for_each_task.task.condition_task.op -tasks[*].for_each_task.task.condition_task.right -tasks[*].for_each_task.task.dashboard_task.dashboard_id -tasks[*].for_each_task.task.dashboard_task.filters.* -tasks[*].for_each_task.task.dashboard_task.subscription.custom_subject -tasks[*].for_each_task.task.dashboard_task.subscription.paused -tasks[*].for_each_task.task.dashboard_task.subscription.subscribers[*].destination_id -tasks[*].for_each_task.task.dashboard_task.subscription.subscribers[*].user_name -tasks[*].for_each_task.task.dashboard_task.warehouse_id -tasks[*].for_each_task.task.dbt_cloud_task.connection_resource_name -tasks[*].for_each_task.task.dbt_cloud_task.dbt_cloud_job_id -tasks[*].for_each_task.task.dbt_platform_task.connection_resource_name -tasks[*].for_each_task.task.dbt_platform_task.dbt_platform_job_id -tasks[*].for_each_task.task.dbt_task.catalog -tasks[*].for_each_task.task.dbt_task.commands[*] -tasks[*].for_each_task.task.dbt_task.profiles_directory -tasks[*].for_each_task.task.dbt_task.project_directory -tasks[*].for_each_task.task.dbt_task.schema -tasks[*].for_each_task.task.dbt_task.source -tasks[*].for_each_task.task.dbt_task.warehouse_id -tasks[*].for_each_task.task.depends_on[*].outcome -tasks[*].for_each_task.task.depends_on[*].task_key -tasks[*].for_each_task.task.description -tasks[*].for_each_task.task.disable_auto_optimization -tasks[*].for_each_task.task.disabled -tasks[*].for_each_task.task.email_notifications.no_alert_for_skipped_runs -tasks[*].for_each_task.task.email_notifications.on_duration_warning_threshold_exceeded[*] -tasks[*].for_each_task.task.email_notifications.on_failure[*] -tasks[*].for_each_task.task.email_notifications.on_start[*] -tasks[*].for_each_task.task.email_notifications.on_streaming_backlog_exceeded[*] -tasks[*].for_each_task.task.email_notifications.on_success[*] -tasks[*].for_each_task.task.environment_key -tasks[*].for_each_task.task.existing_cluster_id -tasks[*].for_each_task.task.for_each_task.concurrency -tasks[*].for_each_task.task.for_each_task.inputs -tasks[*].for_each_task.task.gen_ai_compute_task.command -tasks[*].for_each_task.task.gen_ai_compute_task.compute.gpu_node_pool_id -tasks[*].for_each_task.task.gen_ai_compute_task.compute.gpu_type -tasks[*].for_each_task.task.gen_ai_compute_task.compute.num_gpus -tasks[*].for_each_task.task.gen_ai_compute_task.dl_runtime_image -tasks[*].for_each_task.task.gen_ai_compute_task.mlflow_experiment_name -tasks[*].for_each_task.task.gen_ai_compute_task.source -tasks[*].for_each_task.task.gen_ai_compute_task.training_script_path -tasks[*].for_each_task.task.gen_ai_compute_task.yaml_parameters -tasks[*].for_each_task.task.gen_ai_compute_task.yaml_parameters_file_path -tasks[*].for_each_task.task.health.rules[*].metric -tasks[*].for_each_task.task.health.rules[*].op -tasks[*].for_each_task.task.health.rules[*].value -tasks[*].for_each_task.task.job_cluster_key -tasks[*].for_each_task.task.libraries[*].cran.package -tasks[*].for_each_task.task.libraries[*].cran.repo -tasks[*].for_each_task.task.libraries[*].egg -tasks[*].for_each_task.task.libraries[*].jar -tasks[*].for_each_task.task.libraries[*].maven.coordinates -tasks[*].for_each_task.task.libraries[*].maven.exclusions[*] -tasks[*].for_each_task.task.libraries[*].maven.repo -tasks[*].for_each_task.task.libraries[*].pypi.package -tasks[*].for_each_task.task.libraries[*].pypi.repo -tasks[*].for_each_task.task.libraries[*].requirements -tasks[*].for_each_task.task.libraries[*].whl -tasks[*].for_each_task.task.max_retries -tasks[*].for_each_task.task.min_retry_interval_millis -tasks[*].for_each_task.task.new_cluster.apply_policy_default_values -tasks[*].for_each_task.task.new_cluster.autoscale.max_workers -tasks[*].for_each_task.task.new_cluster.autoscale.min_workers -tasks[*].for_each_task.task.new_cluster.autotermination_minutes -tasks[*].for_each_task.task.new_cluster.aws_attributes.availability -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_count -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_iops -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_size -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_throughput -tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_type -tasks[*].for_each_task.task.new_cluster.aws_attributes.first_on_demand -tasks[*].for_each_task.task.new_cluster.aws_attributes.instance_profile_arn -tasks[*].for_each_task.task.new_cluster.aws_attributes.spot_bid_price_percent -tasks[*].for_each_task.task.new_cluster.aws_attributes.zone_id -tasks[*].for_each_task.task.new_cluster.azure_attributes.availability -tasks[*].for_each_task.task.new_cluster.azure_attributes.capacity_reservation_group -tasks[*].for_each_task.task.new_cluster.azure_attributes.first_on_demand -tasks[*].for_each_task.task.new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key -tasks[*].for_each_task.task.new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id -tasks[*].for_each_task.task.new_cluster.azure_attributes.spot_bid_max_price -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.dbfs.destination -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.canned_acl -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.destination -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.enable_encryption -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.encryption_type -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.endpoint -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.kms_key -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.s3.region -tasks[*].for_each_task.task.new_cluster.cluster_log_conf.volumes.destination -tasks[*].for_each_task.task.new_cluster.cluster_name -tasks[*].for_each_task.task.new_cluster.custom_tags.* -tasks[*].for_each_task.task.new_cluster.data_security_mode -tasks[*].for_each_task.task.new_cluster.dependency_mode -tasks[*].for_each_task.task.new_cluster.docker_image.basic_auth.password -tasks[*].for_each_task.task.new_cluster.docker_image.basic_auth.username -tasks[*].for_each_task.task.new_cluster.docker_image.url -tasks[*].for_each_task.task.new_cluster.driver_instance_pool_id -tasks[*].for_each_task.task.new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] -tasks[*].for_each_task.task.new_cluster.driver_node_type_id -tasks[*].for_each_task.task.new_cluster.enable_elastic_disk -tasks[*].for_each_task.task.new_cluster.enable_local_disk_encryption -tasks[*].for_each_task.task.new_cluster.gcp_attributes.availability -tasks[*].for_each_task.task.new_cluster.gcp_attributes.boot_disk_size -tasks[*].for_each_task.task.new_cluster.gcp_attributes.confidential_compute_type -tasks[*].for_each_task.task.new_cluster.gcp_attributes.first_on_demand -tasks[*].for_each_task.task.new_cluster.gcp_attributes.google_service_account -tasks[*].for_each_task.task.new_cluster.gcp_attributes.local_ssd_count -tasks[*].for_each_task.task.new_cluster.gcp_attributes.use_preemptible_executors -tasks[*].for_each_task.task.new_cluster.gcp_attributes.zone_id -tasks[*].for_each_task.task.new_cluster.init_scripts[*].abfss.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].dbfs.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].file.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].gcs.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.canned_acl -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.enable_encryption -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.encryption_type -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.endpoint -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.kms_key -tasks[*].for_each_task.task.new_cluster.init_scripts[*].s3.region -tasks[*].for_each_task.task.new_cluster.init_scripts[*].volumes.destination -tasks[*].for_each_task.task.new_cluster.init_scripts[*].workspace.destination -tasks[*].for_each_task.task.new_cluster.instance_pool_id -tasks[*].for_each_task.task.new_cluster.is_single_node -tasks[*].for_each_task.task.new_cluster.kind -tasks[*].for_each_task.task.new_cluster.node_type_id -tasks[*].for_each_task.task.new_cluster.num_workers -tasks[*].for_each_task.task.new_cluster.policy_id -tasks[*].for_each_task.task.new_cluster.remote_disk_throughput -tasks[*].for_each_task.task.new_cluster.runtime_engine -tasks[*].for_each_task.task.new_cluster.single_user_name -tasks[*].for_each_task.task.new_cluster.spark_conf.* -tasks[*].for_each_task.task.new_cluster.spark_env_vars.* -tasks[*].for_each_task.task.new_cluster.spark_version -tasks[*].for_each_task.task.new_cluster.ssh_public_keys[*] -tasks[*].for_each_task.task.new_cluster.total_initial_remote_disk_size -tasks[*].for_each_task.task.new_cluster.use_ml_runtime -tasks[*].for_each_task.task.new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] -tasks[*].for_each_task.task.new_cluster.workload_type.clients.jobs -tasks[*].for_each_task.task.new_cluster.workload_type.clients.notebooks -tasks[*].for_each_task.task.notebook_task.base_parameters.* -tasks[*].for_each_task.task.notebook_task.notebook_path -tasks[*].for_each_task.task.notebook_task.source -tasks[*].for_each_task.task.notebook_task.warehouse_id -tasks[*].for_each_task.task.notification_settings.alert_on_last_attempt -tasks[*].for_each_task.task.notification_settings.no_alert_for_canceled_runs -tasks[*].for_each_task.task.notification_settings.no_alert_for_skipped_runs -tasks[*].for_each_task.task.pipeline_task.full_refresh -tasks[*].for_each_task.task.pipeline_task.full_refresh_selection[*] -tasks[*].for_each_task.task.pipeline_task.parameters.* -tasks[*].for_each_task.task.pipeline_task.pipeline_id -tasks[*].for_each_task.task.pipeline_task.refresh_flow_selection[*] -tasks[*].for_each_task.task.pipeline_task.refresh_selection[*] -tasks[*].for_each_task.task.pipeline_task.reset_checkpoint_selection[*] -tasks[*].for_each_task.task.power_bi_task.connection_resource_name -tasks[*].for_each_task.task.power_bi_task.power_bi_model.authentication_method -tasks[*].for_each_task.task.power_bi_task.power_bi_model.model_name -tasks[*].for_each_task.task.power_bi_task.power_bi_model.overwrite_existing -tasks[*].for_each_task.task.power_bi_task.power_bi_model.storage_mode -tasks[*].for_each_task.task.power_bi_task.power_bi_model.workspace_name -tasks[*].for_each_task.task.power_bi_task.refresh_after_update -tasks[*].for_each_task.task.power_bi_task.tables[*].catalog -tasks[*].for_each_task.task.power_bi_task.tables[*].name -tasks[*].for_each_task.task.power_bi_task.tables[*].schema -tasks[*].for_each_task.task.power_bi_task.tables[*].storage_mode -tasks[*].for_each_task.task.power_bi_task.warehouse_id -tasks[*].for_each_task.task.python_operator_task.main -tasks[*].for_each_task.task.python_operator_task.parameters[*].name -tasks[*].for_each_task.task.python_operator_task.parameters[*].value -tasks[*].for_each_task.task.python_wheel_task.entry_point -tasks[*].for_each_task.task.python_wheel_task.named_parameters.* -tasks[*].for_each_task.task.python_wheel_task.package_name -tasks[*].for_each_task.task.python_wheel_task.parameters[*] -tasks[*].for_each_task.task.retry_on_timeout -tasks[*].for_each_task.task.run_if -tasks[*].for_each_task.task.run_job_task.dbt_commands[*] -tasks[*].for_each_task.task.run_job_task.jar_params[*] -tasks[*].for_each_task.task.run_job_task.job_id -tasks[*].for_each_task.task.run_job_task.job_parameters.* -tasks[*].for_each_task.task.run_job_task.notebook_params.* -tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh -tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh_selection[*] -tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_flow_selection[*] -tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_selection[*] -tasks[*].for_each_task.task.run_job_task.pipeline_params.reset_checkpoint_selection[*] -tasks[*].for_each_task.task.run_job_task.python_named_params.* -tasks[*].for_each_task.task.run_job_task.python_params[*] -tasks[*].for_each_task.task.run_job_task.spark_submit_params[*] -tasks[*].for_each_task.task.run_job_task.sql_params.* -tasks[*].for_each_task.task.spark_jar_task.jar_uri -tasks[*].for_each_task.task.spark_jar_task.main_class_name -tasks[*].for_each_task.task.spark_jar_task.parameters[*] -tasks[*].for_each_task.task.spark_jar_task.run_as_repl -tasks[*].for_each_task.task.spark_python_task.parameters[*] -tasks[*].for_each_task.task.spark_python_task.python_file -tasks[*].for_each_task.task.spark_python_task.source -tasks[*].for_each_task.task.spark_submit_task.parameters[*] -tasks[*].for_each_task.task.sql_task.alert.alert_id -tasks[*].for_each_task.task.sql_task.alert.pause_subscriptions -tasks[*].for_each_task.task.sql_task.alert.subscriptions[*].destination_id -tasks[*].for_each_task.task.sql_task.alert.subscriptions[*].user_name -tasks[*].for_each_task.task.sql_task.dashboard.custom_subject -tasks[*].for_each_task.task.sql_task.dashboard.dashboard_id -tasks[*].for_each_task.task.sql_task.dashboard.pause_subscriptions -tasks[*].for_each_task.task.sql_task.dashboard.subscriptions[*].destination_id -tasks[*].for_each_task.task.sql_task.dashboard.subscriptions[*].user_name -tasks[*].for_each_task.task.sql_task.file.path -tasks[*].for_each_task.task.sql_task.file.source -tasks[*].for_each_task.task.sql_task.parameters.* -tasks[*].for_each_task.task.sql_task.query.query_id -tasks[*].for_each_task.task.sql_task.warehouse_id -tasks[*].for_each_task.task.task_key -tasks[*].for_each_task.task.timeout_seconds -tasks[*].for_each_task.task.webhook_notifications.on_duration_warning_threshold_exceeded[*].id -tasks[*].for_each_task.task.webhook_notifications.on_failure[*].id -tasks[*].for_each_task.task.webhook_notifications.on_start[*].id -tasks[*].for_each_task.task.webhook_notifications.on_streaming_backlog_exceeded[*].id -tasks[*].for_each_task.task.webhook_notifications.on_success[*].id -tasks[*].gen_ai_compute_task.command -tasks[*].gen_ai_compute_task.compute.gpu_node_pool_id -tasks[*].gen_ai_compute_task.compute.gpu_type -tasks[*].gen_ai_compute_task.compute.num_gpus -tasks[*].gen_ai_compute_task.dl_runtime_image -tasks[*].gen_ai_compute_task.mlflow_experiment_name -tasks[*].gen_ai_compute_task.source -tasks[*].gen_ai_compute_task.training_script_path -tasks[*].gen_ai_compute_task.yaml_parameters -tasks[*].gen_ai_compute_task.yaml_parameters_file_path -tasks[*].health.rules[*].metric -tasks[*].health.rules[*].op -tasks[*].health.rules[*].value -tasks[*].job_cluster_key -tasks[*].libraries[*].cran.package -tasks[*].libraries[*].cran.repo -tasks[*].libraries[*].egg -tasks[*].libraries[*].jar -tasks[*].libraries[*].maven.coordinates -tasks[*].libraries[*].maven.exclusions[*] -tasks[*].libraries[*].maven.repo -tasks[*].libraries[*].pypi.package -tasks[*].libraries[*].pypi.repo -tasks[*].libraries[*].requirements -tasks[*].libraries[*].whl -tasks[*].max_retries -tasks[*].min_retry_interval_millis -tasks[*].new_cluster.apply_policy_default_values -tasks[*].new_cluster.autoscale.max_workers -tasks[*].new_cluster.autoscale.min_workers -tasks[*].new_cluster.autotermination_minutes -tasks[*].new_cluster.aws_attributes.availability -tasks[*].new_cluster.aws_attributes.ebs_volume_count -tasks[*].new_cluster.aws_attributes.ebs_volume_iops -tasks[*].new_cluster.aws_attributes.ebs_volume_size -tasks[*].new_cluster.aws_attributes.ebs_volume_throughput -tasks[*].new_cluster.aws_attributes.ebs_volume_type -tasks[*].new_cluster.aws_attributes.first_on_demand -tasks[*].new_cluster.aws_attributes.instance_profile_arn -tasks[*].new_cluster.aws_attributes.spot_bid_price_percent -tasks[*].new_cluster.aws_attributes.zone_id -tasks[*].new_cluster.azure_attributes.availability -tasks[*].new_cluster.azure_attributes.capacity_reservation_group -tasks[*].new_cluster.azure_attributes.first_on_demand -tasks[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_primary_key -tasks[*].new_cluster.azure_attributes.log_analytics_info.log_analytics_workspace_id -tasks[*].new_cluster.azure_attributes.spot_bid_max_price -tasks[*].new_cluster.cluster_log_conf.dbfs.destination -tasks[*].new_cluster.cluster_log_conf.s3.canned_acl -tasks[*].new_cluster.cluster_log_conf.s3.destination -tasks[*].new_cluster.cluster_log_conf.s3.enable_encryption -tasks[*].new_cluster.cluster_log_conf.s3.encryption_type -tasks[*].new_cluster.cluster_log_conf.s3.endpoint -tasks[*].new_cluster.cluster_log_conf.s3.kms_key -tasks[*].new_cluster.cluster_log_conf.s3.region -tasks[*].new_cluster.cluster_log_conf.volumes.destination -tasks[*].new_cluster.cluster_name -tasks[*].new_cluster.custom_tags.* -tasks[*].new_cluster.data_security_mode -tasks[*].new_cluster.dependency_mode -tasks[*].new_cluster.docker_image.basic_auth.password -tasks[*].new_cluster.docker_image.basic_auth.username -tasks[*].new_cluster.docker_image.url -tasks[*].new_cluster.driver_instance_pool_id -tasks[*].new_cluster.driver_node_type_flexibility.alternate_node_type_ids[*] -tasks[*].new_cluster.driver_node_type_id -tasks[*].new_cluster.enable_elastic_disk -tasks[*].new_cluster.enable_local_disk_encryption -tasks[*].new_cluster.gcp_attributes.availability -tasks[*].new_cluster.gcp_attributes.boot_disk_size -tasks[*].new_cluster.gcp_attributes.confidential_compute_type -tasks[*].new_cluster.gcp_attributes.first_on_demand -tasks[*].new_cluster.gcp_attributes.google_service_account -tasks[*].new_cluster.gcp_attributes.local_ssd_count -tasks[*].new_cluster.gcp_attributes.use_preemptible_executors -tasks[*].new_cluster.gcp_attributes.zone_id -tasks[*].new_cluster.init_scripts[*].abfss.destination -tasks[*].new_cluster.init_scripts[*].dbfs.destination -tasks[*].new_cluster.init_scripts[*].file.destination -tasks[*].new_cluster.init_scripts[*].gcs.destination -tasks[*].new_cluster.init_scripts[*].s3.canned_acl -tasks[*].new_cluster.init_scripts[*].s3.destination -tasks[*].new_cluster.init_scripts[*].s3.enable_encryption -tasks[*].new_cluster.init_scripts[*].s3.encryption_type -tasks[*].new_cluster.init_scripts[*].s3.endpoint -tasks[*].new_cluster.init_scripts[*].s3.kms_key -tasks[*].new_cluster.init_scripts[*].s3.region -tasks[*].new_cluster.init_scripts[*].volumes.destination -tasks[*].new_cluster.init_scripts[*].workspace.destination -tasks[*].new_cluster.instance_pool_id -tasks[*].new_cluster.is_single_node -tasks[*].new_cluster.kind -tasks[*].new_cluster.node_type_id -tasks[*].new_cluster.num_workers -tasks[*].new_cluster.policy_id -tasks[*].new_cluster.remote_disk_throughput -tasks[*].new_cluster.runtime_engine -tasks[*].new_cluster.single_user_name -tasks[*].new_cluster.spark_conf.* -tasks[*].new_cluster.spark_env_vars.* -tasks[*].new_cluster.spark_version -tasks[*].new_cluster.ssh_public_keys[*] -tasks[*].new_cluster.total_initial_remote_disk_size -tasks[*].new_cluster.use_ml_runtime -tasks[*].new_cluster.worker_node_type_flexibility.alternate_node_type_ids[*] -tasks[*].new_cluster.workload_type.clients.jobs -tasks[*].new_cluster.workload_type.clients.notebooks -tasks[*].notebook_task.base_parameters.* -tasks[*].notebook_task.notebook_path -tasks[*].notebook_task.source -tasks[*].notebook_task.warehouse_id -tasks[*].notification_settings.alert_on_last_attempt -tasks[*].notification_settings.no_alert_for_canceled_runs -tasks[*].notification_settings.no_alert_for_skipped_runs -tasks[*].pipeline_task.full_refresh -tasks[*].pipeline_task.full_refresh_selection[*] -tasks[*].pipeline_task.parameters.* -tasks[*].pipeline_task.pipeline_id -tasks[*].pipeline_task.refresh_flow_selection[*] -tasks[*].pipeline_task.refresh_selection[*] -tasks[*].pipeline_task.reset_checkpoint_selection[*] -tasks[*].power_bi_task.connection_resource_name -tasks[*].power_bi_task.power_bi_model.authentication_method -tasks[*].power_bi_task.power_bi_model.model_name -tasks[*].power_bi_task.power_bi_model.overwrite_existing -tasks[*].power_bi_task.power_bi_model.storage_mode -tasks[*].power_bi_task.power_bi_model.workspace_name -tasks[*].power_bi_task.refresh_after_update -tasks[*].power_bi_task.tables[*].catalog -tasks[*].power_bi_task.tables[*].name -tasks[*].power_bi_task.tables[*].schema -tasks[*].power_bi_task.tables[*].storage_mode -tasks[*].power_bi_task.warehouse_id -tasks[*].python_operator_task.main -tasks[*].python_operator_task.parameters[*].name -tasks[*].python_operator_task.parameters[*].value -tasks[*].python_wheel_task.entry_point -tasks[*].python_wheel_task.named_parameters.* -tasks[*].python_wheel_task.package_name -tasks[*].python_wheel_task.parameters[*] -tasks[*].retry_on_timeout -tasks[*].run_if -tasks[*].run_job_task.dbt_commands[*] -tasks[*].run_job_task.jar_params[*] -tasks[*].run_job_task.job_id -tasks[*].run_job_task.job_parameters.* -tasks[*].run_job_task.notebook_params.* -tasks[*].run_job_task.pipeline_params.full_refresh -tasks[*].run_job_task.pipeline_params.full_refresh_selection[*] -tasks[*].run_job_task.pipeline_params.refresh_flow_selection[*] -tasks[*].run_job_task.pipeline_params.refresh_selection[*] -tasks[*].run_job_task.pipeline_params.reset_checkpoint_selection[*] -tasks[*].run_job_task.python_named_params.* -tasks[*].run_job_task.python_params[*] -tasks[*].run_job_task.spark_submit_params[*] -tasks[*].run_job_task.sql_params.* -tasks[*].spark_jar_task.jar_uri -tasks[*].spark_jar_task.main_class_name -tasks[*].spark_jar_task.parameters[*] -tasks[*].spark_jar_task.run_as_repl -tasks[*].spark_python_task.parameters[*] -tasks[*].spark_python_task.python_file -tasks[*].spark_python_task.source -tasks[*].spark_submit_task.parameters[*] -tasks[*].sql_task.alert.alert_id -tasks[*].sql_task.alert.pause_subscriptions -tasks[*].sql_task.alert.subscriptions[*].destination_id -tasks[*].sql_task.alert.subscriptions[*].user_name -tasks[*].sql_task.dashboard.custom_subject -tasks[*].sql_task.dashboard.dashboard_id -tasks[*].sql_task.dashboard.pause_subscriptions -tasks[*].sql_task.dashboard.subscriptions[*].destination_id -tasks[*].sql_task.dashboard.subscriptions[*].user_name -tasks[*].sql_task.file.path -tasks[*].sql_task.file.source -tasks[*].sql_task.parameters.* -tasks[*].sql_task.query.query_id -tasks[*].sql_task.warehouse_id -tasks[*].task_key -tasks[*].timeout_seconds -tasks[*].webhook_notifications.on_duration_warning_threshold_exceeded[*].id -tasks[*].webhook_notifications.on_failure[*].id -tasks[*].webhook_notifications.on_start[*].id -tasks[*].webhook_notifications.on_streaming_backlog_exceeded[*].id -tasks[*].webhook_notifications.on_success[*].id -trigger.model.aliases[*] -trigger.table_update.table_names[*] -triggers[*].continuous.task_retry_mode -triggers[*].file_arrival.min_time_between_triggers_seconds -triggers[*].file_arrival.url -triggers[*].file_arrival.wait_after_last_change_seconds -triggers[*].model.aliases[*] -triggers[*].model.condition -triggers[*].model.min_time_between_triggers_seconds -triggers[*].model.securable_name -triggers[*].model.wait_after_last_change_seconds -triggers[*].pause_status -triggers[*].periodic.interval -triggers[*].periodic.unit -triggers[*].schedule.quartz_cron_expression -triggers[*].schedule.timezone_id -triggers[*].sql_condition.sql_query_id -triggers[*].sql_condition.trigger_mode -triggers[*].sql_condition.warehouse_id -triggers[*].table_update.condition -triggers[*].table_update.min_time_between_triggers_seconds -triggers[*].table_update.table_names[*] -triggers[*].table_update.wait_after_last_change_seconds -webhook_notifications.on_duration_warning_threshold_exceeded[*].id -webhook_notifications.on_failure[*].id -webhook_notifications.on_start[*].id -webhook_notifications.on_streaming_backlog_exceeded[*].id -webhook_notifications.on_success[*].id diff --git a/bundle/direct/tests/output/model_serving_endpoints.full.txt b/bundle/direct/tests/output/model_serving_endpoints.full.txt deleted file mode 100644 index e06981a668a..00000000000 --- a/bundle/direct/tests/output/model_serving_endpoints.full.txt +++ /dev/null @@ -1,334 +0,0 @@ -=== model_serving_endpoint.yml.tmpl -ai_gateway.fallback_config.enabled false true OK -ai_gateway.fallback_config.enabled true false OK -ai_gateway.guardrails.input.pii.behavior absent x OK -ai_gateway.guardrails.input.pii.behavior absent y OK -ai_gateway.guardrails.input.pii.behavior x absent OK -ai_gateway.guardrails.input.pii.behavior x y OK -ai_gateway.guardrails.input.pii.behavior y absent OK -ai_gateway.guardrails.input.pii.behavior y x OK -ai_gateway.guardrails.input.safety absent false SUPPRESSED empty -ai_gateway.guardrails.input.safety absent true OK -ai_gateway.guardrails.input.safety false absent SUPPRESSED empty -ai_gateway.guardrails.input.safety false true OK -ai_gateway.guardrails.input.safety true absent OK -ai_gateway.guardrails.input.safety true false OK -ai_gateway.guardrails.output.pii.behavior absent x OK -ai_gateway.guardrails.output.pii.behavior absent y OK -ai_gateway.guardrails.output.pii.behavior x absent OK -ai_gateway.guardrails.output.pii.behavior x y OK -ai_gateway.guardrails.output.pii.behavior y absent OK -ai_gateway.guardrails.output.pii.behavior y x OK -ai_gateway.guardrails.output.safety absent false SUPPRESSED empty -ai_gateway.guardrails.output.safety absent true OK -ai_gateway.guardrails.output.safety false absent SUPPRESSED empty -ai_gateway.guardrails.output.safety false true OK -ai_gateway.guardrails.output.safety true absent OK -ai_gateway.guardrails.output.safety true false OK -ai_gateway.inference_table_config.catalog_name absent x OK -ai_gateway.inference_table_config.catalog_name absent y OK -ai_gateway.inference_table_config.catalog_name x absent OK -ai_gateway.inference_table_config.catalog_name x y OK -ai_gateway.inference_table_config.catalog_name y absent OK -ai_gateway.inference_table_config.catalog_name y x OK -ai_gateway.inference_table_config.enabled absent false SUPPRESSED empty -ai_gateway.inference_table_config.enabled absent true OK -ai_gateway.inference_table_config.enabled false absent SUPPRESSED empty -ai_gateway.inference_table_config.enabled false true OK -ai_gateway.inference_table_config.enabled true absent OK -ai_gateway.inference_table_config.enabled true false OK -ai_gateway.inference_table_config.schema_name absent x OK -ai_gateway.inference_table_config.schema_name absent y OK -ai_gateway.inference_table_config.schema_name x absent OK -ai_gateway.inference_table_config.schema_name x y OK -ai_gateway.inference_table_config.schema_name y absent OK -ai_gateway.inference_table_config.schema_name y x OK -ai_gateway.inference_table_config.table_name_prefix absent x OK -ai_gateway.inference_table_config.table_name_prefix absent y OK -ai_gateway.inference_table_config.table_name_prefix x absent OK -ai_gateway.inference_table_config.table_name_prefix x y OK -ai_gateway.inference_table_config.table_name_prefix y absent OK -ai_gateway.inference_table_config.table_name_prefix y x OK -ai_gateway.usage_tracking_config.enabled absent false OK -ai_gateway.usage_tracking_config.enabled absent true OK -ai_gateway.usage_tracking_config.enabled false absent SUPPRESSED empty -ai_gateway.usage_tracking_config.enabled false true OK -ai_gateway.usage_tracking_config.enabled true absent OK -ai_gateway.usage_tracking_config.enabled true false OK -budget_policy_id absent x SUPPRESSED no_update_api -budget_policy_id absent y SUPPRESSED no_update_api -budget_policy_id x absent NOT_OBSERVABLE -budget_policy_id x y SUPPRESSED no_update_api -budget_policy_id y absent NOT_OBSERVABLE -budget_policy_id y x SUPPRESSED no_update_api -config.auto_capture_config.catalog_name absent x POST_DEPLOY_DRIFT config -config.auto_capture_config.catalog_name absent y OK_RECREATE -config.auto_capture_config.catalog_name x absent OK_RECREATE -config.auto_capture_config.catalog_name x y OK_RECREATE -config.auto_capture_config.catalog_name y absent OK_RECREATE -config.auto_capture_config.catalog_name y x OK_RECREATE -config.auto_capture_config.enabled absent false SUPPRESSED empty -config.auto_capture_config.enabled absent true POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled false absent POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled false true POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled true absent POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled true false POST_DEPLOY_DRIFT config -config.auto_capture_config.schema_name absent x POST_DEPLOY_DRIFT config -config.auto_capture_config.schema_name absent y OK_RECREATE -config.auto_capture_config.schema_name x absent OK_RECREATE -config.auto_capture_config.schema_name x y OK_RECREATE -config.auto_capture_config.schema_name y absent OK_RECREATE -config.auto_capture_config.schema_name y x OK_RECREATE -config.auto_capture_config.table_name_prefix absent x OK_RECREATE -config.auto_capture_config.table_name_prefix absent y OK_RECREATE -config.auto_capture_config.table_name_prefix x absent OK_RECREATE -config.auto_capture_config.table_name_prefix x y OK_RECREATE -config.auto_capture_config.table_name_prefix y absent OK_RECREATE -config.auto_capture_config.table_name_prefix y x OK_RECREATE -description absent x OK_RECREATE -description absent y OK_RECREATE -description x absent OK_RECREATE -description x y OK_RECREATE -description y absent OK_RECREATE -description y x OK_RECREATE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK_RECREATE -name y x OK_RECREATE -route_optimized absent false SUPPRESSED remote_already_set -route_optimized absent true OK_RECREATE -route_optimized false absent SUPPRESSED empty -route_optimized false true OK_RECREATE -route_optimized true absent OK_RECREATE -route_optimized true false OK_RECREATE -telemetry_config.inference_table_config.name SKIPPED needs a writable catalog and schema -telemetry_config.inference_table_config.sampling_fraction SKIPPED needs a writable catalog and schema -telemetry_config.table_names.annotations_table SKIPPED needs a writable catalog and schema -telemetry_config.table_names.logs_table SKIPPED needs a writable catalog and schema -telemetry_config.table_names.metrics_table SKIPPED needs a writable catalog and schema -telemetry_config.table_names.traces_table SKIPPED needs a writable catalog and schema -telemetry_config.telemetry_profile_id SKIPPED needs a writable catalog and schema - -=== model_serving_endpoint_telemetry.yml.tmpl -ai_gateway.fallback_config.enabled false true OK -ai_gateway.fallback_config.enabled true false OK -ai_gateway.guardrails.input.pii.behavior absent x OK -ai_gateway.guardrails.input.pii.behavior absent y OK -ai_gateway.guardrails.input.pii.behavior x absent OK -ai_gateway.guardrails.input.pii.behavior x y OK -ai_gateway.guardrails.input.pii.behavior y absent OK -ai_gateway.guardrails.input.pii.behavior y x OK -ai_gateway.guardrails.input.safety absent false SUPPRESSED empty -ai_gateway.guardrails.input.safety absent true OK -ai_gateway.guardrails.input.safety false absent SUPPRESSED empty -ai_gateway.guardrails.input.safety false true OK -ai_gateway.guardrails.input.safety true absent OK -ai_gateway.guardrails.input.safety true false OK -ai_gateway.guardrails.output.pii.behavior absent x OK -ai_gateway.guardrails.output.pii.behavior absent y OK -ai_gateway.guardrails.output.pii.behavior x absent OK -ai_gateway.guardrails.output.pii.behavior x y OK -ai_gateway.guardrails.output.pii.behavior y absent OK -ai_gateway.guardrails.output.pii.behavior y x OK -ai_gateway.guardrails.output.safety absent false SUPPRESSED empty -ai_gateway.guardrails.output.safety absent true OK -ai_gateway.guardrails.output.safety false absent SUPPRESSED empty -ai_gateway.guardrails.output.safety false true OK -ai_gateway.guardrails.output.safety true absent OK -ai_gateway.guardrails.output.safety true false OK -ai_gateway.inference_table_config.catalog_name absent x OK -ai_gateway.inference_table_config.catalog_name absent y OK -ai_gateway.inference_table_config.catalog_name x absent OK -ai_gateway.inference_table_config.catalog_name x y OK -ai_gateway.inference_table_config.catalog_name y absent OK -ai_gateway.inference_table_config.catalog_name y x OK -ai_gateway.inference_table_config.enabled absent false SUPPRESSED empty -ai_gateway.inference_table_config.enabled absent true OK -ai_gateway.inference_table_config.enabled false absent SUPPRESSED empty -ai_gateway.inference_table_config.enabled false true OK -ai_gateway.inference_table_config.enabled true absent OK -ai_gateway.inference_table_config.enabled true false OK -ai_gateway.inference_table_config.schema_name absent x OK -ai_gateway.inference_table_config.schema_name absent y OK -ai_gateway.inference_table_config.schema_name x absent OK -ai_gateway.inference_table_config.schema_name x y OK -ai_gateway.inference_table_config.schema_name y absent OK -ai_gateway.inference_table_config.schema_name y x OK -ai_gateway.inference_table_config.table_name_prefix absent x OK -ai_gateway.inference_table_config.table_name_prefix absent y OK -ai_gateway.inference_table_config.table_name_prefix x absent OK -ai_gateway.inference_table_config.table_name_prefix x y OK -ai_gateway.inference_table_config.table_name_prefix y absent OK -ai_gateway.inference_table_config.table_name_prefix y x OK -ai_gateway.usage_tracking_config.enabled absent false OK -ai_gateway.usage_tracking_config.enabled absent true OK -ai_gateway.usage_tracking_config.enabled false absent SUPPRESSED empty -ai_gateway.usage_tracking_config.enabled false true OK -ai_gateway.usage_tracking_config.enabled true absent OK -ai_gateway.usage_tracking_config.enabled true false OK -budget_policy_id absent x SUPPRESSED no_update_api -budget_policy_id absent y SUPPRESSED no_update_api -budget_policy_id x absent NOT_OBSERVABLE -budget_policy_id x y SUPPRESSED no_update_api -budget_policy_id y absent NOT_OBSERVABLE -budget_policy_id y x SUPPRESSED no_update_api -config.auto_capture_config.catalog_name absent x OK -config.auto_capture_config.catalog_name absent y OK_RECREATE -config.auto_capture_config.catalog_name x absent OK_RECREATE -config.auto_capture_config.catalog_name x y OK_RECREATE -config.auto_capture_config.catalog_name y absent OK_RECREATE -config.auto_capture_config.catalog_name y x OK_RECREATE -config.auto_capture_config.enabled absent false SUPPRESSED empty -config.auto_capture_config.enabled absent true OK -config.auto_capture_config.enabled false absent SUPPRESSED empty -config.auto_capture_config.enabled false true OK -config.auto_capture_config.enabled true absent OK -config.auto_capture_config.enabled true false OK -config.auto_capture_config.schema_name absent x OK_RECREATE -config.auto_capture_config.schema_name absent y OK_RECREATE -config.auto_capture_config.schema_name x absent OK_RECREATE -config.auto_capture_config.schema_name x y OK_RECREATE -config.auto_capture_config.schema_name y absent OK_RECREATE -config.auto_capture_config.schema_name y x OK_RECREATE -config.auto_capture_config.table_name_prefix absent x OK_RECREATE -config.auto_capture_config.table_name_prefix absent y OK_RECREATE -config.auto_capture_config.table_name_prefix x absent OK_RECREATE -config.auto_capture_config.table_name_prefix x y OK_RECREATE -config.auto_capture_config.table_name_prefix y absent OK_RECREATE -config.auto_capture_config.table_name_prefix y x OK_RECREATE -description absent x OK_RECREATE -description absent y OK_RECREATE -description x absent OK_RECREATE -description x y OK_RECREATE -description y absent OK_RECREATE -description y x OK_RECREATE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK_RECREATE -name y x OK_RECREATE -route_optimized absent false SUPPRESSED remote_already_set -route_optimized absent true OK_RECREATE -route_optimized false absent SUPPRESSED empty -route_optimized false true OK_RECREATE -route_optimized true absent OK_RECREATE -route_optimized true false OK_RECREATE -telemetry_config.inference_table_config.name SKIPPED needs a writable catalog and schema -telemetry_config.inference_table_config.sampling_fraction SKIPPED needs a writable catalog and schema -telemetry_config.table_names.annotations_table SKIPPED needs a writable catalog and schema -telemetry_config.table_names.logs_table SKIPPED needs a writable catalog and schema -telemetry_config.table_names.metrics_table SKIPPED needs a writable catalog and schema -telemetry_config.table_names.traces_table SKIPPED needs a writable catalog and schema -telemetry_config.telemetry_profile_id SKIPPED needs a writable catalog and schema - -=== summary -OK 103 -OK_RECREATE 57 -SUPPRESSED 29 -NOT_OBSERVABLE 16 -POST_DEPLOY_DRIFT 7 -SKIPPED 14 - -=== not covered: 95 fields under a slice or map -ai_gateway.guardrails.input.invalid_keywords[*] -ai_gateway.guardrails.input.valid_topics[*] -ai_gateway.guardrails.output.invalid_keywords[*] -ai_gateway.guardrails.output.valid_topics[*] -ai_gateway.rate_limits[*].calls -ai_gateway.rate_limits[*].key -ai_gateway.rate_limits[*].principal -ai_gateway.rate_limits[*].renewal_period -ai_gateway.rate_limits[*].tokens -config.served_entities[*].burst_scaling_enabled -config.served_entities[*].entity_name -config.served_entities[*].entity_version -config.served_entities[*].environment_vars.* -config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key -config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext -config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id -config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id_plaintext -config.served_entities[*].external_model.amazon_bedrock_config.aws_region -config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key -config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key_plaintext -config.served_entities[*].external_model.amazon_bedrock_config.bedrock_provider -config.served_entities[*].external_model.amazon_bedrock_config.instance_profile_arn -config.served_entities[*].external_model.anthropic_config.anthropic_api_key -config.served_entities[*].external_model.anthropic_config.anthropic_api_key_plaintext -config.served_entities[*].external_model.cohere_config.cohere_api_base -config.served_entities[*].external_model.cohere_config.cohere_api_key -config.served_entities[*].external_model.cohere_config.cohere_api_key_plaintext -config.served_entities[*].external_model.custom_provider_config.api_key_auth.key -config.served_entities[*].external_model.custom_provider_config.api_key_auth.value -config.served_entities[*].external_model.custom_provider_config.api_key_auth.value_plaintext -config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token -config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token_plaintext -config.served_entities[*].external_model.custom_provider_config.custom_provider_url -config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token -config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token_plaintext -config.served_entities[*].external_model.databricks_model_serving_config.databricks_workspace_url -config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key -config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key_plaintext -config.served_entities[*].external_model.google_cloud_vertex_ai_config.project_id -config.served_entities[*].external_model.google_cloud_vertex_ai_config.region -config.served_entities[*].external_model.name -config.served_entities[*].external_model.openai_config.microsoft_entra_client_id -config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret -config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret_plaintext -config.served_entities[*].external_model.openai_config.microsoft_entra_tenant_id -config.served_entities[*].external_model.openai_config.openai_api_base -config.served_entities[*].external_model.openai_config.openai_api_key -config.served_entities[*].external_model.openai_config.openai_api_key_plaintext -config.served_entities[*].external_model.openai_config.openai_api_type -config.served_entities[*].external_model.openai_config.openai_api_version -config.served_entities[*].external_model.openai_config.openai_deployment_name -config.served_entities[*].external_model.openai_config.openai_organization -config.served_entities[*].external_model.palm_config.palm_api_key -config.served_entities[*].external_model.palm_config.palm_api_key_plaintext -config.served_entities[*].external_model.provider -config.served_entities[*].external_model.task -config.served_entities[*].instance_profile_arn -config.served_entities[*].max_provisioned_concurrency -config.served_entities[*].max_provisioned_throughput -config.served_entities[*].min_provisioned_concurrency -config.served_entities[*].min_provisioned_throughput -config.served_entities[*].name -config.served_entities[*].provisioned_model_units -config.served_entities[*].scale_to_zero_enabled -config.served_entities[*].workload_size -config.served_entities[*].workload_type -config.served_models[*].burst_scaling_enabled -config.served_models[*].environment_vars.* -config.served_models[*].instance_profile_arn -config.served_models[*].max_provisioned_concurrency -config.served_models[*].max_provisioned_throughput -config.served_models[*].min_provisioned_concurrency -config.served_models[*].min_provisioned_throughput -config.served_models[*].model_name -config.served_models[*].model_version -config.served_models[*].name -config.served_models[*].provisioned_model_units -config.served_models[*].scale_to_zero_enabled -config.served_models[*].workload_size -config.served_models[*].workload_type -config.traffic_config.routes[*].served_entity_name -config.traffic_config.routes[*].served_model_name -config.traffic_config.routes[*].traffic_percentage -email_notifications.on_update_failure[*] -email_notifications.on_update_success[*] -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -rate_limits[*].calls -rate_limits[*].key -rate_limits[*].renewal_period -tags[*].key -tags[*].value -telemetry_config.enabled_telemetry_features[*] diff --git a/bundle/direct/tests/output/models.full.txt b/bundle/direct/tests/output/models.full.txt deleted file mode 100644 index c5711ca5524..00000000000 --- a/bundle/direct/tests/output/models.full.txt +++ /dev/null @@ -1,28 +0,0 @@ -=== model.yml.tmpl -description absent x OK -description absent y OK -description x absent OK -description x y OK -description y absent OK -description y x OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK_RECREATE -name y x OK_RECREATE - -=== summary -OK 6 -OK_RECREATE 2 -NOT_OBSERVABLE 6 - -=== not covered: 6 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -tags[*].key -tags[*].value diff --git a/bundle/direct/tests/output/pipelines.full.txt b/bundle/direct/tests/output/pipelines.full.txt deleted file mode 100644 index 1ed2c37e9fb..00000000000 --- a/bundle/direct/tests/output/pipelines.full.txt +++ /dev/null @@ -1,1406 +0,0 @@ -=== pipeline.yml.tmpl -allow_duplicate_names absent false SUPPRESSED empty -allow_duplicate_names absent true OK -allow_duplicate_names false absent SUPPRESSED empty -allow_duplicate_names false true OK -allow_duplicate_names true absent OK -allow_duplicate_names true false OK -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent OK -budget_policy_id x y OK -budget_policy_id y absent OK -budget_policy_id y x OK -cascade_on_destroy absent false OK -cascade_on_destroy absent true OK -cascade_on_destroy false absent OK -cascade_on_destroy false true OK -cascade_on_destroy true absent OK -cascade_on_destroy true false OK -catalog absent x OK -catalog absent y OK -catalog x absent OK -catalog x y OK -catalog y absent OK -catalog y x OK -channel absent x OK -channel absent y OK -channel x absent OK -channel x y OK -channel y absent OK -channel y x OK -continuous absent false SUPPRESSED empty -continuous absent true OK -continuous false absent SUPPRESSED empty -continuous false true OK -continuous true absent OK -continuous true false OK -deployment.deployment_id absent x OK -deployment.deployment_id absent y OK -deployment.deployment_id x absent OK -deployment.deployment_id x y OK -deployment.deployment_id y absent OK -deployment.deployment_id y x OK -deployment.kind x y OK -deployment.kind y x OK -deployment.metadata_file_path absent x OK -deployment.metadata_file_path absent y OK -deployment.metadata_file_path x absent OK -deployment.metadata_file_path x y OK -deployment.metadata_file_path y absent OK -deployment.metadata_file_path y x OK -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x absent NOT_OBSERVABLE -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y absent NOT_OBSERVABLE -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -development absent false SUPPRESSED empty -development absent true OK -development false absent SUPPRESSED empty -development false true OK -development true absent OK -development true false OK -dry_run absent false SUPPRESSED empty -dry_run absent true POST_DEPLOY_DRIFT dry_run -dry_run false absent SUPPRESSED remote_already_set -dry_run false true POST_DEPLOY_DRIFT dry_run -dry_run true absent SUPPRESSED remote_already_set -dry_run true false OK -edition absent x OK -edition absent y OK -edition x absent OK -edition x y OK -edition y absent OK -edition y x OK -environment.environment_version absent x OK -environment.environment_version absent y OK -environment.environment_version x absent OK -environment.environment_version x y OK -environment.environment_version y absent OK -environment.environment_version y x OK -event_log.catalog absent x OK -event_log.catalog absent y OK -event_log.catalog x absent OK -event_log.catalog x y OK -event_log.catalog y absent OK -event_log.catalog y x OK -event_log.name absent x OK -event_log.name absent y OK -event_log.name x absent OK -event_log.name x y OK -event_log.name y absent OK -event_log.name y x OK -event_log.schema absent x OK -event_log.schema absent y OK -event_log.schema x absent OK -event_log.schema x y OK -event_log.schema y absent OK -event_log.schema y x OK -gateway_definition.connection_id absent x OK -gateway_definition.connection_id absent y OK_RECREATE -gateway_definition.connection_id x absent OK_RECREATE -gateway_definition.connection_id x y OK_RECREATE -gateway_definition.connection_id y absent OK_RECREATE -gateway_definition.connection_id y x OK_RECREATE -gateway_definition.connection_name x y OK_RECREATE -gateway_definition.connection_name y x OK_RECREATE -gateway_definition.connection_parameters.source_catalog absent x OK -gateway_definition.connection_parameters.source_catalog absent y OK -gateway_definition.connection_parameters.source_catalog x absent OK -gateway_definition.connection_parameters.source_catalog x y OK -gateway_definition.connection_parameters.source_catalog y absent OK -gateway_definition.connection_parameters.source_catalog y x OK -gateway_definition.gateway_storage_catalog x y OK_RECREATE -gateway_definition.gateway_storage_catalog y x OK_RECREATE -gateway_definition.gateway_storage_name absent x OK -gateway_definition.gateway_storage_name absent y OK -gateway_definition.gateway_storage_name x absent OK -gateway_definition.gateway_storage_name x y OK -gateway_definition.gateway_storage_name y absent OK -gateway_definition.gateway_storage_name y x OK -gateway_definition.gateway_storage_schema x y OK_RECREATE -gateway_definition.gateway_storage_schema y x OK_RECREATE -id absent x NOT_OBSERVABLE -id absent y NOT_OBSERVABLE -id x absent NOT_OBSERVABLE -id x y NOT_OBSERVABLE -id y absent NOT_OBSERVABLE -id y x NOT_OBSERVABLE -ingestion_definition.connection_name absent x OK -ingestion_definition.connection_name absent y OK_RECREATE -ingestion_definition.connection_name x absent OK_RECREATE -ingestion_definition.connection_name x y OK_RECREATE -ingestion_definition.connection_name y absent OK_RECREATE -ingestion_definition.connection_name y x OK_RECREATE -ingestion_definition.connector_type absent x OK -ingestion_definition.connector_type absent y OK -ingestion_definition.connector_type x absent OK -ingestion_definition.connector_type x y OK -ingestion_definition.connector_type y absent OK -ingestion_definition.connector_type y x OK -ingestion_definition.data_staging_options.catalog_name x y OK -ingestion_definition.data_staging_options.catalog_name y x OK -ingestion_definition.data_staging_options.schema_name x y OK -ingestion_definition.data_staging_options.schema_name y x OK -ingestion_definition.data_staging_options.volume_name absent x OK -ingestion_definition.data_staging_options.volume_name absent y OK -ingestion_definition.data_staging_options.volume_name x absent OK -ingestion_definition.data_staging_options.volume_name x y OK -ingestion_definition.data_staging_options.volume_name y absent OK -ingestion_definition.data_staging_options.volume_name y x OK -ingestion_definition.full_refresh_window.start_hour 1 2 OK -ingestion_definition.full_refresh_window.start_hour 2 1 OK -ingestion_definition.full_refresh_window.time_zone_id absent x OK -ingestion_definition.full_refresh_window.time_zone_id absent y OK -ingestion_definition.full_refresh_window.time_zone_id x absent OK -ingestion_definition.full_refresh_window.time_zone_id x y OK -ingestion_definition.full_refresh_window.time_zone_id y absent OK -ingestion_definition.full_refresh_window.time_zone_id y x OK -ingestion_definition.ingest_from_uc_foreign_catalog absent false SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog absent true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog false absent SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog false true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true absent OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true false OK_RECREATE -ingestion_definition.ingestion_gateway_id absent x OK_RECREATE -ingestion_definition.ingestion_gateway_id absent y OK_RECREATE -ingestion_definition.ingestion_gateway_id x absent OK_RECREATE -ingestion_definition.ingestion_gateway_id x y OK_RECREATE -ingestion_definition.ingestion_gateway_id y absent OK_RECREATE -ingestion_definition.ingestion_gateway_id y x OK_RECREATE -ingestion_definition.netsuite_jar_path absent x OK -ingestion_definition.netsuite_jar_path absent y OK -ingestion_definition.netsuite_jar_path x absent OK -ingestion_definition.netsuite_jar_path x y OK -ingestion_definition.netsuite_jar_path y absent OK -ingestion_definition.netsuite_jar_path y x OK -ingestion_definition.source_type absent x OK -ingestion_definition.source_type absent y OK -ingestion_definition.source_type x absent OK -ingestion_definition.source_type x y OK -ingestion_definition.source_type y absent OK -ingestion_definition.source_type y x OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false true OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true false OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 2 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 2 OK -ingestion_definition.table_configuration.enable_auto_clustering absent false SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering absent true OK -ingestion_definition.table_configuration.enable_auto_clustering false absent SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering false true OK -ingestion_definition.table_configuration.enable_auto_clustering true absent OK -ingestion_definition.table_configuration.enable_auto_clustering true false OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent x OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y x OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 2 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 2 OK -ingestion_definition.table_configuration.row_filter absent x OK -ingestion_definition.table_configuration.row_filter absent y OK -ingestion_definition.table_configuration.row_filter x absent OK -ingestion_definition.table_configuration.row_filter x y OK -ingestion_definition.table_configuration.row_filter y absent OK -ingestion_definition.table_configuration.row_filter y x OK -ingestion_definition.table_configuration.salesforce_include_formula_fields absent false SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields absent true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields false absent SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields false true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true absent OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true false OK -ingestion_definition.table_configuration.scd_type absent x OK -ingestion_definition.table_configuration.scd_type absent y OK -ingestion_definition.table_configuration.scd_type x absent OK -ingestion_definition.table_configuration.scd_type x y OK -ingestion_definition.table_configuration.scd_type y absent OK -ingestion_definition.table_configuration.scd_type y x OK -ingestion_definition.table_configuration.source_metadata_column absent x OK -ingestion_definition.table_configuration.source_metadata_column absent y OK -ingestion_definition.table_configuration.source_metadata_column x absent OK -ingestion_definition.table_configuration.source_metadata_column x y OK -ingestion_definition.table_configuration.source_metadata_column y absent OK -ingestion_definition.table_configuration.source_metadata_column y x OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent false OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental false absent SUPPRESSED empty -ingestion_definition.table_configuration.workday_report_parameters.incremental false true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true absent OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true false OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -photon absent false SUPPRESSED empty -photon absent true OK -photon false absent SUPPRESSED empty -photon false true OK -photon true absent OK -photon true false OK -restart_window.start_hour 1 2 OK -restart_window.start_hour 2 1 OK -restart_window.time_zone_id absent x OK -restart_window.time_zone_id absent y OK -restart_window.time_zone_id x absent OK -restart_window.time_zone_id x y OK -restart_window.time_zone_id y absent OK -restart_window.time_zone_id y x OK -root_path absent x OK -root_path absent y OK -root_path x absent OK -root_path x y OK -root_path y absent OK -root_path y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -schema absent x OK -schema absent y OK -schema x absent OK -schema x y OK -schema y absent OK -schema y x OK -serverless absent false SUPPRESSED empty -serverless absent true OK -serverless false absent SUPPRESSED empty -serverless false true OK -serverless true absent OK -serverless true false OK -serverless_compute_id absent x OK -serverless_compute_id absent y OK -serverless_compute_id x absent OK -serverless_compute_id x y OK -serverless_compute_id y absent OK -serverless_compute_id y x OK -storage absent x OK_RECREATE -storage absent y OK_RECREATE -storage x absent OK_RECREATE -storage x y OK_RECREATE -storage y absent OK_RECREATE -storage y x OK_RECREATE -target absent x OK -target absent y OK -target x absent OK -target x y OK -target y absent OK -target y x OK -trigger.cron.quartz_cron_schedule absent x OK -trigger.cron.quartz_cron_schedule absent y OK -trigger.cron.quartz_cron_schedule x absent OK -trigger.cron.quartz_cron_schedule x y OK -trigger.cron.quartz_cron_schedule y absent OK -trigger.cron.quartz_cron_schedule y x OK -trigger.cron.timezone_id absent x OK -trigger.cron.timezone_id absent y OK -trigger.cron.timezone_id x absent OK -trigger.cron.timezone_id x y OK -trigger.cron.timezone_id y absent OK -trigger.cron.timezone_id y x OK -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent OK -usage_policy_id x y OK -usage_policy_id y absent OK -usage_policy_id y x OK - -=== pipeline_allow_duplicate_names.yml.tmpl -allow_duplicate_names absent false SUPPRESSED empty -allow_duplicate_names absent true OK -allow_duplicate_names false absent SUPPRESSED empty -allow_duplicate_names false true OK -allow_duplicate_names true absent OK -allow_duplicate_names true false OK -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent OK -budget_policy_id x y OK -budget_policy_id y absent OK -budget_policy_id y x OK -cascade_on_destroy absent false OK -cascade_on_destroy absent true OK -cascade_on_destroy false absent OK -cascade_on_destroy false true OK -cascade_on_destroy true absent OK -cascade_on_destroy true false OK -catalog absent x OK -catalog absent y OK -catalog x absent OK -catalog x y OK -catalog y absent OK -catalog y x OK -channel absent x OK -channel absent y OK -channel x absent OK -channel x y OK -channel y absent OK -channel y x OK -continuous absent false SUPPRESSED empty -continuous absent true OK -continuous false absent SUPPRESSED empty -continuous false true OK -continuous true absent OK -continuous true false OK -deployment.deployment_id absent x OK -deployment.deployment_id absent y OK -deployment.deployment_id x absent OK -deployment.deployment_id x y OK -deployment.deployment_id y absent OK -deployment.deployment_id y x OK -deployment.kind x y OK -deployment.kind y x OK -deployment.metadata_file_path absent x OK -deployment.metadata_file_path absent y OK -deployment.metadata_file_path x absent OK -deployment.metadata_file_path x y OK -deployment.metadata_file_path y absent OK -deployment.metadata_file_path y x OK -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x absent NOT_OBSERVABLE -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y absent NOT_OBSERVABLE -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -development absent false SUPPRESSED empty -development absent true OK -development false absent SUPPRESSED empty -development false true OK -development true absent OK -development true false OK -dry_run absent false SUPPRESSED empty -dry_run absent true POST_DEPLOY_DRIFT dry_run -dry_run false absent SUPPRESSED remote_already_set -dry_run false true POST_DEPLOY_DRIFT dry_run -dry_run true absent SUPPRESSED remote_already_set -dry_run true false OK -edition absent x OK -edition absent y OK -edition x absent OK -edition x y OK -edition y absent OK -edition y x OK -environment.environment_version absent x OK -environment.environment_version absent y OK -environment.environment_version x absent OK -environment.environment_version x y OK -environment.environment_version y absent OK -environment.environment_version y x OK -event_log.catalog absent x OK -event_log.catalog absent y OK -event_log.catalog x absent OK -event_log.catalog x y OK -event_log.catalog y absent OK -event_log.catalog y x OK -event_log.name absent x OK -event_log.name absent y OK -event_log.name x absent OK -event_log.name x y OK -event_log.name y absent OK -event_log.name y x OK -event_log.schema absent x OK -event_log.schema absent y OK -event_log.schema x absent OK -event_log.schema x y OK -event_log.schema y absent OK -event_log.schema y x OK -gateway_definition.connection_id absent x OK -gateway_definition.connection_id absent y OK_RECREATE -gateway_definition.connection_id x absent OK_RECREATE -gateway_definition.connection_id x y OK_RECREATE -gateway_definition.connection_id y absent OK_RECREATE -gateway_definition.connection_id y x OK_RECREATE -gateway_definition.connection_name x y OK_RECREATE -gateway_definition.connection_name y x OK_RECREATE -gateway_definition.connection_parameters.source_catalog absent x OK -gateway_definition.connection_parameters.source_catalog absent y OK -gateway_definition.connection_parameters.source_catalog x absent OK -gateway_definition.connection_parameters.source_catalog x y OK -gateway_definition.connection_parameters.source_catalog y absent OK -gateway_definition.connection_parameters.source_catalog y x OK -gateway_definition.gateway_storage_catalog x y OK_RECREATE -gateway_definition.gateway_storage_catalog y x OK_RECREATE -gateway_definition.gateway_storage_name absent x OK -gateway_definition.gateway_storage_name absent y OK -gateway_definition.gateway_storage_name x absent OK -gateway_definition.gateway_storage_name x y OK -gateway_definition.gateway_storage_name y absent OK -gateway_definition.gateway_storage_name y x OK -gateway_definition.gateway_storage_schema x y OK_RECREATE -gateway_definition.gateway_storage_schema y x OK_RECREATE -id absent x NOT_OBSERVABLE -id absent y NOT_OBSERVABLE -id x absent NOT_OBSERVABLE -id x y NOT_OBSERVABLE -id y absent NOT_OBSERVABLE -id y x NOT_OBSERVABLE -ingestion_definition.connection_name absent x OK -ingestion_definition.connection_name absent y OK_RECREATE -ingestion_definition.connection_name x absent OK_RECREATE -ingestion_definition.connection_name x y OK_RECREATE -ingestion_definition.connection_name y absent OK_RECREATE -ingestion_definition.connection_name y x OK_RECREATE -ingestion_definition.connector_type absent x OK -ingestion_definition.connector_type absent y OK -ingestion_definition.connector_type x absent OK -ingestion_definition.connector_type x y OK -ingestion_definition.connector_type y absent OK -ingestion_definition.connector_type y x OK -ingestion_definition.data_staging_options.catalog_name x y OK -ingestion_definition.data_staging_options.catalog_name y x OK -ingestion_definition.data_staging_options.schema_name x y OK -ingestion_definition.data_staging_options.schema_name y x OK -ingestion_definition.data_staging_options.volume_name absent x OK -ingestion_definition.data_staging_options.volume_name absent y OK -ingestion_definition.data_staging_options.volume_name x absent OK -ingestion_definition.data_staging_options.volume_name x y OK -ingestion_definition.data_staging_options.volume_name y absent OK -ingestion_definition.data_staging_options.volume_name y x OK -ingestion_definition.full_refresh_window.start_hour 1 2 OK -ingestion_definition.full_refresh_window.start_hour 2 1 OK -ingestion_definition.full_refresh_window.time_zone_id absent x OK -ingestion_definition.full_refresh_window.time_zone_id absent y OK -ingestion_definition.full_refresh_window.time_zone_id x absent OK -ingestion_definition.full_refresh_window.time_zone_id x y OK -ingestion_definition.full_refresh_window.time_zone_id y absent OK -ingestion_definition.full_refresh_window.time_zone_id y x OK -ingestion_definition.ingest_from_uc_foreign_catalog absent false SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog absent true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog false absent SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog false true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true absent OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true false OK_RECREATE -ingestion_definition.ingestion_gateway_id absent x OK_RECREATE -ingestion_definition.ingestion_gateway_id absent y OK_RECREATE -ingestion_definition.ingestion_gateway_id x absent OK_RECREATE -ingestion_definition.ingestion_gateway_id x y OK_RECREATE -ingestion_definition.ingestion_gateway_id y absent OK_RECREATE -ingestion_definition.ingestion_gateway_id y x OK_RECREATE -ingestion_definition.netsuite_jar_path absent x OK -ingestion_definition.netsuite_jar_path absent y OK -ingestion_definition.netsuite_jar_path x absent OK -ingestion_definition.netsuite_jar_path x y OK -ingestion_definition.netsuite_jar_path y absent OK -ingestion_definition.netsuite_jar_path y x OK -ingestion_definition.source_type absent x OK -ingestion_definition.source_type absent y OK -ingestion_definition.source_type x absent OK -ingestion_definition.source_type x y OK -ingestion_definition.source_type y absent OK -ingestion_definition.source_type y x OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false true OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true false OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 2 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 2 OK -ingestion_definition.table_configuration.enable_auto_clustering absent false SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering absent true OK -ingestion_definition.table_configuration.enable_auto_clustering false absent SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering false true OK -ingestion_definition.table_configuration.enable_auto_clustering true absent OK -ingestion_definition.table_configuration.enable_auto_clustering true false OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent x OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y x OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 2 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 2 OK -ingestion_definition.table_configuration.row_filter absent x OK -ingestion_definition.table_configuration.row_filter absent y OK -ingestion_definition.table_configuration.row_filter x absent OK -ingestion_definition.table_configuration.row_filter x y OK -ingestion_definition.table_configuration.row_filter y absent OK -ingestion_definition.table_configuration.row_filter y x OK -ingestion_definition.table_configuration.salesforce_include_formula_fields absent false SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields absent true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields false absent SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields false true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true absent OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true false OK -ingestion_definition.table_configuration.scd_type absent x OK -ingestion_definition.table_configuration.scd_type absent y OK -ingestion_definition.table_configuration.scd_type x absent OK -ingestion_definition.table_configuration.scd_type x y OK -ingestion_definition.table_configuration.scd_type y absent OK -ingestion_definition.table_configuration.scd_type y x OK -ingestion_definition.table_configuration.source_metadata_column absent x OK -ingestion_definition.table_configuration.source_metadata_column absent y OK -ingestion_definition.table_configuration.source_metadata_column x absent OK -ingestion_definition.table_configuration.source_metadata_column x y OK -ingestion_definition.table_configuration.source_metadata_column y absent OK -ingestion_definition.table_configuration.source_metadata_column y x OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent false OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental false absent SUPPRESSED empty -ingestion_definition.table_configuration.workday_report_parameters.incremental false true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true absent OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true false OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -photon absent false SUPPRESSED empty -photon absent true OK -photon false absent SUPPRESSED empty -photon false true OK -photon true absent OK -photon true false OK -restart_window.start_hour 1 2 OK -restart_window.start_hour 2 1 OK -restart_window.time_zone_id absent x OK -restart_window.time_zone_id absent y OK -restart_window.time_zone_id x absent OK -restart_window.time_zone_id x y OK -restart_window.time_zone_id y absent OK -restart_window.time_zone_id y x OK -root_path absent x OK -root_path absent y OK -root_path x absent OK -root_path x y OK -root_path y absent OK -root_path y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -schema absent x OK -schema absent y OK -schema x absent OK -schema x y OK -schema y absent OK -schema y x OK -serverless absent false SUPPRESSED empty -serverless absent true OK -serverless false absent SUPPRESSED empty -serverless false true OK -serverless true absent OK -serverless true false OK -serverless_compute_id absent x OK -serverless_compute_id absent y OK -serverless_compute_id x absent OK -serverless_compute_id x y OK -serverless_compute_id y absent OK -serverless_compute_id y x OK -storage absent x OK_RECREATE -storage absent y OK_RECREATE -storage x absent OK_RECREATE -storage x y OK_RECREATE -storage y absent OK_RECREATE -storage y x OK_RECREATE -target absent x OK -target absent y OK -target x absent OK -target x y OK -target y absent OK -target y x OK -trigger.cron.quartz_cron_schedule absent x OK -trigger.cron.quartz_cron_schedule absent y OK -trigger.cron.quartz_cron_schedule x absent OK -trigger.cron.quartz_cron_schedule x y OK -trigger.cron.quartz_cron_schedule y absent OK -trigger.cron.quartz_cron_schedule y x OK -trigger.cron.timezone_id absent x OK -trigger.cron.timezone_id absent y OK -trigger.cron.timezone_id x absent OK -trigger.cron.timezone_id x y OK -trigger.cron.timezone_id y absent OK -trigger.cron.timezone_id y x OK -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent OK -usage_policy_id x y OK -usage_policy_id y absent OK -usage_policy_id y x OK - -=== pipeline_apply_policy_default_values.yml.tmpl -allow_duplicate_names absent false SUPPRESSED empty -allow_duplicate_names absent true OK -allow_duplicate_names false absent SUPPRESSED empty -allow_duplicate_names false true OK -allow_duplicate_names true absent OK -allow_duplicate_names true false OK -budget_policy_id absent x OK -budget_policy_id absent y OK -budget_policy_id x absent OK -budget_policy_id x y OK -budget_policy_id y absent OK -budget_policy_id y x OK -cascade_on_destroy absent false OK -cascade_on_destroy absent true OK -cascade_on_destroy false absent OK -cascade_on_destroy false true OK -cascade_on_destroy true absent OK -cascade_on_destroy true false OK -catalog absent x OK -catalog absent y OK -catalog x absent OK -catalog x y OK -catalog y absent OK -catalog y x OK -channel absent x OK -channel absent y OK -channel x absent OK -channel x y OK -channel y absent OK -channel y x OK -continuous absent false SUPPRESSED empty -continuous absent true OK -continuous false absent SUPPRESSED empty -continuous false true OK -continuous true absent OK -continuous true false OK -deployment.deployment_id absent x OK -deployment.deployment_id absent y OK -deployment.deployment_id x absent OK -deployment.deployment_id x y OK -deployment.deployment_id y absent OK -deployment.deployment_id y x OK -deployment.kind x y OK -deployment.kind y x OK -deployment.metadata_file_path absent x OK -deployment.metadata_file_path absent y OK -deployment.metadata_file_path x absent OK -deployment.metadata_file_path x y OK -deployment.metadata_file_path y absent OK -deployment.metadata_file_path y x OK -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x absent NOT_OBSERVABLE -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y absent NOT_OBSERVABLE -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -development absent false SUPPRESSED empty -development absent true OK -development false absent SUPPRESSED empty -development false true OK -development true absent OK -development true false OK -dry_run absent false SUPPRESSED empty -dry_run absent true POST_DEPLOY_DRIFT dry_run -dry_run false absent SUPPRESSED remote_already_set -dry_run false true POST_DEPLOY_DRIFT dry_run -dry_run true absent SUPPRESSED remote_already_set -dry_run true false OK -edition absent x OK -edition absent y OK -edition x absent OK -edition x y OK -edition y absent OK -edition y x OK -environment.environment_version absent x OK -environment.environment_version absent y OK -environment.environment_version x absent OK -environment.environment_version x y OK -environment.environment_version y absent OK -environment.environment_version y x OK -event_log.catalog absent x OK -event_log.catalog absent y OK -event_log.catalog x absent OK -event_log.catalog x y OK -event_log.catalog y absent OK -event_log.catalog y x OK -event_log.name absent x OK -event_log.name absent y OK -event_log.name x absent OK -event_log.name x y OK -event_log.name y absent OK -event_log.name y x OK -event_log.schema absent x OK -event_log.schema absent y OK -event_log.schema x absent OK -event_log.schema x y OK -event_log.schema y absent OK -event_log.schema y x OK -gateway_definition.connection_id absent x OK -gateway_definition.connection_id absent y OK_RECREATE -gateway_definition.connection_id x absent OK_RECREATE -gateway_definition.connection_id x y OK_RECREATE -gateway_definition.connection_id y absent OK_RECREATE -gateway_definition.connection_id y x OK_RECREATE -gateway_definition.connection_name x y OK_RECREATE -gateway_definition.connection_name y x OK_RECREATE -gateway_definition.connection_parameters.source_catalog absent x OK -gateway_definition.connection_parameters.source_catalog absent y OK -gateway_definition.connection_parameters.source_catalog x absent OK -gateway_definition.connection_parameters.source_catalog x y OK -gateway_definition.connection_parameters.source_catalog y absent OK -gateway_definition.connection_parameters.source_catalog y x OK -gateway_definition.gateway_storage_catalog x y OK_RECREATE -gateway_definition.gateway_storage_catalog y x OK_RECREATE -gateway_definition.gateway_storage_name absent x OK -gateway_definition.gateway_storage_name absent y OK -gateway_definition.gateway_storage_name x absent OK -gateway_definition.gateway_storage_name x y OK -gateway_definition.gateway_storage_name y absent OK -gateway_definition.gateway_storage_name y x OK -gateway_definition.gateway_storage_schema x y OK_RECREATE -gateway_definition.gateway_storage_schema y x OK_RECREATE -id absent x NOT_OBSERVABLE -id absent y NOT_OBSERVABLE -id x absent NOT_OBSERVABLE -id x y NOT_OBSERVABLE -id y absent NOT_OBSERVABLE -id y x NOT_OBSERVABLE -ingestion_definition.connection_name absent x OK -ingestion_definition.connection_name absent y OK_RECREATE -ingestion_definition.connection_name x absent OK_RECREATE -ingestion_definition.connection_name x y OK_RECREATE -ingestion_definition.connection_name y absent OK_RECREATE -ingestion_definition.connection_name y x OK_RECREATE -ingestion_definition.connector_type absent x OK -ingestion_definition.connector_type absent y OK -ingestion_definition.connector_type x absent OK -ingestion_definition.connector_type x y OK -ingestion_definition.connector_type y absent OK -ingestion_definition.connector_type y x OK -ingestion_definition.data_staging_options.catalog_name x y OK -ingestion_definition.data_staging_options.catalog_name y x OK -ingestion_definition.data_staging_options.schema_name x y OK -ingestion_definition.data_staging_options.schema_name y x OK -ingestion_definition.data_staging_options.volume_name absent x OK -ingestion_definition.data_staging_options.volume_name absent y OK -ingestion_definition.data_staging_options.volume_name x absent OK -ingestion_definition.data_staging_options.volume_name x y OK -ingestion_definition.data_staging_options.volume_name y absent OK -ingestion_definition.data_staging_options.volume_name y x OK -ingestion_definition.full_refresh_window.start_hour 1 2 OK -ingestion_definition.full_refresh_window.start_hour 2 1 OK -ingestion_definition.full_refresh_window.time_zone_id absent x OK -ingestion_definition.full_refresh_window.time_zone_id absent y OK -ingestion_definition.full_refresh_window.time_zone_id x absent OK -ingestion_definition.full_refresh_window.time_zone_id x y OK -ingestion_definition.full_refresh_window.time_zone_id y absent OK -ingestion_definition.full_refresh_window.time_zone_id y x OK -ingestion_definition.ingest_from_uc_foreign_catalog absent false SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog absent true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog false absent SUPPRESSED empty -ingestion_definition.ingest_from_uc_foreign_catalog false true OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true absent OK_RECREATE -ingestion_definition.ingest_from_uc_foreign_catalog true false OK_RECREATE -ingestion_definition.ingestion_gateway_id absent x OK_RECREATE -ingestion_definition.ingestion_gateway_id absent y OK_RECREATE -ingestion_definition.ingestion_gateway_id x absent OK_RECREATE -ingestion_definition.ingestion_gateway_id x y OK_RECREATE -ingestion_definition.ingestion_gateway_id y absent OK_RECREATE -ingestion_definition.ingestion_gateway_id y x OK_RECREATE -ingestion_definition.netsuite_jar_path absent x OK -ingestion_definition.netsuite_jar_path absent y OK -ingestion_definition.netsuite_jar_path x absent OK -ingestion_definition.netsuite_jar_path x y OK -ingestion_definition.netsuite_jar_path y absent OK -ingestion_definition.netsuite_jar_path y x OK -ingestion_definition.source_type absent x OK -ingestion_definition.source_type absent y OK -ingestion_definition.source_type x absent OK -ingestion_definition.source_type x y OK -ingestion_definition.source_type y absent OK -ingestion_definition.source_type y x OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled false true OK -ingestion_definition.table_configuration.auto_full_refresh_policy.enabled true false OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 2 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 1 absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours 2 absent OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 1 OK -ingestion_definition.table_configuration.auto_full_refresh_policy.min_interval_hours absent 2 OK -ingestion_definition.table_configuration.enable_auto_clustering absent false SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering absent true OK -ingestion_definition.table_configuration.enable_auto_clustering false absent SUPPRESSED empty -ingestion_definition.table_configuration.enable_auto_clustering false true OK -ingestion_definition.table_configuration.enable_auto_clustering true absent OK -ingestion_definition.table_configuration.enable_auto_clustering true false OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent x OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition absent y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition x y OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y absent OK -ingestion_definition.table_configuration.query_based_connector_config.deletion_condition y x OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 2 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 1 absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds 2 absent OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 1 OK -ingestion_definition.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds absent 2 OK -ingestion_definition.table_configuration.row_filter absent x OK -ingestion_definition.table_configuration.row_filter absent y OK -ingestion_definition.table_configuration.row_filter x absent OK -ingestion_definition.table_configuration.row_filter x y OK -ingestion_definition.table_configuration.row_filter y absent OK -ingestion_definition.table_configuration.row_filter y x OK -ingestion_definition.table_configuration.salesforce_include_formula_fields absent false SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields absent true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields false absent SUPPRESSED empty -ingestion_definition.table_configuration.salesforce_include_formula_fields false true OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true absent OK -ingestion_definition.table_configuration.salesforce_include_formula_fields true false OK -ingestion_definition.table_configuration.scd_type absent x OK -ingestion_definition.table_configuration.scd_type absent y OK -ingestion_definition.table_configuration.scd_type x absent OK -ingestion_definition.table_configuration.scd_type x y OK -ingestion_definition.table_configuration.scd_type y absent OK -ingestion_definition.table_configuration.scd_type y x OK -ingestion_definition.table_configuration.source_metadata_column absent x OK -ingestion_definition.table_configuration.source_metadata_column absent y OK -ingestion_definition.table_configuration.source_metadata_column x absent OK -ingestion_definition.table_configuration.source_metadata_column x y OK -ingestion_definition.table_configuration.source_metadata_column y absent OK -ingestion_definition.table_configuration.source_metadata_column y x OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent false OK -ingestion_definition.table_configuration.workday_report_parameters.incremental absent true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental false absent SUPPRESSED empty -ingestion_definition.table_configuration.workday_report_parameters.incremental false true OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true absent OK -ingestion_definition.table_configuration.workday_report_parameters.incremental true false OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name absent x OK -name absent y OK -name x absent OK -name x y OK -name y absent OK -name y x OK -photon absent false SUPPRESSED empty -photon absent true OK -photon false absent SUPPRESSED empty -photon false true OK -photon true absent OK -photon true false OK -restart_window.start_hour 1 2 OK -restart_window.start_hour 2 1 OK -restart_window.time_zone_id absent x OK -restart_window.time_zone_id absent y OK -restart_window.time_zone_id x absent OK -restart_window.time_zone_id x y OK -restart_window.time_zone_id y absent OK -restart_window.time_zone_id y x OK -root_path absent x OK -root_path absent y OK -root_path x absent OK -root_path x y OK -root_path y absent OK -root_path y x OK -run_as.service_principal_name absent x OK -run_as.service_principal_name absent y OK -run_as.service_principal_name x absent OK -run_as.service_principal_name x y OK -run_as.service_principal_name y absent OK -run_as.service_principal_name y x OK -run_as.user_name absent x OK -run_as.user_name absent y OK -run_as.user_name x absent OK -run_as.user_name x y OK -run_as.user_name y absent OK -run_as.user_name y x OK -schema absent x OK -schema absent y OK -schema x absent OK -schema x y OK -schema y absent OK -schema y x OK -serverless absent false SUPPRESSED empty -serverless absent true OK -serverless false absent SUPPRESSED empty -serverless false true OK -serverless true absent OK -serverless true false OK -serverless_compute_id absent x OK -serverless_compute_id absent y OK -serverless_compute_id x absent OK -serverless_compute_id x y OK -serverless_compute_id y absent OK -serverless_compute_id y x OK -storage absent x OK_RECREATE -storage absent y OK_RECREATE -storage x absent OK_RECREATE -storage x y OK_RECREATE -storage y absent OK_RECREATE -storage y x OK_RECREATE -target absent x OK -target absent y OK -target x absent OK -target x y OK -target y absent OK -target y x OK -trigger.cron.quartz_cron_schedule absent x OK -trigger.cron.quartz_cron_schedule absent y OK -trigger.cron.quartz_cron_schedule x absent OK -trigger.cron.quartz_cron_schedule x y OK -trigger.cron.quartz_cron_schedule y absent OK -trigger.cron.quartz_cron_schedule y x OK -trigger.cron.timezone_id absent x OK -trigger.cron.timezone_id absent y OK -trigger.cron.timezone_id x absent OK -trigger.cron.timezone_id x y OK -trigger.cron.timezone_id y absent OK -trigger.cron.timezone_id y x OK -usage_policy_id absent x OK -usage_policy_id absent y OK -usage_policy_id x absent OK -usage_policy_id x y OK -usage_policy_id y absent OK -usage_policy_id y x OK - -=== summary -OK 774 -OK_RECREATE 96 -SUPPRESSED 72 -NOT_OBSERVABLE 42 -POST_DEPLOY_DRIFT 6 - -=== not covered: 402 fields under a slice or map -clusters[*].apply_policy_default_values -clusters[*].autoscale.max_workers -clusters[*].autoscale.min_workers -clusters[*].autoscale.mode -clusters[*].aws_attributes.availability -clusters[*].aws_attributes.ebs_volume_count -clusters[*].aws_attributes.ebs_volume_iops -clusters[*].aws_attributes.ebs_volume_size -clusters[*].aws_attributes.ebs_volume_throughput -clusters[*].aws_attributes.ebs_volume_type -clusters[*].aws_attributes.first_on_demand -clusters[*].aws_attributes.instance_profile_arn -clusters[*].aws_attributes.spot_bid_price_percent -clusters[*].aws_attributes.zone_id -clusters[*].azure_attributes.availability -clusters[*].azure_attributes.capacity_reservation_group -clusters[*].azure_attributes.first_on_demand -clusters[*].azure_attributes.log_analytics_info.log_analytics_primary_key -clusters[*].azure_attributes.log_analytics_info.log_analytics_workspace_id -clusters[*].azure_attributes.spot_bid_max_price -clusters[*].cluster_log_conf.dbfs.destination -clusters[*].cluster_log_conf.s3.canned_acl -clusters[*].cluster_log_conf.s3.destination -clusters[*].cluster_log_conf.s3.enable_encryption -clusters[*].cluster_log_conf.s3.encryption_type -clusters[*].cluster_log_conf.s3.endpoint -clusters[*].cluster_log_conf.s3.kms_key -clusters[*].cluster_log_conf.s3.region -clusters[*].cluster_log_conf.volumes.destination -clusters[*].custom_tags.* -clusters[*].driver_instance_pool_id -clusters[*].driver_node_type_id -clusters[*].enable_local_disk_encryption -clusters[*].gcp_attributes.availability -clusters[*].gcp_attributes.boot_disk_size -clusters[*].gcp_attributes.confidential_compute_type -clusters[*].gcp_attributes.first_on_demand -clusters[*].gcp_attributes.google_service_account -clusters[*].gcp_attributes.local_ssd_count -clusters[*].gcp_attributes.use_preemptible_executors -clusters[*].gcp_attributes.zone_id -clusters[*].init_scripts[*].abfss.destination -clusters[*].init_scripts[*].dbfs.destination -clusters[*].init_scripts[*].file.destination -clusters[*].init_scripts[*].gcs.destination -clusters[*].init_scripts[*].s3.canned_acl -clusters[*].init_scripts[*].s3.destination -clusters[*].init_scripts[*].s3.enable_encryption -clusters[*].init_scripts[*].s3.encryption_type -clusters[*].init_scripts[*].s3.endpoint -clusters[*].init_scripts[*].s3.kms_key -clusters[*].init_scripts[*].s3.region -clusters[*].init_scripts[*].volumes.destination -clusters[*].init_scripts[*].workspace.destination -clusters[*].instance_pool_id -clusters[*].label -clusters[*].node_type_id -clusters[*].num_workers -clusters[*].policy_id -clusters[*].spark_conf.* -clusters[*].spark_env_vars.* -clusters[*].ssh_public_keys[*] -configuration.* -environment.dependencies[*] -filters.exclude[*] -filters.include[*] -ingestion_definition.full_refresh_window.days_of_week[*] -ingestion_definition.objects[*].report.destination_catalog -ingestion_definition.objects[*].report.destination_schema -ingestion_definition.objects[*].report.destination_table -ingestion_definition.objects[*].report.source_url -ingestion_definition.objects[*].report.table_configuration.auto_full_refresh_policy.enabled -ingestion_definition.objects[*].report.table_configuration.auto_full_refresh_policy.min_interval_hours -ingestion_definition.objects[*].report.table_configuration.clustering_columns[*] -ingestion_definition.objects[*].report.table_configuration.enable_auto_clustering -ingestion_definition.objects[*].report.table_configuration.exclude_columns[*] -ingestion_definition.objects[*].report.table_configuration.include_columns[*] -ingestion_definition.objects[*].report.table_configuration.primary_keys[*] -ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.cursor_columns[*] -ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.deletion_condition -ingestion_definition.objects[*].report.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds -ingestion_definition.objects[*].report.table_configuration.row_filter -ingestion_definition.objects[*].report.table_configuration.salesforce_include_formula_fields -ingestion_definition.objects[*].report.table_configuration.scd_type -ingestion_definition.objects[*].report.table_configuration.sequence_by[*] -ingestion_definition.objects[*].report.table_configuration.source_metadata_column -ingestion_definition.objects[*].report.table_configuration.table_properties.* -ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.incremental -ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.parameters.* -ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].key -ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].value -ingestion_definition.objects[*].schema.connector_options.api_source_connector_options.options.* -ingestion_definition.objects[*].schema.connector_options.confluence_options.include_confluence_spaces[*] -ingestion_definition.objects[*].schema.connector_options.gdrive_options.entity_type -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format_options.* -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.infer_column_types -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.rescued_data_column -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_hints -ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.single_variant_column -ingestion_definition.objects[*].schema.connector_options.gdrive_options.url -ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.resource -ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.resource_fields[*] -ingestion_definition.objects[*].schema.connector_options.google_ads_options.custom_report_options.segments[*] -ingestion_definition.objects[*].schema.connector_options.google_ads_options.lookback_window_days -ingestion_definition.objects[*].schema.connector_options.google_ads_options.manager_account_id -ingestion_definition.objects[*].schema.connector_options.google_ads_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.jira_options.include_jira_spaces[*] -ingestion_definition.objects[*].schema.connector_options.kafka_options.client_config.* -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.format -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.input_column -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.as_variant -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_file_path -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_hints -ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.output_column -ingestion_definition.objects[*].schema.connector_options.kafka_options.max_offsets_per_trigger -ingestion_definition.objects[*].schema.connector_options.kafka_options.starting_offset -ingestion_definition.objects[*].schema.connector_options.kafka_options.topic_pattern -ingestion_definition.objects[*].schema.connector_options.kafka_options.topics[*] -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.format -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.input_column -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.as_variant -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_file_path -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_hints -ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.output_column -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.entity_granularity[*] -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.finder -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.custom_report_options.time_granularity -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.lookback_window_days -ingestion_definition.objects[*].schema.connector_options.linkedin_ads_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.marketo_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_attribution_windows[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_report_time -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_insights_lookback_window -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_attribution_windows[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.action_report_time -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.level -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_report_options.time_increment -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.level -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.start_date -ingestion_definition.objects[*].schema.connector_options.meta_ads_options.time_increment -ingestion_definition.objects[*].schema.connector_options.outlook_options.attachment_mode -ingestion_definition.objects[*].schema.connector_options.outlook_options.body_format -ingestion_definition.objects[*].schema.connector_options.outlook_options.folder_filter[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.include_folders[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.include_mailboxes[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.include_senders[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.include_subjects[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.sender_filter[*] -ingestion_definition.objects[*].schema.connector_options.outlook_options.start_date -ingestion_definition.objects[*].schema.connector_options.outlook_options.subject_filter[*] -ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.custom_report_options.breakdowns[*] -ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.custom_report_options.fields[*] -ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.lookback_window_days -ingestion_definition.objects[*].schema.connector_options.reddit_ads_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.entity_type -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format_options.* -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.infer_column_types -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_hints -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.single_variant_column -ingestion_definition.objects[*].schema.connector_options.sharepoint_options.url -ingestion_definition.objects[*].schema.connector_options.smartsheet_options.enforce_schema -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.data_level -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.dimensions[*] -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.query_lifetime -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.custom_report_options.report_type -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.data_level -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.dimensions[*] -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.lookback_window_days -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.metrics[*] -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.query_lifetime -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.report_type -ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.sync_start_date -ingestion_definition.objects[*].schema.connector_options.zendesk_support_options.start_date -ingestion_definition.objects[*].schema.destination_catalog -ingestion_definition.objects[*].schema.destination_schema -ingestion_definition.objects[*].schema.fanout_options.fanout_by -ingestion_definition.objects[*].schema.fanout_options.transforms[*].format -ingestion_definition.objects[*].schema.fanout_options.transforms[*].input_column -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.as_variant -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_evolution_mode -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_file_path -ingestion_definition.objects[*].schema.fanout_options.transforms[*].json_options.schema_hints -ingestion_definition.objects[*].schema.fanout_options.transforms[*].output_column -ingestion_definition.objects[*].schema.source_catalog -ingestion_definition.objects[*].schema.source_schema -ingestion_definition.objects[*].schema.table_configuration.auto_full_refresh_policy.enabled -ingestion_definition.objects[*].schema.table_configuration.auto_full_refresh_policy.min_interval_hours -ingestion_definition.objects[*].schema.table_configuration.clustering_columns[*] -ingestion_definition.objects[*].schema.table_configuration.enable_auto_clustering -ingestion_definition.objects[*].schema.table_configuration.exclude_columns[*] -ingestion_definition.objects[*].schema.table_configuration.include_columns[*] -ingestion_definition.objects[*].schema.table_configuration.primary_keys[*] -ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.cursor_columns[*] -ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.deletion_condition -ingestion_definition.objects[*].schema.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds -ingestion_definition.objects[*].schema.table_configuration.row_filter -ingestion_definition.objects[*].schema.table_configuration.salesforce_include_formula_fields -ingestion_definition.objects[*].schema.table_configuration.scd_type -ingestion_definition.objects[*].schema.table_configuration.sequence_by[*] -ingestion_definition.objects[*].schema.table_configuration.source_metadata_column -ingestion_definition.objects[*].schema.table_configuration.table_properties.* -ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.incremental -ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.parameters.* -ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].key -ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].value -ingestion_definition.objects[*].table.connector_options.api_source_connector_options.options.* -ingestion_definition.objects[*].table.connector_options.confluence_options.include_confluence_spaces[*] -ingestion_definition.objects[*].table.connector_options.gdrive_options.entity_type -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format_options.* -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.infer_column_types -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.rescued_data_column -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_hints -ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.single_variant_column -ingestion_definition.objects[*].table.connector_options.gdrive_options.url -ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.resource -ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.resource_fields[*] -ingestion_definition.objects[*].table.connector_options.google_ads_options.custom_report_options.segments[*] -ingestion_definition.objects[*].table.connector_options.google_ads_options.lookback_window_days -ingestion_definition.objects[*].table.connector_options.google_ads_options.manager_account_id -ingestion_definition.objects[*].table.connector_options.google_ads_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.jira_options.include_jira_spaces[*] -ingestion_definition.objects[*].table.connector_options.kafka_options.client_config.* -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.format -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.input_column -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.as_variant -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_file_path -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_hints -ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.output_column -ingestion_definition.objects[*].table.connector_options.kafka_options.max_offsets_per_trigger -ingestion_definition.objects[*].table.connector_options.kafka_options.starting_offset -ingestion_definition.objects[*].table.connector_options.kafka_options.topic_pattern -ingestion_definition.objects[*].table.connector_options.kafka_options.topics[*] -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.format -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.input_column -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.as_variant -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_file_path -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_hints -ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.output_column -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.entity_granularity[*] -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.finder -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.custom_report_options.time_granularity -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.lookback_window_days -ingestion_definition.objects[*].table.connector_options.linkedin_ads_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.marketo_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_attribution_windows[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_breakdowns[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_report_time -ingestion_definition.objects[*].table.connector_options.meta_ads_options.breakdowns[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_insights_lookback_window -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_attribution_windows[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_breakdowns[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.action_report_time -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.breakdowns[*] -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.level -ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_report_options.time_increment -ingestion_definition.objects[*].table.connector_options.meta_ads_options.level -ingestion_definition.objects[*].table.connector_options.meta_ads_options.start_date -ingestion_definition.objects[*].table.connector_options.meta_ads_options.time_increment -ingestion_definition.objects[*].table.connector_options.outlook_options.attachment_mode -ingestion_definition.objects[*].table.connector_options.outlook_options.body_format -ingestion_definition.objects[*].table.connector_options.outlook_options.folder_filter[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.include_folders[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.include_mailboxes[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.include_senders[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.include_subjects[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.sender_filter[*] -ingestion_definition.objects[*].table.connector_options.outlook_options.start_date -ingestion_definition.objects[*].table.connector_options.outlook_options.subject_filter[*] -ingestion_definition.objects[*].table.connector_options.reddit_ads_options.custom_report_options.breakdowns[*] -ingestion_definition.objects[*].table.connector_options.reddit_ads_options.custom_report_options.fields[*] -ingestion_definition.objects[*].table.connector_options.reddit_ads_options.lookback_window_days -ingestion_definition.objects[*].table.connector_options.reddit_ads_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.sharepoint_options.entity_type -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format_options.* -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.infer_column_types -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_hints -ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.single_variant_column -ingestion_definition.objects[*].table.connector_options.sharepoint_options.url -ingestion_definition.objects[*].table.connector_options.smartsheet_options.enforce_schema -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.data_level -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.dimensions[*] -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.metrics[*] -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.query_lifetime -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.custom_report_options.report_type -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.data_level -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.dimensions[*] -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.lookback_window_days -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.metrics[*] -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.query_lifetime -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.report_type -ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.sync_start_date -ingestion_definition.objects[*].table.connector_options.zendesk_support_options.start_date -ingestion_definition.objects[*].table.destination_catalog -ingestion_definition.objects[*].table.destination_schema -ingestion_definition.objects[*].table.destination_table -ingestion_definition.objects[*].table.source_catalog -ingestion_definition.objects[*].table.source_schema -ingestion_definition.objects[*].table.source_table -ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy.enabled -ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy.min_interval_hours -ingestion_definition.objects[*].table.table_configuration.clustering_columns[*] -ingestion_definition.objects[*].table.table_configuration.enable_auto_clustering -ingestion_definition.objects[*].table.table_configuration.exclude_columns[*] -ingestion_definition.objects[*].table.table_configuration.include_columns[*] -ingestion_definition.objects[*].table.table_configuration.primary_keys[*] -ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.cursor_columns[*] -ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.deletion_condition -ingestion_definition.objects[*].table.table_configuration.query_based_connector_config.hard_deletion_sync_min_interval_in_seconds -ingestion_definition.objects[*].table.table_configuration.row_filter -ingestion_definition.objects[*].table.table_configuration.salesforce_include_formula_fields -ingestion_definition.objects[*].table.table_configuration.scd_type -ingestion_definition.objects[*].table.table_configuration.sequence_by[*] -ingestion_definition.objects[*].table.table_configuration.source_metadata_column -ingestion_definition.objects[*].table.table_configuration.table_properties.* -ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.incremental -ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.parameters.* -ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.report_parameters[*].key -ingestion_definition.objects[*].table.table_configuration.workday_report_parameters.report_parameters[*].value -ingestion_definition.source_configurations[*].api_source_connector_config.configs.* -ingestion_definition.source_configurations[*].catalog.postgres.slot_config.publication_name -ingestion_definition.source_configurations[*].catalog.postgres.slot_config.slot_name -ingestion_definition.source_configurations[*].catalog.source_catalog -ingestion_definition.source_configurations[*].google_ads_config.manager_account_id -ingestion_definition.table_configuration.clustering_columns[*] -ingestion_definition.table_configuration.exclude_columns[*] -ingestion_definition.table_configuration.include_columns[*] -ingestion_definition.table_configuration.primary_keys[*] -ingestion_definition.table_configuration.query_based_connector_config.cursor_columns[*] -ingestion_definition.table_configuration.sequence_by[*] -ingestion_definition.table_configuration.table_properties.* -ingestion_definition.table_configuration.workday_report_parameters.parameters.* -ingestion_definition.table_configuration.workday_report_parameters.report_parameters[*].key -ingestion_definition.table_configuration.workday_report_parameters.report_parameters[*].value -libraries[*].file.path -libraries[*].glob.include -libraries[*].jar -libraries[*].maven.coordinates -libraries[*].maven.exclusions[*] -libraries[*].maven.repo -libraries[*].notebook.path -libraries[*].whl -notifications[*].alerts[*] -notifications[*].email_recipients[*] -parameters.* -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -restart_window.days_of_week[*] -tags.* diff --git a/bundle/direct/tests/output/postgres_projects.full.txt b/bundle/direct/tests/output/postgres_projects.full.txt deleted file mode 100644 index 7a3c139ae42..00000000000 --- a/bundle/direct/tests/output/postgres_projects.full.txt +++ /dev/null @@ -1,80 +0,0 @@ -=== postgres_project.yml.tmpl -budget_policy_id absent x OK -budget_policy_id absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -budget_policy_id x y OK -budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -budget_policy_id y x OK -default_branch absent x OK -default_branch absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... -default_branch x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... -default_branch x y OK -default_branch y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... -default_branch y x OK -default_endpoint_settings.autoscaling_limit_max_cu 1 2 OK -default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_max_cu 2 1 OK -default_endpoint_settings.autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_max_cu absent 1 OK -default_endpoint_settings.autoscaling_limit_max_cu absent 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu 1 2 OK -default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu 2 1 OK -default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu absent 1 OK -default_endpoint_settings.autoscaling_limit_min_cu absent 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.no_suspension absent false OK -default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension false absent OK -default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name absent y OK -display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name x y OK -display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name y x OK -enable_pg_native_login absent false SUPPRESSED empty -enable_pg_native_login absent true OK -enable_pg_native_login false absent SUPPRESSED empty -enable_pg_native_login false true OK -enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... -enable_pg_native_login true false OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -pg_version 16 17 OK_RECREATE -pg_version 16 absent OK_RECREATE -pg_version 17 16 OK_RECREATE -pg_version 17 absent OK_RECREATE -pg_version absent 16 OK_RECREATE -pg_version absent 17 OK_RECREATE -project_id pgproj1 pgproj2 OK_RECREATE -project_id pgproj2 pgproj1 OK_RECREATE -purge_on_delete absent false SUPPRESSED empty -purge_on_delete absent true OK -purge_on_delete false absent SUPPRESSED empty -purge_on_delete false true OK -purge_on_delete true absent OK -purge_on_delete true false OK - -=== summary -OK 24 -OK_RECREATE 8 -SUPPRESSED 4 -NOT_OBSERVABLE 6 -BACKEND_ERROR 15 -BASE_ERROR 5 - -=== not covered: 7 fields under a slice or map -custom_tags[*].key -custom_tags[*].value -default_endpoint_settings.pg_settings.* -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name diff --git a/bundle/direct/tests/output/postgres_synced_tables.full.txt b/bundle/direct/tests/output/postgres_synced_tables.full.txt deleted file mode 100644 index d023b3ac993..00000000000 --- a/bundle/direct/tests/output/postgres_synced_tables.full.txt +++ /dev/null @@ -1,96 +0,0 @@ -=== postgres_synced_table.yml.tmpl -accelerated_sync absent false SUPPRESSED empty -accelerated_sync absent true OK_RECREATE -accelerated_sync false absent SUPPRESSED empty -accelerated_sync false true OK_RECREATE -accelerated_sync true absent OK_RECREATE -accelerated_sync true false OK_RECREATE -branch absent x OK_RECREATE -branch absent y OK_RECREATE -branch x absent OK_RECREATE -branch x y OK_RECREATE -branch y absent OK_RECREATE -branch y x OK_RECREATE -create_database_objects_if_missing absent false SUPPRESSED empty -create_database_objects_if_missing absent true OK_RECREATE -create_database_objects_if_missing false absent SUPPRESSED empty -create_database_objects_if_missing false true OK_RECREATE -create_database_objects_if_missing true absent OK_RECREATE -create_database_objects_if_missing true false OK_RECREATE -existing_pipeline_id absent x OK_RECREATE -existing_pipeline_id absent y OK_RECREATE -existing_pipeline_id x absent OK_RECREATE -existing_pipeline_id x y OK_RECREATE -existing_pipeline_id y absent OK_RECREATE -existing_pipeline_id y x OK_RECREATE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -new_pipeline_spec.budget_policy_id absent x OK_RECREATE -new_pipeline_spec.budget_policy_id absent y OK_RECREATE -new_pipeline_spec.budget_policy_id x absent OK_RECREATE -new_pipeline_spec.budget_policy_id x y OK_RECREATE -new_pipeline_spec.budget_policy_id y absent OK_RECREATE -new_pipeline_spec.budget_policy_id y x OK_RECREATE -new_pipeline_spec.pipeline_channel absent x OK_RECREATE -new_pipeline_spec.pipeline_channel absent y OK_RECREATE -new_pipeline_spec.pipeline_channel x absent OK_RECREATE -new_pipeline_spec.pipeline_channel x y OK_RECREATE -new_pipeline_spec.pipeline_channel y absent OK_RECREATE -new_pipeline_spec.pipeline_channel y x OK_RECREATE -new_pipeline_spec.storage_catalog absent x OK_RECREATE -new_pipeline_spec.storage_catalog absent y OK_RECREATE -new_pipeline_spec.storage_catalog x absent OK_RECREATE -new_pipeline_spec.storage_catalog x y OK_RECREATE -new_pipeline_spec.storage_catalog y absent OK_RECREATE -new_pipeline_spec.storage_catalog y x OK_RECREATE -new_pipeline_spec.storage_schema absent x OK_RECREATE -new_pipeline_spec.storage_schema absent y OK_RECREATE -new_pipeline_spec.storage_schema x absent OK_RECREATE -new_pipeline_spec.storage_schema x y OK_RECREATE -new_pipeline_spec.storage_schema y absent OK_RECREATE -new_pipeline_spec.storage_schema y x OK_RECREATE -postgres_database absent x OK_RECREATE -postgres_database absent y OK_RECREATE -postgres_database x absent OK_RECREATE -postgres_database x y OK_RECREATE -postgres_database y absent OK_RECREATE -postgres_database y x OK_RECREATE -scheduling_policy absent x OK_RECREATE -scheduling_policy absent y OK_RECREATE -scheduling_policy x absent OK_RECREATE -scheduling_policy x y OK_RECREATE -scheduling_policy y absent OK_RECREATE -scheduling_policy y x OK_RECREATE -source_table_full_name absent x OK_RECREATE -source_table_full_name absent y OK_RECREATE -source_table_full_name x absent OK_RECREATE -source_table_full_name x y OK_RECREATE -source_table_full_name y absent OK_RECREATE -source_table_full_name y x OK_RECREATE -synced_table_id x y OK_RECREATE -synced_table_id y x OK_RECREATE -timeseries_key absent x OK_RECREATE -timeseries_key absent y OK_RECREATE -timeseries_key x absent OK_RECREATE -timeseries_key x y OK_RECREATE -timeseries_key y absent OK_RECREATE -timeseries_key y x OK_RECREATE - -=== summary -OK_RECREATE 70 -SUPPRESSED 4 -NOT_OBSERVABLE 6 - -=== not covered: 8 fields under a slice or map -extra_columns[*].column_name -extra_columns[*].column_type -extra_columns[*].compute -extra_columns[*].maintenance -primary_key_columns[*] -type_overrides[*].column_name -type_overrides[*].pg_type -type_overrides[*].size diff --git a/bundle/direct/tests/output/registered_models.full.txt b/bundle/direct/tests/output/registered_models.full.txt deleted file mode 100644 index 41d7ac99566..00000000000 --- a/bundle/direct/tests/output/registered_models.full.txt +++ /dev/null @@ -1,102 +0,0 @@ -=== registered_model.yml.tmpl -browse_only absent false SUPPRESSED empty -browse_only absent true OK -browse_only false absent SUPPRESSED empty -browse_only false true OK -browse_only true absent OK -browse_only true false OK -catalog_name absent x OK_RECREATE -catalog_name absent y OK_RECREATE -catalog_name x absent OK_RECREATE -catalog_name x y OK_RECREATE -catalog_name y absent OK_RECREATE -catalog_name y x OK_RECREATE -comment absent x OK -comment absent y OK -comment x absent OK -comment x y OK -comment y absent OK -comment y x OK -created_at 1 2 OK -created_at 1 absent OK -created_at 2 1 OK -created_at 2 absent OK -created_at absent 1 OK -created_at absent 2 OK -created_by absent x OK -created_by absent y OK -created_by x absent OK -created_by x y OK -created_by y absent OK -created_by y x OK -full_name absent x POST_DEPLOY_DRIFT full_name -full_name absent y POST_DEPLOY_DRIFT full_name -full_name x absent OK -full_name x y POST_DEPLOY_DRIFT full_name -full_name y absent OK -full_name y x POST_DEPLOY_DRIFT full_name -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -metastore_id absent x OK -metastore_id absent y OK -metastore_id x absent OK -metastore_id x y OK -metastore_id y absent OK -metastore_id y x OK -name absent x OK_RECREATE -name absent y OK_RECREATE -name x absent OK_RECREATE -name x y OK_RECREATE -name y absent OK_RECREATE -name y x OK_RECREATE -owner absent x POST_DEPLOY_DRIFT owner -owner absent y POST_DEPLOY_DRIFT owner -owner x absent OK -owner x y POST_DEPLOY_DRIFT owner -owner y absent OK -owner y x POST_DEPLOY_DRIFT owner -schema_name absent x OK_RECREATE -schema_name absent y OK_RECREATE -schema_name x absent OK_RECREATE -schema_name x y OK_RECREATE -schema_name y absent OK_RECREATE -schema_name y x OK_RECREATE -storage_location absent x OK_RECREATE -storage_location absent y OK_RECREATE -storage_location x absent OK_RECREATE -storage_location x y OK_RECREATE -storage_location y absent OK_RECREATE -storage_location y x OK_RECREATE -updated_at 1 2 OK -updated_at 1 absent OK -updated_at 2 1 OK -updated_at 2 absent OK -updated_at absent 1 OK -updated_at absent 2 OK -updated_by absent x OK -updated_by absent y OK -updated_by x absent OK -updated_by x y OK -updated_by y absent OK -updated_by y x OK - -=== summary -OK 44 -OK_RECREATE 24 -SUPPRESSED 2 -NOT_OBSERVABLE 6 -POST_DEPLOY_DRIFT 8 - -=== not covered: 8 fields under a slice or map -aliases[*].alias_name -aliases[*].catalog_name -aliases[*].id -aliases[*].model_name -aliases[*].schema_name -aliases[*].version_num -grants[*].principal -grants[*].privileges[*] diff --git a/bundle/direct/tests/output/schemas.full.txt b/bundle/direct/tests/output/schemas.full.txt deleted file mode 100644 index ed9615e586b..00000000000 --- a/bundle/direct/tests/output/schemas.full.txt +++ /dev/null @@ -1,80 +0,0 @@ -=== schema.yml.tmpl -comment absent x OK -comment absent y OK -comment x absent OK -comment x y OK -comment y absent OK -comment y x OK -custom_max_retention_hours 1 2 OK -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 1 OK -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours absent 1 OK -custom_max_retention_hours absent 2 OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK_RECREATE -name y x OK_RECREATE -storage_root SKIPPED needs an external location - -=== schema_empty_grants.yml.tmpl -comment absent x OK -comment absent y OK -comment x absent OK -comment x y OK -comment y absent OK -comment y x OK -custom_max_retention_hours 1 2 OK -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 1 OK -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours absent 1 OK -custom_max_retention_hours absent 2 OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK_RECREATE -name y x OK_RECREATE -storage_root SKIPPED needs an external location - -=== schema_uppercase_name.yml.tmpl -comment absent x OK -comment absent y OK -comment x absent OK -comment x y OK -comment y absent OK -comment y x OK -custom_max_retention_hours 1 2 OK -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 1 OK -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours absent 1 OK -custom_max_retention_hours absent 2 OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK_RECREATE -name y x OK_RECREATE -storage_root SKIPPED needs an external location - -=== summary -OK 30 -OK_RECREATE 6 -NOT_OBSERVABLE 18 -POST_DEPLOY_DRIFT 6 -SKIPPED 3 - -=== not covered: 3 fields under a slice or map -grants[*].principal -grants[*].privileges[*] -properties.* diff --git a/bundle/direct/tests/output/secret_scopes.full.txt b/bundle/direct/tests/output/secret_scopes.full.txt deleted file mode 100644 index 60b81a6f21e..00000000000 --- a/bundle/direct/tests/output/secret_scopes.full.txt +++ /dev/null @@ -1,49 +0,0 @@ -=== secret_scope.yml.tmpl -backend_type absent x OK_RECREATE -backend_type absent y OK_RECREATE -backend_type x absent OK_RECREATE -backend_type x y OK_RECREATE -backend_type y absent OK_RECREATE -backend_type y x OK_RECREATE -keyvault_metadata.dns_name x y OK_RECREATE -keyvault_metadata.dns_name y x OK_RECREATE -keyvault_metadata.resource_id x y OK_RECREATE -keyvault_metadata.resource_id y x OK_RECREATE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK_RECREATE -name y x OK_RECREATE - -=== secret_scope_default_backend_type.yml.tmpl -backend_type absent x OK_RECREATE -backend_type absent y OK_RECREATE -backend_type x absent OK_RECREATE -backend_type x y OK_RECREATE -backend_type y absent OK_RECREATE -backend_type y x OK_RECREATE -keyvault_metadata.dns_name x y OK_RECREATE -keyvault_metadata.dns_name y x OK_RECREATE -keyvault_metadata.resource_id x y OK_RECREATE -keyvault_metadata.resource_id y x OK_RECREATE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK_RECREATE -name y x OK_RECREATE - -=== summary -OK_RECREATE 24 -NOT_OBSERVABLE 12 - -=== not covered: 4 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name diff --git a/bundle/direct/tests/output/sql_warehouses.full.txt b/bundle/direct/tests/output/sql_warehouses.full.txt deleted file mode 100644 index 97f038d50ea..00000000000 --- a/bundle/direct/tests/output/sql_warehouses.full.txt +++ /dev/null @@ -1,111 +0,0 @@ -=== sql_warehouse.yml.tmpl -auto_stop_mins 1 2 OK -auto_stop_mins 1 absent OK -auto_stop_mins 2 1 OK -auto_stop_mins 2 absent OK -auto_stop_mins absent 1 OK -auto_stop_mins absent 2 OK -channel.dbsql_version absent x OK -channel.dbsql_version absent y OK -channel.dbsql_version x absent OK -channel.dbsql_version x y OK -channel.dbsql_version y absent OK -channel.dbsql_version y x OK -channel.name absent x OK -channel.name absent y OK -channel.name x absent OK -channel.name x y OK -channel.name y absent OK -channel.name y x OK -cluster_size absent x OK -cluster_size absent y OK -cluster_size x absent OK -cluster_size x y OK -cluster_size y absent OK -cluster_size y x OK -creator_name absent x OK -creator_name absent y OK -creator_name x absent OK -creator_name x y OK -creator_name y absent OK -creator_name y x OK -enable_photon absent false SUPPRESSED empty -enable_photon absent true OK -enable_photon false absent SUPPRESSED empty -enable_photon false true OK -enable_photon true absent OK -enable_photon true false OK -enable_serverless_compute absent false SUPPRESSED empty -enable_serverless_compute absent true OK -enable_serverless_compute false absent SUPPRESSED empty -enable_serverless_compute false true OK -enable_serverless_compute true absent OK -enable_serverless_compute true false OK -instance_profile_arn absent x OK -instance_profile_arn absent y OK -instance_profile_arn x absent OK -instance_profile_arn x y OK -instance_profile_arn y absent OK -instance_profile_arn y x OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -lifecycle.started absent false OK -lifecycle.started absent true OK -lifecycle.started false absent OK -lifecycle.started false true OK -lifecycle.started true absent OK -lifecycle.started true false OK -max_num_clusters 1 2 OK -max_num_clusters 1 absent OK -max_num_clusters 2 1 OK -max_num_clusters 2 absent OK -max_num_clusters absent 1 OK -max_num_clusters absent 2 OK -min_num_clusters 1 2 OK -min_num_clusters 1 absent OK -min_num_clusters 2 1 OK -min_num_clusters 2 absent OK -min_num_clusters absent 1 OK -min_num_clusters absent 2 OK -name absent x OK -name absent y OK -name x absent POST_DEPLOY_DRIFT name -name x y OK -name y absent POST_DEPLOY_DRIFT name -name y x OK -spot_instance_policy absent x OK -spot_instance_policy absent y OK -spot_instance_policy x absent OK -spot_instance_policy x y OK -spot_instance_policy y absent OK -spot_instance_policy y x OK -warehouse_type absent x OK -warehouse_type absent y OK -warehouse_type x absent OK -warehouse_type x y OK -warehouse_type y absent OK -warehouse_type y x OK - -=== summary -OK 78 -SUPPRESSED 4 -NOT_OBSERVABLE 12 -POST_DEPLOY_DRIFT 2 - -=== not covered: 6 fields under a slice or map -permissions[*].group_name -permissions[*].level -permissions[*].service_principal_name -permissions[*].user_name -tags.custom_tags[*].key -tags.custom_tags[*].value diff --git a/bundle/direct/tests/output/volumes.full.txt b/bundle/direct/tests/output/volumes.full.txt deleted file mode 100644 index 6ff0a0b407d..00000000000 --- a/bundle/direct/tests/output/volumes.full.txt +++ /dev/null @@ -1,51 +0,0 @@ -=== volume.yml.tmpl -catalog_name x y OK_RECREATE -catalog_name y x OK_RECREATE -comment absent x OK -comment absent y OK -comment x absent OK -comment x y OK -comment y absent OK -comment y x OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK -name y x OK -schema_name x y OK_RECREATE -schema_name y x OK_RECREATE -storage_location SKIPPED needs an external location - -=== volume_external.yml.tmpl -catalog_name x y OK_RECREATE -catalog_name y x OK_RECREATE -comment absent x OK -comment absent y OK -comment x absent OK -comment x y OK -comment y absent OK -comment y x OK -lifecycle.prevent_destroy absent false NOT_OBSERVABLE -lifecycle.prevent_destroy absent true NOT_OBSERVABLE -lifecycle.prevent_destroy false absent NOT_OBSERVABLE -lifecycle.prevent_destroy false true NOT_OBSERVABLE -lifecycle.prevent_destroy true absent NOT_OBSERVABLE -lifecycle.prevent_destroy true false NOT_OBSERVABLE -name x y OK -name y x OK -schema_name x y OK_RECREATE -schema_name y x OK_RECREATE -storage_location SKIPPED needs an external location - -=== summary -OK 16 -OK_RECREATE 8 -NOT_OBSERVABLE 12 -SKIPPED 2 - -=== not covered: 2 fields under a slice or map -grants[*].principal -grants[*].privileges[*] From 4e0bb0c8863ff3f41d97090e7a68571b26262443 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 12:57:45 +0200 Subject: [PATCH 011/118] Ignore the full field reports They were committed by accident: output/.txt is the file worth diffing. Co-authored-by: Isaac --- bundle/direct/tests/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bundle/direct/tests/.gitignore b/bundle/direct/tests/.gitignore index 7dc023cabdf..f1809932f18 100644 --- a/bundle/direct/tests/.gitignore +++ b/bundle/direct/tests/.gitignore @@ -1,3 +1,7 @@ +# The full report moves whenever a passing row moves; output/.txt is the one that +# records findings and is worth diffing. +output/*.full.txt + # Cloud runs record what a real backend accepted, which varies by workspace. output/*.aws.txt output/*.azure.txt From 7a81b957c5a91d7559f465721e13a9e626b63b2b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 13:37:18 +0200 Subject: [PATCH 012/118] Test slices, maps, and seeded blocks; walk value pairs as one chain Slices and maps are covered two ways. The container becomes a field whose values are the config's own and that value minus its last entry, so with absent in the set one field covers adding and removing an entry as well as the whole container -- all with data the backend already accepted. Separately, a pattern like tasks[*].description is expanded against the deployed config to the indices that exist, so fields inside an element are tested like any other. A pattern with nothing behind it is reported as not covered rather than silently passing. base: in a value library seeds a block the invariant config does not declare, so its fields become reachable. A job's git_source needs a provider, a url and exactly one ref, and cannot be built up one field at a time from nothing; seeded, its skip list drops from nine entries to the two that are genuinely a different side of the same oneof. Value pairs are now walked as a single chain instead of staged one at a time. The values form a complete digraph, so one Eulerian circuit covers every ordered pair exactly once and each move starts where the last ended -- one deploy per transition instead of two, which took jobs from 381s to 233s. Walk order is shuffled from a seed derived from the field path: fixed per field so goldens stay byte-stable, different between fields so the suite is not always taking the same path. TestTransitionsCoverEveryPairInOneChain is what guarantees nothing is left for a second chain to pick up. Configs of a resource type now run in parallel, so the report is guarded. out.full.txt indents the evidence under each finding: the post-deploy plan for drift, the whole API error for a rejection. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 42 ++- bundle/direct/tests/fields_test.go | 105 +++++-- bundle/direct/tests/harness_test.go | 29 +- bundle/direct/tests/output/apps.txt | 1 + bundle/direct/tests/output/catalogs.txt | 1 + bundle/direct/tests/output/dashboards.txt | 1 + .../tests/output/database_instances.txt | 3 - .../tests/output/external_locations.txt | 2 + .../tests/output/model_serving_endpoints.txt | 14 +- bundle/direct/tests/output/pipelines.txt | 3 - .../direct/tests/output/postgres_projects.txt | 4 - .../tests/output/postgres_synced_tables.txt | 5 + bundle/direct/tests/output/secret_scopes.txt | 2 + bundle/direct/tests/output/volumes.txt | 3 + bundle/direct/tests/report_test.go | 50 +++- bundle/direct/tests/testdata/fields/jobs.yml | 32 ++- bundle/direct/tests/values_test.go | 268 ++++++++++++++++-- bundle/direct/tests/values_unit_test.go | 54 ++++ 18 files changed, 513 insertions(+), 106 deletions(-) create mode 100644 bundle/direct/tests/values_unit_test.go diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index 8c9d51ba9d4..ef19994dc81 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -13,11 +13,23 @@ changing a field's type. For each config in `acceptance/bundle/invariant/configs` that describes a single resource, the suite deploys it once, then walks the resource's input struct the way -`cmd/bundle/debug` refschema does. For each scalar field it moves the field through -every ordered pair of a small value set — with `absent` in the set, so adding and -removing a field are just the pairs with `absent` on one side — and records three -things per move: did the plan propose anything, did the apply succeed, and was the -next plan clean. +`cmd/bundle/debug` refschema does. For each field it moves the field through every +ordered pair of a small value set — with `absent` in the set, so adding and removing a +field are just the pairs with `absent` on one side — and records three things per move: +did the plan propose anything, did the apply succeed, and was the next plan clean. + +The pairs are walked as one chain rather than staged one at a time: the values form a +complete digraph, so a single Eulerian circuit covers every ordered pair exactly once and +each move starts where the last one ended. That halves the deploys, and +`TestTransitionsCoverEveryPairInOneChain` is what guarantees nothing is missed. + +**Slices and maps** are covered two ways. The container is a field in its own right, whose +values are the config's own and that value with its last entry dropped — so with `absent` +in the set, one field covers adding and removing an entry as well as adding and removing +the whole container, all with data the backend has already accepted. Separately, a pattern +like `tasks[*].description` is expanded against the deployed config to the indices that +exist, so fields inside an element are tested like any other. A pattern with nothing +behind it in the config is reported as not covered rather than silently passing. Everything runs in-process against the direct engine's own `CalculatePlan` and `Apply`. There is no CLI subprocess and no bundle-file upload: at thousands of @@ -47,8 +59,9 @@ is not a gap; it is the wire format telling the truth. Two files per resource type: `output/.txt` holds only the results worth looking at, one line each, so any diff to it is a change in behaviour. `output/.full.txt` holds -every result plus the summary and the not-covered list; it is gitignored, because it moves -whenever a passing row moves. +every result plus the summary and the not-covered list, and indents the evidence under each +finding — the post-deploy plan for drift, the whole API error for a rejection. It is +gitignored, because it moves whenever a passing row moves and is full of generated ids. The suite deliberately does **not** read `resources.yml`. That file is the engine's answer to many of these cases, so consulting it would just restate the implementation. @@ -60,10 +73,21 @@ The `SUPPRESSED` reason string is the engine explaining itself, which is differe guess — enums, ids, anything the backend constrains: ```yaml +# Seeded into the resource before the first deploy, so this block's fields become +# reachable. Some blocks only validate as a whole -- a job's git_source needs a provider, +# a url and exactly one ref -- and cannot be built up one field at a time from nothing. +# Keep path-valued fields out: base is merged after the mutator pipeline has run. +base: + git_source: + git_provider: gitHub + git_url: https://github.com/databricks/cli.test + git_commit: abc123 + skip: - name: rename waits out an asynchronous delete + git_source.git_branch: mutually exclusive with the seeded git_commit + fields: - compute_size: [MEDIUM, LARGE] + git_source.git_commit: [abc123, def456] ``` Every resource type runs everywhere — local and cloud alike — so most types need no file diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 34f8cb6e400..e59bf57e75f 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -36,6 +36,7 @@ import ( "fmt" "regexp" "slices" + "strconv" "strings" "testing" @@ -74,11 +75,17 @@ func TestFields(t *testing.T) { ctx := t.Context() rep := &report{resourceType: resourceType} //exhaustruct:ignore - for _, cfg := range configs { - t.Run(cfg.name, func(t *testing.T) { - runConfig(t, ctx, client, user, cfg, fv, rep) - }) - } + t.Run("configs", func(t *testing.T) { + for _, cfg := range configs { + t.Run(cfg.name, func(t *testing.T) { + // Each config has its own bundle, state and resources, so they + // only share the workspace -- which the fake server guards and a + // real one handles anyway. The unique name keeps them apart. + t.Parallel() + runConfig(t, ctx, client, user, cfg, fv, rep) + }) + } + }) rep.write(t) }) } @@ -88,35 +95,47 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl adapter, err := dresources.NewAdapter(dresources.SupportedResources[cfg.resourceType], cfg.resourceType, client) require.NoError(t, err) - fields, wildcard := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv) - rep.wildcard = wildcard - // The base deploy establishes that the config itself is deployable. If it is not, // nothing below can be attributed to a field. - h, err := newBaseline(t, ctx, client, user, cfg) + h, err := newBaseline(t, ctx, client, user, cfg, fv) if err != nil { - rep.add(result{cfg.name, "(base config)", "", "create", verdictBaseError, oneLine(err.Error())}) + rep.add(result{cfg.name, "(base config)", "", "create", verdictBaseError, oneLine(err.Error()), err.Error()}) return } + // Enumerated from the deployed config, not just the type: which slice and map entries + // exist decides which fields inside them can be reached at all. base := h.fieldSnapshot() + fields, wildcard := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, base) + rep.addNotCovered(wildcard) + + for path, reason := range fv.skip { + rep.add(result{cfg.name, path, "", "", verdictSkipped, reason, ""}) + } for _, f := range fields { - if reason, ok := fv.skipReason(f.path); ok { - rep.add(result{cfg.name, f.path, "", "", verdictSkipped, reason}) - continue - } t.Run(f.path, func(t *testing.T) { + // What the field is currently deployed as, when that is known. Transitions are + // generated in runs that share a starting value, so this skips about half of + // the setup deploys -- the single biggest cost in the suite. + deployed, deployedKnown := dyn.InvalidValue, false + for _, tr := range f.transitions() { t.Run(tr.label(), func(t *testing.T) { - res := runTransition(t, h, cfg.name, f.path, tr) + reuse := deployedKnown && valueLabel(deployed) == valueLabel(tr.from) + res := runTransition(t, h, cfg.name, f.path, tr, reuse) rep.add(res) + + // Only a clean apply leaves the field provably at "to". + deployed, deployedKnown = tr.to, res.verdict == verdictOK || res.verdict == verdictRecreate + if res.verdict.leavesResourceUsable() { return } + deployedKnown = false // The resource is in an unknown state; carrying it into the next // transition would turn one failure into a run of them. - rebuilt, err := rebuild(t, ctx, client, user, cfg, h) + rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h) if err != nil { t.Skipf("could not rebuild baseline: %s", err) } @@ -131,9 +150,9 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // what makes that cheap: reusing the old one waits out an asynchronous delete. h.restoreField(base, f.path) if !h.converged() { - rebuilt, err := rebuild(t, ctx, client, user, cfg, h) + rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h) if err != nil { - rep.add(result{cfg.name, f.path, "", "", verdictBaseError, oneLine(err.Error())}) + rep.add(result{cfg.name, f.path, "", "", verdictBaseError, oneLine(err.Error()), err.Error()}) return } h = rebuilt @@ -143,7 +162,9 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } // runTransition moves one field from one value to another and reports what happened. -func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition) result { +// runTransition moves one field from one value to another and reports what happened. +// startsDeployed says the field already holds tr.from, so the setup deploy can be skipped. +func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool) result { from, to := tr.from, tr.to res := result{config: config, field: path, from: valueLabel(tr.from), to: valueLabel(tr.to)} //exhaustruct:ignore @@ -153,10 +174,13 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.detail = err.Error() return res } - if _, diags := h.deploy(); diags.HasError() { - res.verdict = verdictBaseError - res.detail = firstError(diags) - return res + if !startsDeployed { + if _, diags := h.deploy(); diags.HasError() { + res.verdict = verdictBaseError + res.detail = firstError(diags) + res.evidence = allErrors(diags) + return res + } } // Now the change under test. @@ -188,6 +212,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.verdict = verdictDeployError } res.detail = firstError(diags) + res.evidence = allErrors(diags) return res } @@ -199,8 +224,9 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans } if own, child := driftDetail(after, h.node); own != "" || child != "" { // The plan that still wants a change is the whole evidence for this verdict, so - // print it. Subtests never fail here, so -v is how you see it. - t.Logf("post-deploy plan still proposes a change:\n%s", planJSON(after)) + // keep it: -v prints it, and it goes into the full report. + res.evidence = planJSON(after) + t.Logf("post-deploy plan still proposes a change:\n%s", res.evidence) // Attribute to the field only when the field's own node is still drifted; a // child left behind by a recreate is a different problem with a different fix. res.verdict, res.detail = verdictDrift, own @@ -218,8 +244,8 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans } // newBaseline builds a harness and deploys the config as written. -func newBaseline(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig) (*bundleHarness, error) { - h, err := newHarness(t, ctx, client, user, cfg.name, uniqueName()) +func newBaseline(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues) (*bundleHarness, error) { + h, err := newHarness(t, ctx, client, user, cfg.name, uniqueName(), fv.base) if err != nil { return nil, err } @@ -238,9 +264,9 @@ func newBaseline(t *testing.T, ctx context.Context, client *databricks.Workspace // rebuild starts over on a fresh resource, leaving the old one to be destroyed at the // end of the test. A new name is what makes this cheap: reusing the old one can mean // waiting out an asynchronous delete. -func rebuild(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, old *bundleHarness) (*bundleHarness, error) { +func rebuild(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, old *bundleHarness) (*bundleHarness, error) { t.Cleanup(func() { _ = old.destroy() }) - return newBaseline(t, ctx, client, user, cfg) + return newBaseline(t, ctx, client, user, cfg, fv) } // explainSkip says why a plan came back with nothing to do for the field under test. @@ -262,11 +288,18 @@ func explainSkip(plan *deployplan.Plan, node, path string) (verdict, string) { return verdictSuppressed, string(change.Action) } -// valueLabel renders a value for the subtest name and the report. +// valueLabel renders a value for the subtest name and the report. A slice or map is +// labelled by size, since the point of testing one is how many entries it has. func valueLabel(v dyn.Value) string { if !v.IsValid() { return "absent" } + if items, ok := v.AsSequence(); ok { + return "len" + strconv.Itoa(len(items)) + } + if m, ok := v.AsMap(); ok { + return "keys" + strconv.Itoa(m.Len()) + } switch s := fmt.Sprintf("%v", v.AsAny()); s { case "": return "empty" @@ -275,6 +308,18 @@ func valueLabel(v dyn.Value) string { } } +// allErrors returns every error diagnostic in full -- status, code, message, endpoint -- +// the way the CLI would print it. firstError's one-line form is for the report column. +func allErrors(diags diag.Diagnostics) string { + var out []string + for _, d := range diags { + if d.Severity == diag.Error { + out = append(out, idPattern.ReplaceAllString(d.Summary, "[UNIQUE_NAME]")) + } + } + return strings.Join(out, "\n") +} + func firstError(diags diag.Diagnostics) string { for _, d := range diags { if d.Severity == diag.Error { diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index 0c5767ee5f1..0550d9afc89 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -24,6 +24,7 @@ import ( "github.com/databricks/cli/libs/dbr" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/merge" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/cli/libs/testserver" @@ -146,7 +147,7 @@ func maxWaitSeconds() string { // built is usually the environment (an expired token, a workspace that rejects the // config), and the run is more useful if that lands in the report as one bad verdict // than if it aborts thousands of pending observations. -func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string) (*bundleHarness, error) { +func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string, base dyn.Value) (*bundleHarness, error) { dir := t.TempDir() if err := copyDir(dataDir, dir); err != nil { return nil, err @@ -237,6 +238,12 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC return nil, err } + if base.IsValid() { + if err := mergeBase(&b.Config, node, base); err != nil { + return nil, err + } + } + return &bundleHarness{ t: t, ctx: ctx, @@ -456,6 +463,26 @@ func ensureParents(root *dyn.Value, p dyn.Path) error { return nil } +// mergeBase folds the value library's base fragment into the resource, so the fields it +// declares are present and can be varied. +func mergeBase(root *bundleconfig.Root, node string, base dyn.Value) error { + path, err := dyn.NewPathFromString(node) + if err != nil { + return err + } + return root.Mutate(func(v dyn.Value) (dyn.Value, error) { + current, err := dyn.GetByPath(v, path) + if err != nil { + return dyn.InvalidValue, err + } + merged, err := merge.Merge(current, base) + if err != nil { + return dyn.InvalidValue, err + } + return dyn.SetByPath(v, path, merged) + }) +} + // subResourceKinds are the child plan nodes this suite removes from every config; see // stripSubResources. var subResourceKinds = []string{"permissions", "grants"} diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 1ef4b3393ff..ae5f29a60c1 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -5,6 +5,7 @@ compute_max_instances 1 absent POST_DEPLOY_D compute_max_instances 2 absent POST_DEPLOY_DRIFT compute_max_instances compute_min_instances 1 absent POST_DEPLOY_DRIFT compute_min_instances compute_min_instances 2 absent POST_DEPLOY_DRIFT compute_min_instances +compute_min_instances absent 2 SUPPRESSED remote_already_set compute_size absent MEDIUM SUPPRESSED remote_already_set description x absent POST_DEPLOY_DRIFT description description y absent POST_DEPLOY_DRIFT description diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index 6d38140bf79..51276c82643 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -5,3 +5,4 @@ custom_max_retention_hours 2 absent POST_DEPLOY_D === catalog_optional_fields.yml.tmpl custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours +name y x POST_DEPLOY_DRIFT custom_max_retention_hours,managed_encryption_settings.customer_managed_key_id diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index ddb28dbcead..4cdb3bfebbd 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -1,4 +1,5 @@ === dashboard.yml.tmpl +display_name absent y SUPPRESSED remote_already_set display_name x absent POST_DEPLOY_DRIFT display_name display_name y absent POST_DEPLOY_DRIFT display_name etag absent x SUPPRESSED custom diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index 696a98dfa0b..de0069c1bfa 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -29,7 +29,6 @@ effective_enable_pg_native_login false true POST_DEPLOY_D effective_enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity effective_enable_pg_native_login true false POST_DEPLOY_DRIFT capacity effective_enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries false absent POST_DEPLOY_DRIFT capacity effective_enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity effective_enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity effective_enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity @@ -62,7 +61,6 @@ enable_pg_native_login false true POST_DEPLOY_D enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity enable_pg_native_login true false POST_DEPLOY_DRIFT capacity enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity -enable_readable_secondaries false absent POST_DEPLOY_DRIFT capacity enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity @@ -103,7 +101,6 @@ state x y POST_DEPLOY_D state y absent POST_DEPLOY_DRIFT capacity state y x POST_DEPLOY_DRIFT capacity stopped absent true POST_DEPLOY_DRIFT capacity -stopped false absent POST_DEPLOY_DRIFT capacity stopped false true POST_DEPLOY_DRIFT capacity stopped true absent POST_DEPLOY_DRIFT capacity stopped true false POST_DEPLOY_DRIFT capacity diff --git a/bundle/direct/tests/output/external_locations.txt b/bundle/direct/tests/output/external_locations.txt index e69de29bb2d..f05326e375f 100644 --- a/bundle/direct/tests/output/external_locations.txt +++ b/bundle/direct/tests/output/external_locations.txt @@ -0,0 +1,2 @@ +=== external_location.yml.tmpl +read_only absent false SUPPRESSED remote_already_set diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index 81fc83f63e5..a3da1997569 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -3,18 +3,24 @@ budget_policy_id absent x SUPPRESSED n budget_policy_id absent y SUPPRESSED no_update_api budget_policy_id x y SUPPRESSED no_update_api budget_policy_id y x SUPPRESSED no_update_api -config.auto_capture_config.catalog_name absent x POST_DEPLOY_DRIFT config +config.auto_capture_config.catalog_name absent y POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled absent false POST_DEPLOY_DRIFT config config.auto_capture_config.enabled absent true POST_DEPLOY_DRIFT config config.auto_capture_config.enabled false absent POST_DEPLOY_DRIFT config config.auto_capture_config.enabled false true POST_DEPLOY_DRIFT config config.auto_capture_config.enabled true absent POST_DEPLOY_DRIFT config config.auto_capture_config.enabled true false POST_DEPLOY_DRIFT config -config.auto_capture_config.schema_name absent x POST_DEPLOY_DRIFT config -route_optimized absent false SUPPRESSED remote_already_set +config.auto_capture_config.schema_name absent y POST_DEPLOY_DRIFT config +config.auto_capture_config.schema_name y absent POST_DEPLOY_DRIFT config === model_serving_endpoint_telemetry.yml.tmpl budget_policy_id absent x SUPPRESSED no_update_api budget_policy_id absent y SUPPRESSED no_update_api budget_policy_id x y SUPPRESSED no_update_api budget_policy_id y x SUPPRESSED no_update_api -route_optimized absent false SUPPRESSED remote_already_set +config.served_entities absent len0 POST_DEPLOY_DRIFT config +config.served_entities len0 absent POST_DEPLOY_DRIFT config +config.served_entities len1 absent POST_DEPLOY_DRIFT config +config.served_entities len1 len0 POST_DEPLOY_DRIFT config +description absent x POST_DEPLOY_DRIFT telemetry_config +description x y BASE_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index e1009d0d530..ba44aecb10f 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -4,7 +4,6 @@ deployment.version_id absent y SUPPRESSED m deployment.version_id x y SUPPRESSED managed by the deployment metadata service deployment.version_id y x SUPPRESSED managed by the deployment metadata service dry_run absent true POST_DEPLOY_DRIFT dry_run -dry_run false absent SUPPRESSED remote_already_set dry_run false true POST_DEPLOY_DRIFT dry_run dry_run true absent SUPPRESSED remote_already_set @@ -14,7 +13,6 @@ deployment.version_id absent y SUPPRESSED m deployment.version_id x y SUPPRESSED managed by the deployment metadata service deployment.version_id y x SUPPRESSED managed by the deployment metadata service dry_run absent true POST_DEPLOY_DRIFT dry_run -dry_run false absent SUPPRESSED remote_already_set dry_run false true POST_DEPLOY_DRIFT dry_run dry_run true absent SUPPRESSED remote_already_set @@ -24,6 +22,5 @@ deployment.version_id absent y SUPPRESSED m deployment.version_id x y SUPPRESSED managed by the deployment metadata service deployment.version_id y x SUPPRESSED managed by the deployment metadata service dry_run absent true POST_DEPLOY_DRIFT dry_run -dry_run false absent SUPPRESSED remote_already_set dry_run false true POST_DEPLOY_DRIFT dry_run dry_run true absent SUPPRESSED remote_already_set diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index e98bae45387..097381460a2 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -1,16 +1,12 @@ === postgres_project.yml.tmpl -budget_policy_id absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -default_branch absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_branch x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_branch y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_max_cu absent 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu absent 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... diff --git a/bundle/direct/tests/output/postgres_synced_tables.txt b/bundle/direct/tests/output/postgres_synced_tables.txt index e69de29bb2d..e3554a486c6 100644 --- a/bundle/direct/tests/output/postgres_synced_tables.txt +++ b/bundle/direct/tests/output/postgres_synced_tables.txt @@ -0,0 +1,5 @@ +=== postgres_synced_table.yml.tmpl +primary_key_columns[0] absent x UNSETTABLE cannot remove resources.postgres_synced_tables.foo.primary_key_columns[0]: not a map key +primary_key_columns[0] absent y UNSETTABLE cannot remove resources.postgres_synced_tables.foo.primary_key_columns[0]: not a map key +primary_key_columns[0] x absent UNSETTABLE cannot remove resources.postgres_synced_tables.foo.primary_key_columns[0]: not a map key +primary_key_columns[0] y absent UNSETTABLE cannot remove resources.postgres_synced_tables.foo.primary_key_columns[0]: not a map key diff --git a/bundle/direct/tests/output/secret_scopes.txt b/bundle/direct/tests/output/secret_scopes.txt index e69de29bb2d..fbe57427472 100644 --- a/bundle/direct/tests/output/secret_scopes.txt +++ b/bundle/direct/tests/output/secret_scopes.txt @@ -0,0 +1,2 @@ +=== secret_scope.yml.tmpl +name x y DEPLOY_ERROR cannot recreate resources.secret_scopes.foo: Scope y already exists (400 ) diff --git a/bundle/direct/tests/output/volumes.txt b/bundle/direct/tests/output/volumes.txt index e69de29bb2d..82c6ab35c96 100644 --- a/bundle/direct/tests/output/volumes.txt +++ b/bundle/direct/tests/output/volumes.txt @@ -0,0 +1,3 @@ +=== volume.yml.tmpl +name x y POST_DEPLOY_DRIFT volume_type +name y x POST_DEPLOY_DRIFT create diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index 03050835a6f..c4962b63d5f 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -5,10 +5,12 @@ import ( "errors" "fmt" "io/fs" + "maps" "os" "path/filepath" "slices" "strings" + "sync" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/testdiff" @@ -104,21 +106,49 @@ type result struct { from, to string verdict verdict detail string + + // evidence is the raw material behind the verdict -- the post-deploy plan for drift, + // the whole API error for a rejection. Printed only into the full report, indented + // under its line: it is what you would otherwise re-run the case to see, and it is + // far too long and too full of generated ids to belong in a committed golden. + evidence string } -// report accumulates results for one resource type. +// report accumulates results for one resource type. The configs of a type run in +// parallel, so both fields are guarded. type report struct { resourceType string - results []result - wildcard []string + + mu sync.Mutex + results []result + wildcard map[string]bool } -func (r *report) add(res result) { r.results = append(r.results, res) } +func (r *report) add(res result) { + r.mu.Lock() + defer r.mu.Unlock() + r.results = append(r.results, res) +} + +// addNotCovered records a field that no config of this type could reach. +func (r *report) addNotCovered(paths []string) { + r.mu.Lock() + defer r.mu.Unlock() + if r.wildcard == nil { + r.wildcard = map[string]bool{} + } + for _, p := range paths { + r.wildcard[p] = true + } +} // render produces a report body. Problems-only is the committed form: one line per // finding and nothing else, so a diff is always a change in behaviour. The full form // adds the passing rows, the summary, and what was not covered. func (r *report) render(problemsOnly bool) string { + r.mu.Lock() + defer r.mu.Unlock() + var sb strings.Builder slices.SortStableFunc(r.results, func(a, b result) int { @@ -149,6 +179,12 @@ func (r *report) render(problemsOnly bool) string { line += " " + res.detail } sb.WriteString(strings.TrimRight(line, " ") + "\n") + + if !problemsOnly && res.evidence != "" { + for evidenceLine := range strings.SplitSeq(strings.TrimRight(res.evidence, "\n"), "\n") { + sb.WriteString(" | " + evidenceLine + "\n") + } + } } if problemsOnly { @@ -168,9 +204,9 @@ func (r *report) render(problemsOnly bool) string { } if len(r.wildcard) > 0 { - slices.Sort(r.wildcard) - fmt.Fprintf(&sb, "\n=== not covered: %d fields under a slice or map\n", len(r.wildcard)) - for _, p := range r.wildcard { + paths := slices.Sorted(maps.Keys(r.wildcard)) + fmt.Fprintf(&sb, "\n=== not covered: %d fields under an absent slice or map\n", len(paths)) + for _, p := range paths { fmt.Fprintf(&sb, "%s\n", p) } } diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml index ea98c1fe05c..3b70c9fb878 100644 --- a/bundle/direct/tests/testdata/fields/jobs.yml +++ b/bundle/direct/tests/testdata/fields/jobs.yml @@ -1,21 +1,25 @@ -# Value library for jobs. See ../README.md. +# Value library for jobs. See ../../README.md. + +# git_source only validates as a coherent set: a provider, a url, and exactly one of +# branch/tag/commit. Seeding one makes its fields reachable, so changing the commit or +# the provider is tested rather than skipped. +base: + git_source: + git_provider: gitHub + git_url: https://github.com/databricks/cli.test + git_commit: abc123 + skip: - # git_source only validates as a coherent set: a provider, a url, and exactly one of - # branch/tag/commit. Moving one of its fields at a time always produces an invalid - # combination, so every value comes back rejected and tells us nothing about field - # support. Covered instead by acceptance/bundle/resources/jobs. - git_source.git_provider: needs a coherent git_source block - git_source.git_url: needs a coherent git_source block - git_source.git_branch: needs a coherent git_source block - git_source.git_tag: needs a coherent git_source block - git_source.git_commit: needs a coherent git_source block - git_source.git_snapshot.used_commit: needs a coherent git_source block - git_source.job_source.job_config_path: needs a coherent git_source block - git_source.job_source.import_from_git_branch: needs a coherent git_source block - git_source.job_source.dirty_state: needs a coherent git_source block + # git_branch and git_tag are the other two sides of the same oneof as git_commit, which + # base sets. Adding one alongside it is rejected for being ambiguous, not for anything + # this suite measures. Covered by acceptance/bundle/resources/jobs. + git_source.git_branch: mutually exclusive with the seeded git_commit + git_source.git_tag: mutually exclusive with the seeded git_commit fields: format: [SINGLE_TASK, MULTI_TASK] performance_target: [PERFORMANCE_OPTIMIZED, STANDARD] max_concurrent_runs: [1, 5] timeout_seconds: [600, 1200] + git_source.git_provider: [gitHub, gitLab] + git_source.git_commit: [abc123, def456] diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index c0e872f90d3..dee9929d6c6 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -4,11 +4,14 @@ import ( "bytes" "errors" "fmt" + "hash/fnv" "io/fs" + "math/rand/v2" "os" "path/filepath" "reflect" "slices" + "strconv" "strings" "github.com/databricks/cli/bundle/internal/validation/generated" @@ -35,6 +38,15 @@ type fieldValues struct { // fields maps a field path to the values to try. fields map[string][]dyn.Value + + // base is merged into the resource before the first deploy. It makes a block + // reachable that the invariant config does not declare: a coherent git_source, say, + // whose fields cannot be tested one at a time from nothing because the API only + // accepts the set as a whole. Once seeded, each of its fields varies normally. + // + // Keep path-valued fields out of it: it is merged after the mutator pipeline has run, + // so nothing here goes through path translation. + base dyn.Value } // fieldsDir holds the per-resource-type value libraries. @@ -44,7 +56,7 @@ const fieldsDir = "testdata/fields" // yamlloader rather than a yaml package, so the values arrive as dyn.Value -- the same // representation the bundle config uses, which is what they are written into. func loadFieldValues(resourceType string) (*fieldValues, error) { - fv := &fieldValues{skip: map[string]string{}, fields: map[string][]dyn.Value{}} + fv := &fieldValues{skip: map[string]string{}, fields: map[string][]dyn.Value{}, base: dyn.InvalidValue} path := filepath.Join(fieldsDir, resourceType+".yml") data, err := os.ReadFile(path) @@ -68,6 +80,9 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { fv.skip[key] = reason } } + if v, err := dyn.Get(root, "base"); err == nil { + fv.base = v + } if v, err := dyn.Get(root, "fields"); err == nil { m, _ := v.AsMap() for _, pair := range m.Pairs() { @@ -139,24 +154,69 @@ func (t transition) label() string { return valueLabel(t.from) + "_to_" + valueLabel(t.to) } -// transitions returns every ordered pair of the field's values. absent is one of them, -// so adding and removing the field are the pairs with absent on one side. +// transitions returns every ordered pair of the field's values, ordered as a single walk: +// each transition starts where the previous one ended, so reaching a starting value costs +// nothing and the field is deployed once per transition instead of twice. absent is one of +// the values, so adding and removing the field are the pairs with absent on one side. +// +// The values form a complete digraph, where every vertex has equal in- and out-degree, so +// an Eulerian circuit always exists and covers each ordered pair exactly once. The order +// among a vertex's outgoing edges is shuffled from a seed derived from the field path: +// fixed for a given field, so the report stays byte-stable, but different between fields, +// so the suite is not always walking the same shape of path. func (f field) transitions() []transition { values := f.values if !f.required { values = append([]dyn.Value{absent}, values...) } - var out []transition - for _, from := range values { - for _, to := range values { - if valueLabel(from) != valueLabel(to) { - out = append(out, transition{from: from, to: to}) + if len(values) < 2 { + return nil + } + + rng := rand.New(rand.NewPCG(pathSeed(f.path), 0)) + unused := make([][]int, len(values)) + for from := range values { + for to := range values { + if from != to { + unused[from] = append(unused[from], to) } } + rng.Shuffle(len(unused[from]), func(i, j int) { + unused[from][i], unused[from][j] = unused[from][j], unused[from][i] + }) + } + + // Hierholzer: walk until stuck, then splice in circuits from vertices that still have + // unused edges. The reversed visit order is the circuit. + var stack, circuit []int + stack = append(stack, 0) + for len(stack) > 0 { + v := stack[len(stack)-1] + if len(unused[v]) == 0 { + circuit = append(circuit, v) + stack = stack[:len(stack)-1] + continue + } + next := unused[v][0] + unused[v] = unused[v][1:] + stack = append(stack, next) + } + slices.Reverse(circuit) + + out := make([]transition, 0, len(circuit)-1) + for i := 1; i < len(circuit); i++ { + out = append(out, transition{from: values[circuit[i-1]], to: values[circuit[i]]}) } return out } +// pathSeed derives a stable seed from a field path. +func pathSeed(path string) uint64 { + h := fnv.New64a() + _, _ = h.Write([]byte(path)) + return h.Sum64() +} + // requiredFields returns the field names the bundle schema marks required directly // under the given path inside a resource, e.g. "" for the resource itself or // "evaluation" for a nested object. The data is generated from the config structs @@ -192,12 +252,30 @@ func isRequired(resourceType, path string) bool { } // enumerateFields walks the resource's input config type the way cmd/bundle/debug -// refschema does, and pairs each scalar leaf with the values to try. +// refschema does, and pairs each field with the values to try. // -// Paths containing a wildcard are left out: they address a slice element or map entry -// that only exists if the base config happens to declare one, and picking an index -// here would silently test nothing. They are reported so the gap stays visible. -func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues) (fields []field, wildcard []string) { +// base is the resource as the config actually declares it, which is what makes slices and +// maps testable. A slice or map the config populates becomes a field in its own right, +// with add-an-entry and remove-an-entry transitions; and a pattern like +// "tasks[*].description" is expanded to the indices that exist, so the fields inside an +// element get the same treatment as any other. A pattern with nothing behind it in the +// config is reported as not covered rather than silently tested against nothing. +func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, base dyn.Value) (fields []field, wildcard []string) { + add := func(path string, kind reflect.Kind, values []dyn.Value) { + if values == nil { + return + } + if _, skipped := fv.skipReason(path); skipped { + return + } + fields = append(fields, field{ + path: path, + kind: kind, + values: values, + required: isRequired(resourceType, path), + }) + } + _ = structwalk.WalkType(inputType, func(p *structpath.PatternNode, typ reflect.Type, sf *reflect.StructField) bool { if p.IsRoot() { return true @@ -205,33 +283,46 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue for typ.Kind() == reflect.Pointer { typ = typ.Elem() } - if isContainer(typ.Kind()) { - // An intermediate node: descend to reach its leaves. - return true + if sf != nil && isNotUserSettable(sf) { + return false } path := p.String() - if strings.Contains(path, "*") { - wildcard = append(wildcard, path) - return false + isWildcard := strings.Contains(path, "*") + + if isContainer(typ.Kind()) { + // A struct is only a grouping; a slice or map is also a value the user can + // grow and shrink, so test it as a field before descending into it. + if typ.Kind() != reflect.Struct { + for _, concrete := range expandPattern(base, path) { + add(concrete, typ.Kind(), containerValues(base, concrete)) + } + } + return true } - if sf != nil && isNotUserSettable(sf) { + + if !isWildcard { + values := fv.fields[path] + if values == nil { + values = defaultValues(typ.Kind()) + } + add(path, typ.Kind(), values) return false } - values := fv.fields[path] - if values == nil { - values = defaultValues(typ.Kind()) - } - if values == nil { + concrete := expandPattern(base, path) + if len(concrete) == 0 { + wildcard = append(wildcard, path) return false } - fields = append(fields, field{ - path: path, - kind: typ.Kind(), - values: values, - required: isRequired(resourceType, path), - }) + for _, c := range concrete { + // Explicit values are keyed by the pattern, since an index is incidental. + values := fv.fields[path] + if values == nil { + values = defaultValues(typ.Kind()) + } + add(c, typ.Kind(), values) + } return false }) return fields, wildcard @@ -245,3 +336,118 @@ func isNotUserSettable(sf *reflect.StructField) bool { strings.Contains(tag, "internal") || sf.Tag.Get("json") == "-" } + +// containerValues returns the values to try for a slice or map field that the base config +// actually populates: the config's own value, and that value with its last entry dropped. +// Combined with the implicit "absent", one field then covers adding and removing the whole +// container as well as adding and removing a single entry -- all with data the backend has +// already accepted, so nothing has to be invented. +func containerValues(base dyn.Value, path string) []dyn.Value { + current, err := dyn.Get(base, path) + if err != nil || !current.IsValid() { + return nil + } + + switch current.Kind() { + case dyn.KindSequence: + items, _ := current.AsSequence() + if len(items) == 0 { + return nil + } + return []dyn.Value{current, dyn.V(slices.Clone(items[:len(items)-1]))} + + case dyn.KindMap: + m, _ := current.AsMap() + pairs := m.Pairs() + if len(pairs) == 0 { + return nil + } + trimmed := dyn.NewMapping() + for _, pair := range pairs[:len(pairs)-1] { + key, _ := pair.Key.AsString() + trimmed.SetLoc(key, pair.Key.Locations(), pair.Value) + } + return []dyn.Value{current, dyn.NewValue(trimmed, current.Locations())} + + default: + return nil + } +} + +// expandPattern turns a pattern from the type walk into the concrete paths the base config +// actually has: "tasks[*].description" against a config with one task yields +// "tasks[0].description". A pattern with nothing behind it yields nothing, which is how a +// field under an absent container is reported as not covered. +func expandPattern(base dyn.Value, pattern string) []string { + paths := []string{""} + for _, seg := range splitPattern(pattern) { + var next []string + for _, prefix := range paths { + switch seg { + case "[*]": + value, err := dyn.Get(base, prefix) + if err != nil { + continue + } + items, ok := value.AsSequence() + if !ok { + continue + } + for i := range items { + next = append(next, prefix+"["+strconv.Itoa(i)+"]") + } + case "*": + value, err := dyn.Get(base, prefix) + if err != nil { + continue + } + m, ok := value.AsMap() + if !ok { + continue + } + for _, pair := range m.Pairs() { + key, _ := pair.Key.AsString() + next = append(next, prefix+"["+quoteKey(key)+"]") + } + default: + next = append(next, joinPath(prefix, seg)) + } + } + paths = next + } + return paths +} + +// splitPattern breaks a pattern into field names, "[*]" and "*" segments. +func splitPattern(pattern string) []string { + var segs []string + for part := range strings.SplitSeq(pattern, ".") { + for { + name, rest, found := strings.Cut(part, "[*]") + if name != "" { + segs = append(segs, name) + } + if !found { + break + } + segs = append(segs, "[*]") + part = rest + } + if part == "*" { + segs = append(segs, "*") + } + } + return segs +} + +func joinPath(prefix, name string) string { + if prefix == "" { + return name + } + return prefix + "." + name +} + +// quoteKey renders a map key the way structpath and dyn both parse it back. +func quoteKey(key string) string { + return "'" + strings.ReplaceAll(key, "'", "''") + "'" +} diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go new file mode 100644 index 00000000000..0b16220a426 --- /dev/null +++ b/bundle/direct/tests/values_unit_test.go @@ -0,0 +1,54 @@ +package tests + +import ( + "testing" + + "github.com/databricks/cli/libs/dyn" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// A field's values form a complete digraph, so one Eulerian circuit covers every ordered +// pair exactly once and no second chain is ever needed. This pins that down: full +// coverage, no repeats, and every step starting where the last one ended. +func TestTransitionsCoverEveryPairInOneChain(t *testing.T) { + for n := 2; n <= 6; n++ { + values := make([]dyn.Value, 0, n-1) + for i := 1; i < n; i++ { + values = append(values, dyn.V(i)) + } + f := field{path: "some.field", values: values} //exhaustruct:ignore + + got := f.transitions() + + // n-1 explicit values plus the implicit absent. + want := n * (n - 1) + require.Len(t, got, want, "n=%d", n) + + seen := map[string]bool{} + for i, tr := range got { + pair := tr.label() + assert.False(t, seen[pair], "pair %s repeated at %d (n=%d)", pair, i, n) + seen[pair] = true + assert.NotEqual(t, valueLabel(tr.from), valueLabel(tr.to), "self-loop at %d", i) + if i > 0 { + assert.Equal(t, valueLabel(got[i-1].to), valueLabel(tr.from), + "step %d does not start where step %d ended (n=%d)", i, i-1, n) + } + } + assert.Len(t, seen, want, "n=%d", n) + } +} + +// A required field has no absent value, so its chain is one vertex smaller. +func TestTransitionsSkipAbsentForRequiredField(t *testing.T) { + f := field{path: "name", values: []dyn.Value{dyn.V("a"), dyn.V("b")}, required: true} //exhaustruct:ignore + + got := f.transitions() + + require.Len(t, got, 2) + for _, tr := range got { + assert.NotEqual(t, "absent", valueLabel(tr.from)) + assert.NotEqual(t, "absent", valueLabel(tr.to)) + } +} From 54d9f8f0145f9e5860dec1aa056b77fd5185abac Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 18:47:52 +0200 Subject: [PATCH 013/118] Cover slices and maps, seed missing structure, split the drift verdicts One config per resource type -- the simplest one -- and whatever structure that config lacks is seeded through testdata/fields instead. A slice or map is a field in its own right, whose values are the config's own and that value minus its last entry, so with absent in the set one field covers adding and removing an entry as well as the whole container. Patterns like tasks[*].description expand against the deployed config, so fields inside an element are tested like any other, and what no config declares is named rather than passed over. Findings are classified until each verdict names one mechanism: UPDATE_IGNORED (write accepted, remote unmoved on two reads) separated from STALE_READ (unmoved on the first read only), COLLATERAL_DRIFT (updating this field left another one drifting -- 119 rows were one field wearing other fields' names), BASELINE_DRIFT (drifts with nothing changed), and OK_INERT / INERT_NOT_HONOURED, which check the ignore_local_changes declarations rather than trusting them. Order comes from git HEAD so a finding cannot be retried away, and the pairs are walked as one Eulerian circuit, halving the deploys. Three bugs in the harness, each of which quietly under-reported coverage: map wildcards never expanded, since splitPattern emitted the wildcard twice; the not-covered list was a union across configs, so it named fields other configs were testing; and neither a map key nor a slice element could be removed, since dyn's path parser reads brackets as indices and deletePath only handled keys. libs/testserver grows SettleAsyncImmediately, off by default so acceptance keeps exercising the waiters. The SDK's poller has a hardcoded one-second backoff with no exported way to shorten it, so one in-progress read per update cost serving endpoints more wall time than every other resource type combined: 512s to 5s. 7052 observations over 22 resource types in 76s. Co-authored-by: Isaac --- bundle/direct/tests/.gitignore | 8 +- bundle/direct/tests/fields_test.go | 250 ++++++++++++++++-- bundle/direct/tests/harness_test.go | 87 +++++- bundle/direct/tests/output/alerts.txt | 7 - bundle/direct/tests/output/apps.txt | 103 ++++++-- bundle/direct/tests/output/catalogs.txt | 21 +- .../direct/tests/output/cluster_policies.txt | 7 + bundle/direct/tests/output/clusters.txt | 20 +- bundle/direct/tests/output/dashboards.txt | 5 +- .../tests/output/database_instances.txt | 175 ++++-------- .../tests/output/external_locations.txt | 2 - bundle/direct/tests/output/genie_spaces.txt | 22 +- bundle/direct/tests/output/instance_pools.txt | 15 +- bundle/direct/tests/output/jobs.txt | 46 +--- .../tests/output/model_serving_endpoints.txt | 41 ++- bundle/direct/tests/output/pipelines.txt | 57 ++-- .../direct/tests/output/postgres_projects.txt | 25 +- .../tests/output/postgres_synced_tables.txt | 9 +- .../direct/tests/output/registered_models.txt | 19 +- bundle/direct/tests/output/schemas.txt | 18 +- bundle/direct/tests/output/secret_scopes.txt | 2 - bundle/direct/tests/output/volumes.txt | 3 - bundle/direct/tests/report_test.go | 160 ++++++++--- .../direct/tests/testdata/fields/alerts.yml | 13 + bundle/direct/tests/testdata/fields/apps.yml | 20 +- .../direct/tests/testdata/fields/catalogs.yml | 6 + .../testdata/fields/cluster_policies.yml | 13 + .../direct/tests/testdata/fields/clusters.yml | 26 ++ .../testdata/fields/database_instances.yml | 5 + .../tests/testdata/fields/experiments.yml | 9 + .../tests/testdata/fields/instance_pools.yml | 14 + bundle/direct/tests/testdata/fields/jobs.yml | 52 +++- .../fields/model_serving_endpoints.yml | 36 ++- .../direct/tests/testdata/fields/models.yml | 5 + .../tests/testdata/fields/pipelines.yml | 48 ++++ .../testdata/fields/postgres_projects.yml | 10 +- .../fields/postgres_synced_tables.yml | 8 + .../testdata/fields/registered_models.yml | 9 + .../direct/tests/testdata/fields/schemas.yml | 14 +- .../tests/testdata/fields/sql_warehouses.yml | 6 + bundle/direct/tests/values_test.go | 149 ++++++++++- bundle/direct/tests/values_unit_test.go | 33 +++ libs/testserver/apps.go | 9 + libs/testserver/fake_workspace.go | 19 ++ libs/testserver/server.go | 19 +- libs/testserver/serving_endpoints.go | 8 +- 46 files changed, 1229 insertions(+), 404 deletions(-) create mode 100644 bundle/direct/tests/testdata/fields/alerts.yml create mode 100644 bundle/direct/tests/testdata/fields/catalogs.yml create mode 100644 bundle/direct/tests/testdata/fields/cluster_policies.yml create mode 100644 bundle/direct/tests/testdata/fields/clusters.yml create mode 100644 bundle/direct/tests/testdata/fields/database_instances.yml create mode 100644 bundle/direct/tests/testdata/fields/experiments.yml create mode 100644 bundle/direct/tests/testdata/fields/instance_pools.yml create mode 100644 bundle/direct/tests/testdata/fields/models.yml create mode 100644 bundle/direct/tests/testdata/fields/pipelines.yml create mode 100644 bundle/direct/tests/testdata/fields/postgres_synced_tables.yml create mode 100644 bundle/direct/tests/testdata/fields/registered_models.yml create mode 100644 bundle/direct/tests/testdata/fields/sql_warehouses.yml diff --git a/bundle/direct/tests/.gitignore b/bundle/direct/tests/.gitignore index f1809932f18..6fabbf724b7 100644 --- a/bundle/direct/tests/.gitignore +++ b/bundle/direct/tests/.gitignore @@ -1,8 +1,4 @@ # The full report moves whenever a passing row moves; output/.txt is the one that -# records findings and is worth diffing. +# records findings and is worth diffing. Both the fake server and a real workspace are held +# to that single golden. output/*.full.txt - -# Cloud runs record what a real backend accepted, which varies by workspace. -output/*.aws.txt -output/*.azure.txt -output/*.gcp.txt diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index e59bf57e75f..61829208224 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -30,10 +30,14 @@ package tests import ( + "bytes" "context" "encoding/json" "errors" "fmt" + "hash/fnv" + "maps" + "os/exec" "regexp" "slices" "strconv" @@ -52,14 +56,32 @@ import ( func TestFields(t *testing.T) { usable, skipped := discoverConfigs(t) - byType := map[string][]testConfig{} + // The order fields are tested in, and the order each field's values are visited in, + // come from this seed. It is the commit rather than the clock so that a finding cannot + // be retried away: one commit always produces one order, and a new commit explores a + // different one. + runSeed := commitSeed(t) + + // One config per resource type: the simplest one, which is the shortest name -- a + // ".yml.tmpl" sorts before its "_.yml.tmpl" siblings. The variants + // exist to exercise specific engine behaviour, not extra fields, and anything a variant + // declares that the plain config does not is better seeded through testdata/fields, where + // it applies to every run rather than only where a corpus config happens to have it. + // + // It also removes the per-config workspace problem outright: a field's values are the same + // for every config, so two configs of one type would rename their resource to the same + // value and delete each other's. + byType := map[string]testConfig{} for _, c := range usable { - byType[c.resourceType] = append(byType[c.resourceType], c) + if existing, ok := byType[c.resourceType]; ok && simplerConfig(existing.name, c.name) { + continue + } + byType[c.resourceType] = c } writeCorpusReport(t, usable, skipped) - for resourceType, configs := range byType { + for resourceType, cfg := range byType { t.Run(resourceType, func(t *testing.T) { // One resource type per goroutine: the types are independent, and the // slowest type then sets the wall time instead of their sum. @@ -71,27 +93,20 @@ func TestFields(t *testing.T) { // state. On cloud this is the same real workspace either way. client := newClient(t) user := workspaceUser(t, client) + // Outlives the field-level subtests that rebuild harnesses. ctx := t.Context() rep := &report{resourceType: resourceType} //exhaustruct:ignore - t.Run("configs", func(t *testing.T) { - for _, cfg := range configs { - t.Run(cfg.name, func(t *testing.T) { - // Each config has its own bundle, state and resources, so they - // only share the workspace -- which the fake server guards and a - // real one handles anyway. The unique name keeps them apart. - t.Parallel() - runConfig(t, ctx, client, user, cfg, fv, rep) - }) - } + t.Run(cfg.name, func(t *testing.T) { + runConfig(t, ctx, client, user, cfg, fv, rep, runSeed) }) rep.write(t) }) } } -func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, rep *report) { +func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, rep *report, runSeed uint64) { adapter, err := dresources.NewAdapter(dresources.SupportedResources[cfg.resourceType], cfg.resourceType, client) require.NoError(t, err) @@ -106,13 +121,33 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // Enumerated from the deployed config, not just the type: which slice and map entries // exist decides which fields inside them can be reached at all. base := h.fieldSnapshot() - fields, wildcard := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, base) - rep.addNotCovered(wildcard) + fields, wildcard, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, base, runSeed, declaredUnsettable(adapter)) + rep.addCoverage(fields, wildcard) + + // Fields the resource declares a user cannot meaningfully set. Recorded with the + // resource's own reason rather than tested: every transition would come back SUPPRESSED + // with exactly that reason. + for _, path := range slices.Sorted(maps.Keys(inert)) { + rep.add(result{cfg.name, path, "", "", verdictSkipped, "backend output: " + inert[path], ""}) + } + + // Fields the resource says it ignores local changes to. Not skipped: the declaration is + // a claim about behaviour, and a transition either bears it out or does not. + ignoredLocally := declaredIgnoredLocally(adapter) for path, reason := range fv.skip { rep.add(result{cfg.name, path, "", "", verdictSkipped, reason, ""}) } + // Some resources do not converge even with nothing changed: a field the read never + // echoes diffs forever. Measure that once, against the field that causes it, and leave + // it out of every transition's drift -- otherwise it dirties every post-deploy plan and + // gets blamed on whichever field happened to be under test. + baseline := baselineDrift(h) + for _, path := range slices.Sorted(maps.Keys(baseline)) { + rep.add(result{cfg.name, path, "", "", verdictBaselineDrift, "drifts with no config change", ""}) + } + for _, f := range fields { t.Run(f.path, func(t *testing.T) { // What the field is currently deployed as, when that is known. Transitions are @@ -123,7 +158,8 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl for _, tr := range f.transitions() { t.Run(tr.label(), func(t *testing.T) { reuse := deployedKnown && valueLabel(deployed) == valueLabel(tr.from) - res := runTransition(t, h, cfg.name, f.path, tr, reuse) + res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline) + res = checkDeclaredInert(res, ignoredLocally) rep.add(res) // Only a clean apply leaves the field provably at "to". @@ -161,10 +197,9 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } } -// runTransition moves one field from one value to another and reports what happened. // runTransition moves one field from one value to another and reports what happened. // startsDeployed says the field already holds tr.from, so the setup deploy can be skipped. -func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool) result { +func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool, baseline map[string]bool) result { from, to := tr.from, tr.to res := result{config: config, field: path, from: valueLabel(tr.from), to: valueLabel(tr.to)} //exhaustruct:ignore @@ -178,7 +213,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans if _, diags := h.deploy(); diags.HasError() { res.verdict = verdictBaseError res.detail = firstError(diags) - res.evidence = allErrors(diags) + res.evidence = withContext("error from the deploy:", allErrors(diags)) return res } } @@ -212,7 +247,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.verdict = verdictDeployError } res.detail = firstError(diags) - res.evidence = allErrors(diags) + res.evidence = withContext("error from the deploy:", allErrors(diags)) return res } @@ -222,16 +257,48 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.detail = firstError(diags) return res } - if own, child := driftDetail(after, h.node); own != "" || child != "" { + // A write the backend accepted and then ignored leaves the field's remote value exactly + // as it was before the apply. That is worth separating from other drift: the engine did + // everything right and the request simply had no effect. + // + // One read cannot tell that apart from a read that was merely stale, so take a second + // one. If the value has appeared by then the write did land and the first read was + // behind; if it still has not, the field really was ignored. + if !baseline[path] && wasIgnored(plan, after, h.node, path) { + _, second, diags := h.plan() + if diags.HasError() { + res.verdict = verdictPlanError + res.detail = firstError(diags) + return res + } + if !wasIgnored(plan, second, h.node, path) { + res.verdict = verdictStaleRead + res.detail = path + res.evidence = withContext("plan taken right after the deploy:", planJSON(after)) + return res + } + res.verdict = verdictUpdateIgnored + res.detail = path + res.evidence = withContext("plan taken twice after the deploy, still unchanged:", planJSON(second)) + t.Logf("the write was accepted but the remote value did not move on two reads:\n%s", res.evidence) + return res + } + + if own, child := driftDetail(after, h.node, baseline); own != "" || child != "" { + // Whether the field under test is among the drifting ones decides who is to blame. + drifted := strings.Split(own, ",") // The plan that still wants a change is the whole evidence for this verdict, so // keep it: -v prints it, and it goes into the full report. - res.evidence = planJSON(after) + res.evidence = withContext("plan taken right after the deploy:", planJSON(after)) t.Logf("post-deploy plan still proposes a change:\n%s", res.evidence) - // Attribute to the field only when the field's own node is still drifted; a - // child left behind by a recreate is a different problem with a different fix. - res.verdict, res.detail = verdictDrift, own - if own == "" { + switch { + case own == "": + // A child node left behind by a recreate: a different problem, different fix. res.verdict, res.detail = verdictDriftChild, child + case slices.Contains(drifted, path): + res.verdict, res.detail = verdictDrift, own + default: + res.verdict, res.detail = verdictCollateral, own } return res } @@ -363,11 +430,99 @@ func isAPIError(diags diag.Diagnostics) bool { return false } +// checkDeclaredInert re-reads a result for a field the resource claims to ignore locally. +// Suppressed with the declared reason confirms the claim; anything else means a change the +// resource said it would drop was not dropped. +func checkDeclaredInert(res result, rules []dresources.FieldRule) result { + reason, declared := ruleReason(rules, res.field) + if !declared { + return res + } + + switch res.verdict { + case verdictSuppressed, verdictNotObservable: + // Both mean the change had no effect, which is what inert claims. Which of the two + // appears depends on whether the planner suppresses the change or drops the entry + // outright, and that is an implementation detail of the rule, not of the field. + res.verdict, res.detail = verdictInertConfirmed, reason + case verdictOK, verdictRecreate: + // The change took effect, so the field is not inert after all. + res.detail = fmt.Sprintf("declared inert (%s) but the change was applied", reason) + res.verdict = verdictInertViolated + default: + // An error or a drift says something more specific than the declaration does; leave + // the verdict alone and note that the field was expected to be inert. + res.detail = fmt.Sprintf("%s (declared inert: %s)", res.detail, reason) + } + return res +} + +// baselineDrift returns the fields the resource already wants to change with the config +// exactly as deployed. +func baselineDrift(h *bundleHarness) map[string]bool { + _, plan, diags := h.plan() + if diags.HasError() || plan == nil { + return nil + } + entry, ok := plan.Plan[h.node] + if !ok { + return nil + } + out := map[string]bool{} + for path, change := range entry.Changes { + if change.Action != deployplan.Skip { + out[path] = true + } + } + return out +} + +// wasIgnored reports whether the write for one field was accepted and then had no effect: +// the field is still pending in the post-deploy plan, and its remote value is identical to +// what the pre-deploy plan saw. +func wasIgnored(before, after *deployplan.Plan, node, path string) bool { + beforeChange, ok := planChange(before, node, path) + if !ok { + return false + } + afterChange, ok := planChange(after, node, path) + if !ok || afterChange.Action == deployplan.Skip { + return false + } + return jsonEqual(beforeChange.Remote, afterChange.Remote) +} + +func planChange(plan *deployplan.Plan, node, path string) (*deployplan.ChangeDesc, bool) { + if plan == nil { + return nil, false + } + entry, ok := plan.Plan[node] + if !ok { + return nil, false + } + change, ok := entry.Changes[path] + return change, ok +} + +// jsonEqual compares two plan values, which are decoded as any and so cannot be compared +// directly. +func jsonEqual(a, b any) bool { + left, err := json.Marshal(a) + if err != nil { + return false + } + right, err := json.Marshal(b) + if err != nil { + return false + } + return bytes.Equal(left, right) +} + // driftDetail describes what a post-deploy plan still wants to change, split into drift // on the node under test (own: the field paths that did not stick) and drift on any // other node (child: how a recreate orphaning a permissions or grants node shows up). // Both are "" when the deploy converged. -func driftDetail(plan *deployplan.Plan, node string) (own, child string) { +func driftDetail(plan *deployplan.Plan, node string, baseline map[string]bool) (own, child string) { var ownPaths, childNodes []string for key, entry := range plan.Plan { if entry.Action == deployplan.Skip { @@ -378,7 +533,7 @@ func driftDetail(plan *deployplan.Plan, node string) (own, child string) { continue } for p, ch := range entry.Changes { - if ch.Action != deployplan.Skip { + if ch.Action != deployplan.Skip && !baseline[p] { ownPaths = append(ownPaths, p) } } @@ -396,6 +551,43 @@ func driftDetail(plan *deployplan.Plan, node string) (own, child string) { // to be redacted or the goldens differ on every run. var idPattern = regexp.MustCompile(`f[0-9a-f]{20}`) +// simplerConfig reports whether a is the simpler of two config names for one resource type. +// Shorter wins, so "job.yml.tmpl" beats "job_with_depends_on.yml.tmpl"; length ties break +// alphabetically so the choice is stable. +func simplerConfig(a, b string) bool { + if len(a) != len(b) { + return len(a) < len(b) + } + return a < b +} + +// commitSeed derives the run seed from HEAD, ignoring the working tree: a dirty checkout +// still explores the order of the commit it is based on. Falls back to a fixed value where +// there is no git at all, so the suite still runs. +// +// Tying the seed to the commit rather than the clock is deliberate: a finding cannot be +// made to disappear by running the test again. +func commitSeed(t *testing.T) uint64 { + out, err := exec.Command("git", "rev-parse", "HEAD").Output() + if err != nil { + t.Logf("no git HEAD (%s); falling back to a fixed order seed", err) + return 0 + } + + commit := strings.TrimSpace(string(out)) + h := fnv.New64a() + _, _ = h.Write([]byte(commit)) + seed := h.Sum64() + t.Logf("field and value order seeded from HEAD %s (seed %d)", commit, seed) + return seed +} + +// withContext labels an evidence block, so a reader of the full report knows what they are +// looking at. The label is its own line, leaving the JSON below it copy-pasteable. +func withContext(label, body string) string { + return label + "\n" + body +} + // planJSON renders a plan for a -v dump. func planJSON(plan *deployplan.Plan) string { body, err := json.MarshalIndent(plan, "", " ") diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index 0550d9afc89..e11ff63cd63 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "slices" + "strconv" "strings" "testing" "time" @@ -55,6 +56,12 @@ func newClient(t *testing.T) *databricks.WorkspaceClient { } server := testserver.New(tolerantT{t}) testserver.AddDefaultHandlers(server) + // This suite performs thousands of updates, and an asynchronous resource that reports + // itself in-progress once costs a full second of SDK backoff each time -- serving + // endpoints alone accounted for more wall time than every other resource type + // combined. Waiter behaviour is covered by the acceptance suite, which leaves the + // simulation on. + server.SettleAsyncImmediately() //exhaustruct:ignore // an SDK config needs only these three fields to reach a fake server w, err := databricks.NewWorkspaceClient(&databricks.Config{ Host: server.URL, @@ -403,10 +410,15 @@ func copyDir(src, dst string) error { // value is absent. Edits go through the dynamic config because that is what the // planner reads (config.Root.GetResourceConfig). func (h *bundleHarness) setField(path string, value dyn.Value) error { - full, err := dyn.NewPathFromString(h.node + "." + path) + rel, err := parsePath(path) if err != nil { return err } + node, err := dyn.NewPathFromString(h.node) + if err != nil { + return err + } + full := append(node, rel...) return h.mutate(func(root dyn.Value) (dyn.Value, error) { if !value.IsValid() { return deletePath(root, full) @@ -418,6 +430,50 @@ func (h *bundleHarness) setField(path string, value dyn.Value) error { }) } +// parsePath converts a field path into a dyn.Path. Written by hand rather than handed to +// dyn.NewPathFromString because that parser treats everything in brackets as an integer +// index, so a map key -- which the field paths here render as ['key'], the form structpath +// prints and the form a key containing a dot requires -- is rejected outright. +func parsePath(path string) (dyn.Path, error) { + var out dyn.Path + for len(path) > 0 { + path = strings.TrimPrefix(path, ".") + + switch { + case strings.HasPrefix(path, "['"): + end := strings.Index(path, "']") + if end < 0 { + return nil, fmt.Errorf("unterminated quoted key in %q", path) + } + // '' is how a literal quote is escaped, matching structpath. + out = append(out, dyn.Key(strings.ReplaceAll(path[2:end], "''", "'"))) + path = path[end+2:] + + case strings.HasPrefix(path, "["): + end := strings.Index(path, "]") + if end < 0 { + return nil, fmt.Errorf("unterminated index in %q", path) + } + index, err := strconv.Atoi(path[1:end]) + if err != nil { + return nil, fmt.Errorf("bad index in %q: %w", path, err) + } + out = append(out, dyn.Index(index)) + path = path[end+1:] + + default: + end := strings.IndexAny(path, ".[") + if end < 0 { + out = append(out, dyn.Key(path)) + return out, nil + } + out = append(out, dyn.Key(path[:end])) + path = path[end:] + } + } + return out, nil +} + // fieldSnapshot captures the resource node as deployed, so a field can be put back to // its base value once its transitions are done. func (h *bundleHarness) fieldSnapshot() dyn.Value { @@ -430,7 +486,7 @@ func (h *bundleHarness) fieldSnapshot() dyn.Value { // restoreField resets one field to whatever the base config had, including absent. func (h *bundleHarness) restoreField(base dyn.Value, path string) { - rel, err := dyn.NewPathFromString(path) + rel, err := parsePath(path) if err != nil { return } @@ -519,11 +575,13 @@ func withoutSubResources(v dyn.Value) (dyn.Value, error) { // deletePath removes a key from its parent map. dyn has no delete, so the parent is // rebuilt without the key, preserving order. func deletePath(root dyn.Value, p dyn.Path) (dyn.Value, error) { + parentPath := p[:len(p)-1] key := p[len(p)-1].Key() if key == "" { - return dyn.InvalidValue, fmt.Errorf("cannot remove %s: not a map key", p) + // An index: drop that element from its sequence. Removing the only element of a + // scalar list is how "absent" is expressed for a []string entry. + return deleteIndex(root, parentPath, p[len(p)-1].Index()) } - parentPath := p[:len(p)-1] parent, err := dyn.GetByPath(root, parentPath) if dyn.IsNoSuchKeyError(err) || dyn.IsCannotTraverseNilError(err) { @@ -552,6 +610,27 @@ func deletePath(root dyn.Value, p dyn.Path) (dyn.Value, error) { return dyn.SetByPath(root, parentPath, dyn.NewValue(trimmed, parent.Locations())) } +// deleteIndex rebuilds a sequence without one element. +func deleteIndex(root dyn.Value, parentPath dyn.Path, index int) (dyn.Value, error) { + parent, err := dyn.GetByPath(root, parentPath) + if dyn.IsNoSuchKeyError(err) || dyn.IsCannotTraverseNilError(err) { + return root, nil + } + if err != nil { + return dyn.InvalidValue, err + } + items, ok := parent.AsSequence() + if !ok { + return dyn.InvalidValue, fmt.Errorf("cannot remove %s[%d]: parent is %s", parentPath, index, parent.Kind()) + } + if index < 0 || index >= len(items) { + return root, nil + } + trimmed := slices.Clone(items[:index]) + trimmed = append(trimmed, items[index+1:]...) + return dyn.SetByPath(root, parentPath, dyn.NewValue(trimmed, parent.Locations())) +} + // uniqueName keeps cloud resources from colliding between runs and between the // parallel subtests of one run. func uniqueName() string { diff --git a/bundle/direct/tests/output/alerts.txt b/bundle/direct/tests/output/alerts.txt index 3c6206aae9f..e69de29bb2d 100644 --- a/bundle/direct/tests/output/alerts.txt +++ b/bundle/direct/tests/output/alerts.txt @@ -1,7 +0,0 @@ -=== alert.yml.tmpl -id absent x SUPPRESSED spec:output_only -id absent y SUPPRESSED spec:output_only -id x absent SUPPRESSED spec:output_only -id x y SUPPRESSED spec:output_only -id y absent SUPPRESSED spec:output_only -id y x SUPPRESSED spec:output_only diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index ae5f29a60c1..b2282e5d4ef 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -1,35 +1,82 @@ === app.yml.tmpl -budget_policy_id x absent POST_DEPLOY_DRIFT budget_policy_id -budget_policy_id y absent POST_DEPLOY_DRIFT budget_policy_id -compute_max_instances 1 absent POST_DEPLOY_DRIFT compute_max_instances -compute_max_instances 2 absent POST_DEPLOY_DRIFT compute_max_instances -compute_min_instances 1 absent POST_DEPLOY_DRIFT compute_min_instances -compute_min_instances 2 absent POST_DEPLOY_DRIFT compute_min_instances -compute_min_instances absent 2 SUPPRESSED remote_already_set -compute_size absent MEDIUM SUPPRESSED remote_already_set -description x absent POST_DEPLOY_DRIFT description -description y absent POST_DEPLOY_DRIFT description -git_repository.auto_deploy true absent POST_DEPLOY_DRIFT git_repository.auto_deploy -git_repository.caller_credential_id 1 absent POST_DEPLOY_DRIFT git_repository.caller_credential_id -git_repository.caller_credential_id 2 absent POST_DEPLOY_DRIFT git_repository.caller_credential_id -id absent x SUPPRESSED spec:output_only -id absent y SUPPRESSED spec:output_only -id x absent SUPPRESSED spec:output_only -id x y SUPPRESSED spec:output_only -id y absent SUPPRESSED spec:output_only -id y x SUPPRESSED spec:output_only -name y x DEPLOY_ERROR cannot recreate resources.apps.foo: timed out: app is deleting, retrying create +budget_policy_id x absent UPDATE_IGNORED budget_policy_id +budget_policy_id y absent UPDATE_IGNORED budget_policy_id +compute_max_instances 1 absent UPDATE_IGNORED compute_max_instances +compute_max_instances 2 absent UPDATE_IGNORED compute_max_instances +compute_min_instances 1 absent UPDATE_IGNORED compute_min_instances +compute_min_instances 2 absent UPDATE_IGNORED compute_min_instances +config.command absent len1 SUPPRESSED no active deployment +config.command len1 absent SUPPRESSED no active deployment +config.command len2 absent SUPPRESSED no active deployment +config.command len2 len1 SUPPRESSED no active deployment +config.command[0] absent y UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[0] x y UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[0] y absent UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[0] y x UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[1] absent x UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[1] absent y UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[1] x absent UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[1] x y UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[1] y absent UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[1] y x UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.env absent len0 SUPPRESSED no active deployment +config.env len0 absent SUPPRESSED no active deployment +config.env len1 absent SUPPRESSED no active deployment +config.env len1 len0 SUPPRESSED no active deployment +config.env[0].name absent x SUPPRESSED no active deployment +config.env[0].name absent y SUPPRESSED no active deployment +config.env[0].name x absent SUPPRESSED no active deployment +config.env[0].name x y SUPPRESSED no active deployment +config.env[0].name y absent SUPPRESSED no active deployment +config.env[0].name y x SUPPRESSED no active deployment +config.env[0].value absent x SUPPRESSED no active deployment +config.env[0].value absent y SUPPRESSED no active deployment +config.env[0].value x absent SUPPRESSED no active deployment +config.env[0].value x y SUPPRESSED no active deployment +config.env[0].value y absent SUPPRESSED no active deployment +config.env[0].value y x SUPPRESSED no active deployment +config.env[0].value_from absent x SUPPRESSED no active deployment +config.env[0].value_from absent y SUPPRESSED no active deployment +config.env[0].value_from x y SUPPRESSED no active deployment +config.env[0].value_from y x SUPPRESSED no active deployment +description x absent UPDATE_IGNORED description +description y absent UPDATE_IGNORED description +git_repository.auto_deploy true absent UPDATE_IGNORED git_repository.auto_deploy +git_repository.caller_credential_id 1 absent UPDATE_IGNORED git_repository.caller_credential_id +git_repository.caller_credential_id 2 absent UPDATE_IGNORED git_repository.caller_credential_id +git_source.branch absent x SUPPRESSED no active deployment +git_source.branch absent y SUPPRESSED no active deployment +git_source.branch x y SUPPRESSED no active deployment +git_source.branch y x SUPPRESSED no active deployment +git_source.resolved_commit absent x SUPPRESSED no active deployment +git_source.resolved_commit absent y SUPPRESSED no active deployment +git_source.resolved_commit x y SUPPRESSED no active deployment +git_source.resolved_commit y x SUPPRESSED no active deployment +git_source.tag absent x SUPPRESSED no active deployment +git_source.tag absent y SUPPRESSED no active deployment +git_source.tag x y SUPPRESSED no active deployment +git_source.tag y x SUPPRESSED no active deployment +resources absent len0 UPDATE_IGNORED resources +resources len0 absent UPDATE_IGNORED resources +resources len1 absent UPDATE_IGNORED resources +resources len1 len0 UPDATE_IGNORED resources +resources[0].app.name x absent UPDATE_IGNORED resources[0].app.name +resources[0].app.name y absent UPDATE_IGNORED resources[0].app.name +resources[0].app.permission x absent UPDATE_IGNORED resources[0].app.permission +resources[0].app.permission y absent UPDATE_IGNORED resources[0].app.permission +resources[0].description x absent UPDATE_IGNORED resources[0].description +resources[0].description y absent UPDATE_IGNORED resources[0].description +resources[0].postgres.branch x absent UPDATE_IGNORED resources[0].postgres.branch +resources[0].postgres.branch y absent UPDATE_IGNORED resources[0].postgres.branch +resources[0].postgres.database x absent UPDATE_IGNORED resources[0].postgres.database +resources[0].postgres.database y absent UPDATE_IGNORED resources[0].postgres.database +resources[0].postgres.permission x absent UPDATE_IGNORED resources[0].postgres.permission +resources[0].postgres.permission y absent UPDATE_IGNORED resources[0].postgres.permission source_code_path absent x SUPPRESSED no active deployment source_code_path absent y SUPPRESSED no active deployment source_code_path x absent SUPPRESSED no active deployment source_code_path x y SUPPRESSED no active deployment source_code_path y absent SUPPRESSED no active deployment source_code_path y x SUPPRESSED no active deployment -url absent x SUPPRESSED spec:output_only -url absent y SUPPRESSED spec:output_only -url x absent SUPPRESSED spec:output_only -url x y SUPPRESSED spec:output_only -url y absent SUPPRESSED spec:output_only -url y x SUPPRESSED spec:output_only -usage_policy_id x absent POST_DEPLOY_DRIFT usage_policy_id -usage_policy_id y absent POST_DEPLOY_DRIFT usage_policy_id +usage_policy_id x absent UPDATE_IGNORED usage_policy_id +usage_policy_id y absent UPDATE_IGNORED usage_policy_id diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index 51276c82643..92df91801fb 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -1,8 +1,15 @@ === catalog.yml.tmpl -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours - -=== catalog_optional_fields.yml.tmpl -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours -name y x POST_DEPLOY_DRIFT custom_max_retention_hours,managed_encryption_settings.customer_managed_key_id +custom_max_retention_hours 1 absent UPDATE_IGNORED custom_max_retention_hours +custom_max_retention_hours 2 absent UPDATE_IGNORED custom_max_retention_hours +options absent keys0 UPDATE_IGNORED options +options keys0 absent UPDATE_IGNORED options +options keys1 absent UPDATE_IGNORED options +options keys1 keys0 UPDATE_IGNORED options +options['region'] x absent UPDATE_IGNORED options['region'] +options['region'] y absent UPDATE_IGNORED options['region'] +properties absent keys0 UPDATE_IGNORED properties +properties keys0 absent UPDATE_IGNORED properties +properties keys1 absent UPDATE_IGNORED properties +properties keys1 keys0 UPDATE_IGNORED properties +properties['team'] x absent UPDATE_IGNORED properties['team'] +properties['team'] y absent UPDATE_IGNORED properties['team'] diff --git a/bundle/direct/tests/output/cluster_policies.txt b/bundle/direct/tests/output/cluster_policies.txt index e69de29bb2d..d3d3756812f 100644 --- a/bundle/direct/tests/output/cluster_policies.txt +++ b/bundle/direct/tests/output/cluster_policies.txt @@ -0,0 +1,7 @@ +=== cluster_policy.yml.tmpl +libraries[1].maven.exclusions[0] absent x UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" +libraries[1].maven.exclusions[0] absent y UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" +libraries[1].maven.exclusions[0] x absent UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" +libraries[1].maven.exclusions[0] x y UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" +libraries[1].maven.exclusions[0] y absent UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" +libraries[1].maven.exclusions[0] y x UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/tests/output/clusters.txt index 6277d40cf4c..1513ce03d3d 100644 --- a/bundle/direct/tests/output/clusters.txt +++ b/bundle/direct/tests/output/clusters.txt @@ -1,7 +1,15 @@ === cluster.yml.tmpl -apply_policy_default_values false absent SUPPRESSED remote_already_set -enable_elastic_disk absent false SUPPRESSED remote_already_set - -=== cluster_apply_policy_default_values.yml.tmpl -apply_policy_default_values false absent SUPPRESSED remote_already_set -enable_elastic_disk absent false SUPPRESSED remote_already_set +driver_node_type_flexibility.alternate_node_type_ids[0] absent x UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" +driver_node_type_flexibility.alternate_node_type_ids[0] absent y UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" +driver_node_type_flexibility.alternate_node_type_ids[0] x absent UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" +driver_node_type_flexibility.alternate_node_type_ids[0] x y UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" +driver_node_type_flexibility.alternate_node_type_ids[0] y x UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" +ssh_public_keys[0] absent x UNSETTABLE index out of bounds at "resources.clusters.foo.ssh_public_keys" +ssh_public_keys[0] absent y UNSETTABLE index out of bounds at "resources.clusters.foo.ssh_public_keys" +ssh_public_keys[0] x absent UNSETTABLE index out of bounds at "resources.clusters.foo.ssh_public_keys" +worker_node_type_flexibility.alternate_node_type_ids[0] absent x UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" +worker_node_type_flexibility.alternate_node_type_ids[0] absent y UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" +worker_node_type_flexibility.alternate_node_type_ids[0] x absent UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" +worker_node_type_flexibility.alternate_node_type_ids[0] x y UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" +worker_node_type_flexibility.alternate_node_type_ids[0] y absent UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" +worker_node_type_flexibility.alternate_node_type_ids[0] y x UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index 4cdb3bfebbd..bd12562eb6f 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -1,7 +1,6 @@ === dashboard.yml.tmpl -display_name absent y SUPPRESSED remote_already_set -display_name x absent POST_DEPLOY_DRIFT display_name -display_name y absent POST_DEPLOY_DRIFT display_name +display_name x absent UPDATE_IGNORED display_name +display_name y absent UPDATE_IGNORED display_name etag absent x SUPPRESSED custom etag absent y SUPPRESSED custom etag x absent SUPPRESSED custom diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index de0069c1bfa..64dcb15fca4 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -1,118 +1,59 @@ === database_instance.yml.tmpl -capacity absent x POST_DEPLOY_DRIFT capacity -capacity absent y POST_DEPLOY_DRIFT capacity -capacity x absent SUPPRESSED remote_already_set -capacity x y POST_DEPLOY_DRIFT capacity -capacity y absent SUPPRESSED remote_already_set -capacity y x POST_DEPLOY_DRIFT capacity -creation_time absent x POST_DEPLOY_DRIFT capacity -creation_time absent y POST_DEPLOY_DRIFT capacity -creation_time x absent POST_DEPLOY_DRIFT capacity -creation_time x y POST_DEPLOY_DRIFT capacity -creation_time y absent POST_DEPLOY_DRIFT capacity -creation_time y x POST_DEPLOY_DRIFT capacity -creator absent x POST_DEPLOY_DRIFT capacity -creator absent y POST_DEPLOY_DRIFT capacity -creator x absent POST_DEPLOY_DRIFT capacity -creator x y POST_DEPLOY_DRIFT capacity -creator y absent POST_DEPLOY_DRIFT capacity -creator y x POST_DEPLOY_DRIFT capacity -effective_capacity absent x POST_DEPLOY_DRIFT capacity -effective_capacity absent y POST_DEPLOY_DRIFT capacity -effective_capacity x absent POST_DEPLOY_DRIFT capacity -effective_capacity x y POST_DEPLOY_DRIFT capacity -effective_capacity y absent POST_DEPLOY_DRIFT capacity -effective_capacity y x POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login absent true POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login false absent POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login false true POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity -effective_enable_pg_native_login true false POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity -effective_enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity -effective_node_count 1 2 POST_DEPLOY_DRIFT capacity -effective_node_count 1 absent POST_DEPLOY_DRIFT capacity -effective_node_count 2 1 POST_DEPLOY_DRIFT capacity -effective_node_count 2 absent POST_DEPLOY_DRIFT capacity -effective_node_count absent 1 POST_DEPLOY_DRIFT capacity -effective_node_count absent 2 POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days 1 2 POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days 1 absent POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days 2 1 POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days 2 absent POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days absent 1 POST_DEPLOY_DRIFT capacity -effective_retention_window_in_days absent 2 POST_DEPLOY_DRIFT capacity -effective_stopped absent true POST_DEPLOY_DRIFT capacity -effective_stopped false absent POST_DEPLOY_DRIFT capacity -effective_stopped false true POST_DEPLOY_DRIFT capacity -effective_stopped true absent POST_DEPLOY_DRIFT capacity -effective_stopped true false POST_DEPLOY_DRIFT capacity -effective_usage_policy_id absent x POST_DEPLOY_DRIFT capacity -effective_usage_policy_id absent y POST_DEPLOY_DRIFT capacity -effective_usage_policy_id x absent POST_DEPLOY_DRIFT capacity -effective_usage_policy_id x y POST_DEPLOY_DRIFT capacity -effective_usage_policy_id y absent POST_DEPLOY_DRIFT capacity -effective_usage_policy_id y x POST_DEPLOY_DRIFT capacity -enable_pg_native_login absent true POST_DEPLOY_DRIFT capacity -enable_pg_native_login false absent POST_DEPLOY_DRIFT capacity -enable_pg_native_login false true POST_DEPLOY_DRIFT capacity -enable_pg_native_login true absent POST_DEPLOY_DRIFT capacity -enable_pg_native_login true false POST_DEPLOY_DRIFT capacity -enable_readable_secondaries absent true POST_DEPLOY_DRIFT capacity -enable_readable_secondaries false true POST_DEPLOY_DRIFT capacity -enable_readable_secondaries true absent POST_DEPLOY_DRIFT capacity -enable_readable_secondaries true false POST_DEPLOY_DRIFT capacity -node_count 1 2 POST_DEPLOY_DRIFT capacity -node_count 1 absent POST_DEPLOY_DRIFT capacity -node_count 2 1 POST_DEPLOY_DRIFT capacity -node_count 2 absent POST_DEPLOY_DRIFT capacity -node_count absent 1 POST_DEPLOY_DRIFT capacity -node_count absent 2 POST_DEPLOY_DRIFT capacity -pg_version absent x POST_DEPLOY_DRIFT capacity -pg_version absent y POST_DEPLOY_DRIFT capacity -pg_version x absent POST_DEPLOY_DRIFT capacity -pg_version x y POST_DEPLOY_DRIFT capacity -pg_version y absent POST_DEPLOY_DRIFT capacity -pg_version y x POST_DEPLOY_DRIFT capacity -read_only_dns absent x POST_DEPLOY_DRIFT capacity -read_only_dns absent y POST_DEPLOY_DRIFT capacity -read_only_dns x absent POST_DEPLOY_DRIFT capacity -read_only_dns x y POST_DEPLOY_DRIFT capacity -read_only_dns y absent POST_DEPLOY_DRIFT capacity -read_only_dns y x POST_DEPLOY_DRIFT capacity -read_write_dns absent x POST_DEPLOY_DRIFT capacity -read_write_dns absent y POST_DEPLOY_DRIFT capacity -read_write_dns x absent POST_DEPLOY_DRIFT capacity -read_write_dns x y POST_DEPLOY_DRIFT capacity -read_write_dns y absent POST_DEPLOY_DRIFT capacity -read_write_dns y x POST_DEPLOY_DRIFT capacity -retention_window_in_days 1 2 POST_DEPLOY_DRIFT capacity -retention_window_in_days 1 absent POST_DEPLOY_DRIFT capacity -retention_window_in_days 2 1 POST_DEPLOY_DRIFT capacity -retention_window_in_days 2 absent POST_DEPLOY_DRIFT capacity -retention_window_in_days absent 1 POST_DEPLOY_DRIFT capacity -retention_window_in_days absent 2 POST_DEPLOY_DRIFT capacity -state absent x POST_DEPLOY_DRIFT capacity -state absent y POST_DEPLOY_DRIFT capacity -state x absent POST_DEPLOY_DRIFT capacity -state x y POST_DEPLOY_DRIFT capacity -state y absent POST_DEPLOY_DRIFT capacity -state y x POST_DEPLOY_DRIFT capacity -stopped absent true POST_DEPLOY_DRIFT capacity -stopped false true POST_DEPLOY_DRIFT capacity -stopped true absent POST_DEPLOY_DRIFT capacity -stopped true false POST_DEPLOY_DRIFT capacity -uid absent x POST_DEPLOY_DRIFT capacity -uid absent y POST_DEPLOY_DRIFT capacity -uid x absent POST_DEPLOY_DRIFT capacity -uid x y POST_DEPLOY_DRIFT capacity -uid y absent POST_DEPLOY_DRIFT capacity -uid y x POST_DEPLOY_DRIFT capacity -usage_policy_id absent x POST_DEPLOY_DRIFT capacity -usage_policy_id absent y POST_DEPLOY_DRIFT capacity -usage_policy_id x absent POST_DEPLOY_DRIFT capacity -usage_policy_id x y POST_DEPLOY_DRIFT capacity -usage_policy_id y absent POST_DEPLOY_DRIFT capacity -usage_policy_id y x POST_DEPLOY_DRIFT capacity +capacity absent x UPDATE_IGNORED capacity +capacity absent y UPDATE_IGNORED capacity +capacity x y UPDATE_IGNORED capacity +capacity y x UPDATE_IGNORED capacity +custom_tags absent len0 COLLATERAL_DRIFT capacity +custom_tags absent len1 COLLATERAL_DRIFT capacity +custom_tags len0 absent COLLATERAL_DRIFT capacity +custom_tags len0 len1 COLLATERAL_DRIFT capacity +custom_tags len1 absent COLLATERAL_DRIFT capacity +custom_tags len1 len0 COLLATERAL_DRIFT capacity +custom_tags[0].key absent x COLLATERAL_DRIFT capacity +custom_tags[0].key absent y COLLATERAL_DRIFT capacity +custom_tags[0].key x absent COLLATERAL_DRIFT capacity +custom_tags[0].key x y COLLATERAL_DRIFT capacity +custom_tags[0].key y absent COLLATERAL_DRIFT capacity +custom_tags[0].key y x COLLATERAL_DRIFT capacity +custom_tags[0].value absent x COLLATERAL_DRIFT capacity +custom_tags[0].value absent y COLLATERAL_DRIFT capacity +custom_tags[0].value x absent COLLATERAL_DRIFT capacity +custom_tags[0].value x y COLLATERAL_DRIFT capacity +custom_tags[0].value y absent COLLATERAL_DRIFT capacity +custom_tags[0].value y x COLLATERAL_DRIFT capacity +enable_pg_native_login absent false COLLATERAL_DRIFT capacity +enable_pg_native_login absent true COLLATERAL_DRIFT capacity +enable_pg_native_login false absent COLLATERAL_DRIFT capacity +enable_pg_native_login false true COLLATERAL_DRIFT capacity +enable_pg_native_login true absent COLLATERAL_DRIFT capacity +enable_pg_native_login true false COLLATERAL_DRIFT capacity +enable_readable_secondaries absent false COLLATERAL_DRIFT capacity +enable_readable_secondaries absent true COLLATERAL_DRIFT capacity +enable_readable_secondaries false absent COLLATERAL_DRIFT capacity +enable_readable_secondaries false true COLLATERAL_DRIFT capacity +enable_readable_secondaries true absent COLLATERAL_DRIFT capacity +enable_readable_secondaries true false COLLATERAL_DRIFT capacity +node_count 1 2 COLLATERAL_DRIFT capacity +node_count 1 absent COLLATERAL_DRIFT capacity +node_count 2 1 COLLATERAL_DRIFT capacity +node_count 2 absent COLLATERAL_DRIFT capacity +node_count absent 1 COLLATERAL_DRIFT capacity +node_count absent 2 COLLATERAL_DRIFT capacity +retention_window_in_days 1 2 COLLATERAL_DRIFT capacity +retention_window_in_days 1 absent COLLATERAL_DRIFT capacity +retention_window_in_days 2 1 COLLATERAL_DRIFT capacity +retention_window_in_days 2 absent COLLATERAL_DRIFT capacity +retention_window_in_days absent 1 COLLATERAL_DRIFT capacity +retention_window_in_days absent 2 COLLATERAL_DRIFT capacity +stopped absent false COLLATERAL_DRIFT capacity +stopped absent true COLLATERAL_DRIFT capacity +stopped false absent COLLATERAL_DRIFT capacity +stopped false true COLLATERAL_DRIFT capacity +stopped true absent COLLATERAL_DRIFT capacity +stopped true false COLLATERAL_DRIFT capacity +usage_policy_id absent x COLLATERAL_DRIFT capacity +usage_policy_id absent y COLLATERAL_DRIFT capacity +usage_policy_id x absent COLLATERAL_DRIFT capacity +usage_policy_id x y COLLATERAL_DRIFT capacity +usage_policy_id y absent COLLATERAL_DRIFT capacity +usage_policy_id y x COLLATERAL_DRIFT capacity diff --git a/bundle/direct/tests/output/external_locations.txt b/bundle/direct/tests/output/external_locations.txt index f05326e375f..e69de29bb2d 100644 --- a/bundle/direct/tests/output/external_locations.txt +++ b/bundle/direct/tests/output/external_locations.txt @@ -1,2 +0,0 @@ -=== external_location.yml.tmpl -read_only absent false SUPPRESSED remote_already_set diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index ddf8efdc1c4..263b96b0cf9 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,19 +1,13 @@ === genie_space.yml.tmpl -description x absent POST_DEPLOY_DRIFT description -description y absent POST_DEPLOY_DRIFT description -etag absent x SUPPRESSED custom -etag absent y SUPPRESSED custom -etag x absent SUPPRESSED custom -etag x y SUPPRESSED custom -etag y absent SUPPRESSED custom -etag y x SUPPRESSED custom +description x absent UPDATE_IGNORED description +description y absent UPDATE_IGNORED description parent_path absent x POST_DEPLOY_DRIFT parent_path parent_path absent y POST_DEPLOY_DRIFT parent_path -parent_path x absent POST_DEPLOY_DRIFT parent_path +parent_path x absent UPDATE_IGNORED parent_path parent_path x y POST_DEPLOY_DRIFT parent_path -parent_path y absent POST_DEPLOY_DRIFT parent_path +parent_path y absent UPDATE_IGNORED parent_path parent_path y x POST_DEPLOY_DRIFT parent_path -title x absent POST_DEPLOY_DRIFT title -title y absent POST_DEPLOY_DRIFT title -warehouse_id x absent POST_DEPLOY_DRIFT warehouse_id -warehouse_id y absent POST_DEPLOY_DRIFT warehouse_id +title x absent UPDATE_IGNORED title +title y absent UPDATE_IGNORED title +warehouse_id x absent UPDATE_IGNORED warehouse_id +warehouse_id y absent UPDATE_IGNORED warehouse_id diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt index d763d7b1bc7..c19882aad6e 100644 --- a/bundle/direct/tests/output/instance_pools.txt +++ b/bundle/direct/tests/output/instance_pools.txt @@ -1,5 +1,12 @@ === instance_pool.yml.tmpl -enable_elastic_disk absent true POST_DEPLOY_DRIFT enable_elastic_disk -enable_elastic_disk false absent SUPPRESSED remote_already_set -enable_elastic_disk false true POST_DEPLOY_DRIFT enable_elastic_disk -enable_elastic_disk true absent SUPPRESSED remote_already_set +enable_elastic_disk absent true UPDATE_IGNORED enable_elastic_disk +enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk +node_type_flexibility.alternate_node_type_ids[0] absent x UNSETTABLE index out of bounds at "resources.instance_pools.foo.node_type_flexibility.alternate_node_type_ids" +node_type_flexibility.alternate_node_type_ids[0] absent y UNSETTABLE index out of bounds at "resources.instance_pools.foo.node_type_flexibility.alternate_node_type_ids" +node_type_flexibility.alternate_node_type_ids[0] x y UNSETTABLE index out of bounds at "resources.instance_pools.foo.node_type_flexibility.alternate_node_type_ids" +node_type_flexibility.alternate_node_type_ids[0] y absent UNSETTABLE index out of bounds at "resources.instance_pools.foo.node_type_flexibility.alternate_node_type_ids" +preloaded_spark_versions[0] absent x UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" +preloaded_spark_versions[0] absent y UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" +preloaded_spark_versions[0] x y UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" +preloaded_spark_versions[0] y absent UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" +preloaded_spark_versions[0] y x UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index fbcafc2d677..d483f94c813 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -1,35 +1,13 @@ === job.yml.tmpl -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y x SUPPRESSED managed by the deployment metadata service - -=== job_apply_policy_default_values_for_each_task.yml.tmpl -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y x SUPPRESSED managed by the deployment metadata service - -=== job_apply_policy_default_values_job_cluster.yml.tmpl -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y x SUPPRESSED managed by the deployment metadata service - -=== job_apply_policy_default_values_task_cluster.yml.tmpl -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y x SUPPRESSED managed by the deployment metadata service - -=== job_with_depends_on.yml.tmpl -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y x SUPPRESSED managed by the deployment metadata service - -=== job_with_task.yml.tmpl -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y x SUPPRESSED managed by the deployment metadata service +email_notifications.on_failure[0] absent x UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" +email_notifications.on_failure[0] absent y UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" +email_notifications.on_failure[0] x absent UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" +email_notifications.on_failure[0] x y UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" +email_notifications.on_failure[0] y absent UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" +email_notifications.on_failure[0] y x UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" +environments[0].spec.dependencies[0] absent x UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" +environments[0].spec.dependencies[0] absent y UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" +environments[0].spec.dependencies[0] x absent UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" +environments[0].spec.dependencies[0] x y UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" +environments[0].spec.dependencies[0] y absent UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" +environments[0].spec.dependencies[0] y x UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index a3da1997569..b8fc370e38e 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -1,26 +1,17 @@ === model_serving_endpoint.yml.tmpl -budget_policy_id absent x SUPPRESSED no_update_api -budget_policy_id absent y SUPPRESSED no_update_api -budget_policy_id x y SUPPRESSED no_update_api -budget_policy_id y x SUPPRESSED no_update_api -config.auto_capture_config.catalog_name absent y POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled absent false POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled absent true POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled false absent POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled false true POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled true absent POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled true false POST_DEPLOY_DRIFT config -config.auto_capture_config.schema_name absent y POST_DEPLOY_DRIFT config -config.auto_capture_config.schema_name y absent POST_DEPLOY_DRIFT config - -=== model_serving_endpoint_telemetry.yml.tmpl -budget_policy_id absent x SUPPRESSED no_update_api -budget_policy_id absent y SUPPRESSED no_update_api -budget_policy_id x y SUPPRESSED no_update_api -budget_policy_id y x SUPPRESSED no_update_api -config.served_entities absent len0 POST_DEPLOY_DRIFT config -config.served_entities len0 absent POST_DEPLOY_DRIFT config -config.served_entities len1 absent POST_DEPLOY_DRIFT config -config.served_entities len1 len0 POST_DEPLOY_DRIFT config -description absent x POST_DEPLOY_DRIFT telemetry_config -description x y BASE_ERROR cannot update resources.model_serving_endpoints.foo: updating id=test-endpoint-[UNIQUE_NAME]: failed to update telemetry config: Telemetry c... +config.auto_capture_config.enabled absent false COLLATERAL_DRIFT config +config.auto_capture_config.enabled absent true COLLATERAL_DRIFT config +config.auto_capture_config.enabled false absent COLLATERAL_DRIFT config +config.auto_capture_config.enabled false true COLLATERAL_DRIFT config +config.auto_capture_config.enabled true absent COLLATERAL_DRIFT config +config.auto_capture_config.enabled true false COLLATERAL_DRIFT config +config.auto_capture_config.table_name_prefix y absent COLLATERAL_DRIFT config +email_notifications.on_update_failure[0] absent x UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_failure" +email_notifications.on_update_failure[0] absent y UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_failure" +email_notifications.on_update_failure[0] x absent UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_failure" +email_notifications.on_update_success[0] absent x UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" +email_notifications.on_update_success[0] absent y UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" +email_notifications.on_update_success[0] x absent UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" +email_notifications.on_update_success[0] x y UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" +email_notifications.on_update_success[0] y absent UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" +email_notifications.on_update_success[0] y x UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index ba44aecb10f..6b27e8a64fc 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -1,26 +1,33 @@ === pipeline.yml.tmpl -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -dry_run absent true POST_DEPLOY_DRIFT dry_run -dry_run false true POST_DEPLOY_DRIFT dry_run -dry_run true absent SUPPRESSED remote_already_set - -=== pipeline_allow_duplicate_names.yml.tmpl -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -dry_run absent true POST_DEPLOY_DRIFT dry_run -dry_run false true POST_DEPLOY_DRIFT dry_run -dry_run true absent SUPPRESSED remote_already_set - -=== pipeline_apply_policy_default_values.yml.tmpl -deployment.version_id absent x SUPPRESSED managed by the deployment metadata service -deployment.version_id absent y SUPPRESSED managed by the deployment metadata service -deployment.version_id x y SUPPRESSED managed by the deployment metadata service -deployment.version_id y x SUPPRESSED managed by the deployment metadata service -dry_run absent true POST_DEPLOY_DRIFT dry_run -dry_run false true POST_DEPLOY_DRIFT dry_run -dry_run true absent SUPPRESSED remote_already_set +clusters[0].ssh_public_keys[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.clusters[0].ssh_public_keys" +clusters[0].ssh_public_keys[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.clusters[0].ssh_public_keys" +clusters[0].ssh_public_keys[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.clusters[0].ssh_public_keys" +dry_run absent true UPDATE_IGNORED dry_run +dry_run false true UPDATE_IGNORED dry_run +environment.dependencies[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" +environment.dependencies[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" +environment.dependencies[0] x absent UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" +environment.dependencies[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" +environment.dependencies[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" +environment.dependencies[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" +filters.exclude[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" +filters.exclude[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" +filters.exclude[0] x absent UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" +filters.exclude[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" +filters.exclude[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" +filters.include[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" +filters.include[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" +filters.include[0] x absent UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" +filters.include[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" +filters.include[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" +filters.include[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" +notifications[0].alerts[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" +notifications[0].alerts[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" +notifications[0].alerts[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" +notifications[0].alerts[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" +notifications[0].alerts[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" +notifications[0].email_recipients[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" +notifications[0].email_recipients[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" +notifications[0].email_recipients[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" +notifications[0].email_recipients[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" +notifications[0].email_recipients[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index 097381460a2..caa99780019 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -1,6 +1,21 @@ === postgres_project.yml.tmpl budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +custom_tags len1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +custom_tags len1 len0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +custom_tags[0].key absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key absent y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key x y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value absent x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value absent y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_branch x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_branch y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... @@ -9,9 +24,15 @@ default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension true false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +default_endpoint_settings.pg_settings absent keys0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... +default_endpoint_settings.pg_settings keys1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... +default_endpoint_settings.pg_settings keys1 keys0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] x y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... diff --git a/bundle/direct/tests/output/postgres_synced_tables.txt b/bundle/direct/tests/output/postgres_synced_tables.txt index e3554a486c6..9d5bca81072 100644 --- a/bundle/direct/tests/output/postgres_synced_tables.txt +++ b/bundle/direct/tests/output/postgres_synced_tables.txt @@ -1,5 +1,6 @@ === postgres_synced_table.yml.tmpl -primary_key_columns[0] absent x UNSETTABLE cannot remove resources.postgres_synced_tables.foo.primary_key_columns[0]: not a map key -primary_key_columns[0] absent y UNSETTABLE cannot remove resources.postgres_synced_tables.foo.primary_key_columns[0]: not a map key -primary_key_columns[0] x absent UNSETTABLE cannot remove resources.postgres_synced_tables.foo.primary_key_columns[0]: not a map key -primary_key_columns[0] y absent UNSETTABLE cannot remove resources.postgres_synced_tables.foo.primary_key_columns[0]: not a map key +primary_key_columns[0] absent x UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" +primary_key_columns[0] absent y UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" +primary_key_columns[0] x y UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" +primary_key_columns[0] y absent UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" +primary_key_columns[0] y x UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index fa73ab0a15f..10414736d1b 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,9 +1,12 @@ === registered_model.yml.tmpl -full_name absent x POST_DEPLOY_DRIFT full_name -full_name absent y POST_DEPLOY_DRIFT full_name -full_name x y POST_DEPLOY_DRIFT full_name -full_name y x POST_DEPLOY_DRIFT full_name -owner absent x POST_DEPLOY_DRIFT owner -owner absent y POST_DEPLOY_DRIFT owner -owner x y POST_DEPLOY_DRIFT owner -owner y x POST_DEPLOY_DRIFT owner +aliases absent len0 UPDATE_IGNORED aliases +aliases len0 absent SUPPRESSED input_only +aliases len1 len0 UPDATE_IGNORED aliases +full_name absent x UPDATE_IGNORED full_name +full_name absent y UPDATE_IGNORED full_name +full_name x y UPDATE_IGNORED full_name +full_name y x UPDATE_IGNORED full_name +owner absent x UPDATE_IGNORED owner +owner absent y UPDATE_IGNORED owner +owner x y UPDATE_IGNORED owner +owner y x UPDATE_IGNORED owner diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index dd6de49f92d..3653dfd024a 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -1,11 +1,9 @@ === schema.yml.tmpl -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours - -=== schema_empty_grants.yml.tmpl -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours - -=== schema_uppercase_name.yml.tmpl -custom_max_retention_hours 1 absent POST_DEPLOY_DRIFT custom_max_retention_hours -custom_max_retention_hours 2 absent POST_DEPLOY_DRIFT custom_max_retention_hours +custom_max_retention_hours 1 absent UPDATE_IGNORED custom_max_retention_hours +custom_max_retention_hours 2 absent UPDATE_IGNORED custom_max_retention_hours +properties absent keys0 UPDATE_IGNORED properties +properties keys0 absent UPDATE_IGNORED properties +properties keys1 absent UPDATE_IGNORED properties +properties keys1 keys0 UPDATE_IGNORED properties +properties['team'] x absent UPDATE_IGNORED properties['team'] +properties['team'] y absent UPDATE_IGNORED properties['team'] diff --git a/bundle/direct/tests/output/secret_scopes.txt b/bundle/direct/tests/output/secret_scopes.txt index fbe57427472..e69de29bb2d 100644 --- a/bundle/direct/tests/output/secret_scopes.txt +++ b/bundle/direct/tests/output/secret_scopes.txt @@ -1,2 +0,0 @@ -=== secret_scope.yml.tmpl -name x y DEPLOY_ERROR cannot recreate resources.secret_scopes.foo: Scope y already exists (400 ) diff --git a/bundle/direct/tests/output/volumes.txt b/bundle/direct/tests/output/volumes.txt index 82c6ab35c96..e69de29bb2d 100644 --- a/bundle/direct/tests/output/volumes.txt +++ b/bundle/direct/tests/output/volumes.txt @@ -1,3 +0,0 @@ -=== volume.yml.tmpl -name x y POST_DEPLOY_DRIFT volume_type -name y x POST_DEPLOY_DRIFT create diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index c4962b63d5f..b6518dda242 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io/fs" - "maps" "os" "path/filepath" "slices" @@ -36,9 +35,37 @@ const ( verdictNotObservable verdict = "NOT_OBSERVABLE" // The config changed, the field diff exists, but the plan produced no action at all. verdictNoPlan verdict = "NO_PLAN" - // The apply succeeded, but the plan taken straight afterwards still proposes the same - // change to this field, so deploying never converges. + // The field drifts with no config change at all: right after the base deploy, the plan + // already wants to update it. Usually the read does not echo the field, so state and + // remote never agree. Recorded once per config against the field itself -- otherwise it + // makes every other field's post-deploy plan dirty and gets blamed on all of them. + verdictBaselineDrift verdict = "BASELINE_DRIFT" + // The apply succeeded and the engine sent the write, but the field's remote value is + // exactly what it was beforehand, on two consecutive reads: the backend accepted the + // request and ignored this field. The most common shape is a field being cleared, where + // the update request type is omitempty so the field never makes it into the body. + // + // Two reads, not one, is what separates this from STALE_READ. + verdictUpdateIgnored verdict = "UPDATE_IGNORED" + // The write did land, but the read straight after the apply did not show it and the one + // after that did. Not a field-support gap, yet worth seeing: a user planning right + // after a deploy is shown a change that does not exist. + verdictStaleRead verdict = "STALE_READ" + // The apply succeeded, but the plan taken straight afterwards still proposes a change + // to this field and the remote moved to something else again, so deploying never + // converges for a reason other than a plainly ignored write. verdictDrift verdict = "POST_DEPLOY_DRIFT" + // The resource declares that it ignores local changes to this field, and it does: the + // change came back suppressed with exactly the declared reason. Recorded so the claim is + // verified rather than assumed. + verdictInertConfirmed verdict = "OK_INERT" + // The resource declares that it ignores local changes to this field, and it does not. + // Either the declaration is wrong or the engine stopped honouring it. + verdictInertViolated verdict = "INERT_NOT_HONOURED" + // The field under test converged, but updating it left some *other* field of the same + // resource drifting. The detail names that other field, which is where the fix belongs: + // one such field makes every deploy of the resource dirty regardless of what changed. + verdictCollateral verdict = "COLLATERAL_DRIFT" // The field itself converged, but some other node of the resource is still drifted // post-deploy. Sub-resources are stripped before planning, so nothing should reach // this: it guards against blaming a field for drift that is not its own. @@ -70,9 +97,11 @@ const ( // resource, or one real failure cascades into a run of unrelated ones. func (v verdict) leavesResourceUsable() bool { switch v { - case verdictOK, verdictRecreate, verdictSuppressed, verdictNotObservable, verdictUnsettable, verdictSkipped: + case verdictOK, verdictRecreate, verdictSuppressed, verdictNotObservable, verdictUnsettable, + verdictSkipped, verdictInertConfirmed, verdictInertViolated: return true - case verdictDrift, verdictDriftChild: + case verdictDrift, verdictCollateral, verdictDriftChild, verdictUpdateIgnored, + verdictStaleRead, verdictBaselineDrift: // The resource exists and still matches what the engine last sent; only the one // field did not stick. The next transition re-deploys the field anyway. return true @@ -81,19 +110,29 @@ func (v verdict) leavesResourceUsable() bool { } } -// reasonEmpty is the planner's own reason for dropping a change whose old and new values -// are both empty -- an unset bool against an explicit false, say. Nothing the user -// expressed is lost, so it reads like OK and belongs in the full report only. -const reasonEmpty = "empty" +// benignSuppressions are the planner's own reasons for dropping a change where nothing the +// user asked for is lost, so the outcome reads like OK and belongs in the full report only. +// +// - empty: old and new are both empty -- an unset bool against an explicit false +// - remote_already_set: the remote already holds the value the config asks for +// - backend_default: the config asks for nothing and the backend chose, by design +// +// Every other reason means a change the user expressed was dropped, which is a finding +// however defensible it is -- terraform_compat and no_update_api being the clearest. +var benignSuppressions = map[string]bool{ + "empty": true, + "remote_already_set": true, + "backend_default": true, +} // isProblem reports whether a result is something a person should look at. The committed // report lists only these; the .full.txt companion lists everything. func (r result) isProblem() bool { switch r.verdict { - case verdictOK, verdictRecreate, verdictNotObservable, verdictSkipped: + case verdictOK, verdictRecreate, verdictNotObservable, verdictSkipped, verdictInertConfirmed: return false case verdictSuppressed: - return r.detail != reasonEmpty + return !benignSuppressions[r.detail] default: return true } @@ -114,14 +153,16 @@ type result struct { evidence string } -// report accumulates results for one resource type. The configs of a type run in -// parallel, so both fields are guarded. +// report accumulates results for one resource type. Resource types run in parallel but +// each has its own report, and a type's configs run in sequence, so the lock only guards +// against a future change to that. type report struct { resourceType string mu sync.Mutex results []result wildcard map[string]bool + covered map[string]bool } func (r *report) add(res result) { @@ -130,16 +171,63 @@ func (r *report) add(res result) { r.results = append(r.results, res) } -// addNotCovered records a field that no config of this type could reach. -func (r *report) addNotCovered(paths []string) { +// addCoverage records what one config reached and what it could not. A field is only +// reported as not covered when *no* config of the type reached it: several configs of one +// resource declare different blocks, so a union of the per-config gaps would list fields +// that are in fact tested -- jobs has eight configs and only some declare tasks. +func (r *report) addCoverage(covered []field, notCovered []string) { r.mu.Lock() defer r.mu.Unlock() if r.wildcard == nil { r.wildcard = map[string]bool{} } - for _, p := range paths { + if r.covered == nil { + r.covered = map[string]bool{} + } + for _, p := range notCovered { r.wildcard[p] = true } + for _, f := range covered { + r.covered[patternOf(f.path)] = true + } +} + +// patternOf turns a concrete path back into the pattern it came from, so a field reached +// through one config's slice cancels the same pattern reported by a config without it. +func patternOf(path string) string { + var sb strings.Builder + for i := 0; i < len(path); i++ { + if path[i] != '[' { + sb.WriteByte(path[i]) + continue + } + end := strings.IndexByte(path[i:], ']') + if end < 0 { + sb.WriteByte(path[i]) + continue + } + inner := path[i+1 : i+end] + if strings.HasPrefix(inner, "'") { + // A map key: the walk pattern for a map is ".*", not "[*]". + sb.WriteString(".*") + } else { + sb.WriteString("[*]") + } + i += end + } + return sb.String() +} + +// uncoveredPaths returns the patterns no config of this type reached. Caller holds the lock. +func (r *report) uncoveredPaths() []string { + var out []string + for path := range r.wildcard { + if !r.covered[path] { + out = append(out, path) + } + } + slices.Sort(out) + return out } // render produces a report body. Problems-only is the committed form: one line per @@ -181,8 +269,10 @@ func (r *report) render(problemsOnly bool) string { sb.WriteString(strings.TrimRight(line, " ") + "\n") if !problemsOnly && res.evidence != "" { + // Indented, but with nothing prefixed: leading whitespace is insignificant to + // JSON, so the block can be copied straight out and parsed. for evidenceLine := range strings.SplitSeq(strings.TrimRight(res.evidence, "\n"), "\n") { - sb.WriteString(" | " + evidenceLine + "\n") + sb.WriteString(" " + evidenceLine + "\n") } } } @@ -193,8 +283,10 @@ func (r *report) render(problemsOnly bool) string { sb.WriteString("\n=== summary\n") for _, v := range []verdict{ - verdictOK, verdictRecreate, verdictSuppressed, verdictNotObservable, verdictNoPlan, - verdictDrift, verdictDriftChild, + verdictOK, verdictRecreate, verdictInertConfirmed, verdictSuppressed, + verdictNotObservable, verdictNoPlan, verdictInertViolated, + verdictBaselineDrift, verdictStaleRead, verdictUpdateIgnored, verdictDrift, + verdictCollateral, verdictDriftChild, verdictBackendError, verdictDeployError, verdictTimeout, verdictPlanError, verdictUnsettable, verdictSkipped, verdictBaseError, } { @@ -203,9 +295,9 @@ func (r *report) render(problemsOnly bool) string { } } - if len(r.wildcard) > 0 { - paths := slices.Sorted(maps.Keys(r.wildcard)) - fmt.Fprintf(&sb, "\n=== not covered: %d fields under an absent slice or map\n", len(paths)) + if gaps := r.uncoveredPaths(); len(gaps) > 0 { + paths := gaps + fmt.Fprintf(&sb, "\n=== not covered: %d fields no config of this type declares\n", len(paths)) for _, p := range paths { fmt.Fprintf(&sb, "%s\n", p) } @@ -217,22 +309,24 @@ func (r *report) render(problemsOnly bool) string { // write compares the report against its golden file, or rewrites it under -update. // Cloud runs go to a separate, uncommitted file: which values a real backend accepts // depends on the workspace, so those results are for reading, not for diffing. +// write compares the findings against the committed golden. There is one golden, and both +// the fake server and a real workspace are held to it: a divergence means the fake server +// does not model the API faithfully, which is worth failing over rather than filing away in +// a per-cloud file nobody diffs. +// +// -update rewrites it, so a cloud run can be used to correct a golden the fake server got +// wrong. The full form is never compared -- it is an aid for reading one run, and it moves +// whenever a passing row moves. func (r *report) write(t testutil.TestingT) { - suffix := ".txt" - if isCloud() { - suffix = "." + cloudName() + ".txt" - } - name := reportPath(r.resourceType + suffix) + name := reportPath(r.resourceType + ".txt") body := r.render(true) - // The full form is never compared: it is a local aid for reading a run, and it moves - // whenever a passing row moves. - writeReport(t, reportPath(r.resourceType+strings.TrimSuffix(suffix, ".txt")+".full.txt"), r.render(false)) - + fullName := r.resourceType + ".full.txt" if isCloud() { - writeReport(t, name, body) - return + // Keep both around locally when comparing a cloud run against the fake server. + fullName = r.resourceType + "." + cloudName() + ".full.txt" } + writeReport(t, reportPath(fullName), r.render(false)) if testdiff.OverwriteMode { writeReport(t, name, body) diff --git a/bundle/direct/tests/testdata/fields/alerts.yml b/bundle/direct/tests/testdata/fields/alerts.yml new file mode 100644 index 00000000000..0cc975c74b7 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/alerts.yml @@ -0,0 +1,13 @@ +# Value library for alerts. See ../../README.md. + +# The invariant config declares neither block, so their fields would only ever be reported +# as not covered. Seeding one entry each makes both testable: the containers themselves and +# the fields inside an entry. +base: + evaluation: + notification: + subscriptions: + - user_email: tester@databricks.com + parameters: + - name: threshold + value: "1" diff --git a/bundle/direct/tests/testdata/fields/apps.yml b/bundle/direct/tests/testdata/fields/apps.yml index 1b073246c16..c5589d5a954 100644 --- a/bundle/direct/tests/testdata/fields/apps.yml +++ b/bundle/direct/tests/testdata/fields/apps.yml @@ -1,4 +1,22 @@ -# Value library for apps. See ../README.md. +# Value library for apps. See ../../README.md. + +# The corpus config declares neither block. config carries the app's command and env; the +# resources list is a union where an entry names one referenced object. +base: + config: + command: + - python + - app.py + env: + - name: MY_VAR + value: value + resources: + - name: seeded + description: seeded resource + secret: + scope: my-scope + key: my-key + permission: READ fields: compute_size: [MEDIUM, LARGE] diff --git a/bundle/direct/tests/testdata/fields/catalogs.yml b/bundle/direct/tests/testdata/fields/catalogs.yml new file mode 100644 index 00000000000..b5f5c3c4312 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/catalogs.yml @@ -0,0 +1,6 @@ +# Value library for catalogs. See ../../README.md. +base: + properties: + team: eng + options: + region: us-east-1 diff --git a/bundle/direct/tests/testdata/fields/cluster_policies.yml b/bundle/direct/tests/testdata/fields/cluster_policies.yml new file mode 100644 index 00000000000..3dddfc36ca9 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/cluster_policies.yml @@ -0,0 +1,13 @@ +# Value library for cluster policies. See ../../README.md. + +# libraries is a union: an entry carries exactly one of jar, egg, whl, pypi, maven or cran. +# Seeding one entry makes the container and that entry's fields testable; the other members +# of the union are then reachable from absent, and the ones the API rejects alongside jar are +# recorded as such rather than left uncovered. +base: + libraries: + - jar: dbfs:/FileStore/test.jar + - maven: + coordinates: com.example:lib:1.0 + exclusions: + - com.other:unwanted diff --git a/bundle/direct/tests/testdata/fields/clusters.yml b/bundle/direct/tests/testdata/fields/clusters.yml new file mode 100644 index 00000000000..d029196c887 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/clusters.yml @@ -0,0 +1,26 @@ +# Value library for clusters. See ../../README.md. + +# init_scripts is a union: an entry carries exactly one of workspace, volumes, s3, abfss, +# gcs, dbfs or file. Two entries make the two most-configured members reachable; the rest +# stay uncovered for this config, which the report says. +base: + custom_tags: + team: eng + spark_conf: + spark.speculation: "true" + spark_env_vars: + MY_VAR: value + ssh_public_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQtest + init_scripts: + - workspace: + destination: /Users/tester@databricks.com/init.sh + - s3: + destination: s3://bucket/init.sh + region: us-east-1 + worker_node_type_flexibility: + alternate_node_type_ids: + - i3.2xlarge + driver_node_type_flexibility: + alternate_node_type_ids: + - i3.2xlarge diff --git a/bundle/direct/tests/testdata/fields/database_instances.yml b/bundle/direct/tests/testdata/fields/database_instances.yml new file mode 100644 index 00000000000..6ab00f3e91c --- /dev/null +++ b/bundle/direct/tests/testdata/fields/database_instances.yml @@ -0,0 +1,5 @@ +# Value library for database instances. See ../../README.md. +base: + custom_tags: + - key: team + value: eng diff --git a/bundle/direct/tests/testdata/fields/experiments.yml b/bundle/direct/tests/testdata/fields/experiments.yml new file mode 100644 index 00000000000..3f13ffb2e2a --- /dev/null +++ b/bundle/direct/tests/testdata/fields/experiments.yml @@ -0,0 +1,9 @@ +# Value library for experiments. See ../../README.md. + +# The invariant config declares no tags, so tags[*] would expand to nothing and its fields +# would only be reported as not covered. Seeding one entry makes the whole block testable: +# the container itself (add and remove an entry) and the fields inside the entry. +base: + tags: + - key: team + value: eng diff --git a/bundle/direct/tests/testdata/fields/instance_pools.yml b/bundle/direct/tests/testdata/fields/instance_pools.yml new file mode 100644 index 00000000000..87259f31ca5 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/instance_pools.yml @@ -0,0 +1,14 @@ +# Value library for instance pools. See ../../README.md. +base: + custom_tags: + team: eng + preloaded_spark_versions: + - 13.3.x-scala2.12 + preloaded_docker_images: + - url: databricksruntime/standard:latest + basic_auth: + username: user + password: pass + node_type_flexibility: + alternate_node_type_ids: + - i3.2xlarge diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml index 3b70c9fb878..6901e4fa258 100644 --- a/bundle/direct/tests/testdata/fields/jobs.yml +++ b/bundle/direct/tests/testdata/fields/jobs.yml @@ -1,13 +1,61 @@ # Value library for jobs. See ../../README.md. # git_source only validates as a coherent set: a provider, a url, and exactly one of -# branch/tag/commit. Seeding one makes its fields reachable, so changing the commit or -# the provider is tested rather than skipped. +# branch/tag/commit. Seeding one makes its fields reachable, so changing the commit or the +# provider is tested rather than skipped. +# +# job_clusters, triggers and environments are declared by no config in the corpus, so their +# fields would go untested; seeding one entry each makes them reachable. base: git_source: git_provider: gitHub git_url: https://github.com/databricks/cli.test git_commit: abc123 + tasks: + - task_key: seeded + notebook_task: + notebook_path: /Shared/notebook + job_cluster_key: seeded + job_clusters: + - job_cluster_key: seeded + new_cluster: + spark_version: 13.3.x-scala2.12 + node_type_id: i3.xlarge + num_workers: 1 + custom_tags: + team: eng + spark_conf: + spark.speculation: "true" + init_scripts: + - workspace: + destination: /Users/tester@databricks.com/init.sh + environments: + - environment_key: seeded + spec: + client: "1" + dependencies: + - requests + parameters: + - name: table + default: users + tags: + team: eng + health: + rules: + - metric: RUN_DURATION_SECONDS + op: GREATER_THAN + value: 3600 + email_notifications: + on_failure: + - tester@databricks.com + webhook_notifications: + on_failure: + - id: 00000000-0000-0000-0000-000000000000 + trigger: + pause_status: UNPAUSED + periodic: + interval: 1 + unit: HOURS skip: # git_branch and git_tag are the other two sides of the same oneof as git_commit, which diff --git a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml index 16a05a73924..56a239abbc6 100644 --- a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml +++ b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml @@ -1,5 +1,33 @@ -# Value library for model serving endpoints. See ../README.md. +# Value library for model serving endpoints. See ../../README.md. +# +# telemetry_config needs no base: and no skip. model_serving_endpoint_telemetry.yml.tmpl +# already declares a coherent block -- a catalog, a schema and table names -- so its fields +# expand and are tested there. +# +# ai_gateway, rate_limits and the notification lists are declared by no config, so they are +# seeded here. served_entities comes from the corpus config. +base: + ai_gateway: + rate_limits: + - calls: 10 + renewal_period: minute + usage_tracking_config: + enabled: true + rate_limits: + - calls: 10 + renewal_period: minute + email_notifications: + on_update_failure: + - tester@databricks.com + on_update_success: + - tester@databricks.com + tags: + - key: team + value: eng + skip: - # The telemetry block is only accepted as a coherent whole, against a catalog and - # schema that exist and are writable, which one-field-at-a-time cannot arrange. - telemetry_config.*: needs a writable catalog and schema + # The corpus config for this type declares no telemetry, and seeding a coherent block here + # is rejected on every later update ("failed to update telemetry config"). The fields are + # covered by model_serving_endpoint_telemetry.yml.tmpl, which declares the whole block -- + # but that config is not the simplest one, so it is not the one this suite picks. + telemetry_config.*: needs the telemetry corpus config, which is not the simplest one diff --git a/bundle/direct/tests/testdata/fields/models.yml b/bundle/direct/tests/testdata/fields/models.yml new file mode 100644 index 00000000000..c2518d7cf46 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/models.yml @@ -0,0 +1,5 @@ +# Value library for MLflow models. See ../../README.md. +base: + tags: + - key: team + value: eng diff --git a/bundle/direct/tests/testdata/fields/pipelines.yml b/bundle/direct/tests/testdata/fields/pipelines.yml new file mode 100644 index 00000000000..ea06cb7cf54 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/pipelines.yml @@ -0,0 +1,48 @@ +# Value library for pipelines. See ../../README.md. + +# No pipeline config in the corpus declares a clusters block, so its whole spec -- 346 +# fields, including the init_scripts union and the tag maps -- would go untested. +base: + clusters: + - label: default + node_type_id: i3.xlarge + num_workers: 1 + custom_tags: + team: eng + spark_conf: + spark.speculation: "true" + spark_env_vars: + MY_VAR: value + ssh_public_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQtest + init_scripts: + - workspace: + destination: /Users/tester@databricks.com/init.sh + - s3: + destination: s3://bucket/init.sh + region: us-east-1 + configuration: + my.key: value + environment: + dependencies: + - requests + filters: + include: + - included + exclude: + - excluded + libraries: + - notebook: + path: /Shared/notebook + notifications: + - alerts: + - on-update-failure + email_recipients: + - tester@databricks.com + restart_window: + start_hour: 1 + time_zone_id: UTC + tags: + team: eng + parameters: + table: users diff --git a/bundle/direct/tests/testdata/fields/postgres_projects.yml b/bundle/direct/tests/testdata/fields/postgres_projects.yml index b679eb9cd9a..c1710251f0c 100644 --- a/bundle/direct/tests/testdata/fields/postgres_projects.yml +++ b/bundle/direct/tests/testdata/fields/postgres_projects.yml @@ -1,4 +1,12 @@ -# Value library for postgres projects. See ../README.md. +# Value library for postgres projects. See ../../README.md. +base: + custom_tags: + - key: team + value: eng + default_endpoint_settings: + pg_settings: + work_mem: 64MB + fields: # project_id is part of the resource's hierarchical name, so a change recreates it. project_id: [pgproj1, pgproj2] diff --git a/bundle/direct/tests/testdata/fields/postgres_synced_tables.yml b/bundle/direct/tests/testdata/fields/postgres_synced_tables.yml new file mode 100644 index 00000000000..0c91e1528ba --- /dev/null +++ b/bundle/direct/tests/testdata/fields/postgres_synced_tables.yml @@ -0,0 +1,8 @@ +# Value library for postgres synced tables. See ../../README.md. +base: + extra_columns: + - column_name: ingested_at + column_type: TIMESTAMP + type_overrides: + - column_name: id + pg_type: bigint diff --git a/bundle/direct/tests/testdata/fields/registered_models.yml b/bundle/direct/tests/testdata/fields/registered_models.yml new file mode 100644 index 00000000000..fc83568da45 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/registered_models.yml @@ -0,0 +1,9 @@ +# Value library for registered models. See ../../README.md. +# +# aliases is output-only in practice -- an alias is created through its own API, not by +# writing the model -- but the OpenAPI spec does not annotate it, so it is not skipped +# automatically. Seeding one lets the suite record what actually happens. +base: + aliases: + - alias_name: champion + version_num: 1 diff --git a/bundle/direct/tests/testdata/fields/schemas.yml b/bundle/direct/tests/testdata/fields/schemas.yml index 58cdd10c2f3..8ac0ca724fe 100644 --- a/bundle/direct/tests/testdata/fields/schemas.yml +++ b/bundle/direct/tests/testdata/fields/schemas.yml @@ -1,10 +1,10 @@ -# Value library for schemas. See ../README.md. +# Value library for schemas. See ../../README.md. + +# properties is a map the config does not declare, so its entries would be unreachable. +base: + properties: + team: eng + skip: # storage_root must point at a real external location. storage_root: needs an external location - -fields: - # A real workspace has no catalog named "x", so the generic string values would only - # ever report BACKEND_ERROR. "main" exists everywhere; with one value the field still - # gets its add and remove transitions. - catalog_name: [main] diff --git a/bundle/direct/tests/testdata/fields/sql_warehouses.yml b/bundle/direct/tests/testdata/fields/sql_warehouses.yml new file mode 100644 index 00000000000..7306a2a3fdb --- /dev/null +++ b/bundle/direct/tests/testdata/fields/sql_warehouses.yml @@ -0,0 +1,6 @@ +# Value library for SQL warehouses. See ../../README.md. +base: + tags: + custom_tags: + - key: team + value: eng diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index dee9929d6c6..2a03ffd5f10 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -6,6 +6,7 @@ import ( "fmt" "hash/fnv" "io/fs" + "maps" "math/rand/v2" "os" "path/filepath" @@ -14,6 +15,7 @@ import ( "strconv" "strings" + "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/bundle/internal/validation/generated" "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/dyn/yamlloader" @@ -52,11 +54,86 @@ type fieldValues struct { // fieldsDir holds the per-resource-type value libraries. const fieldsDir = "testdata/fields" +// declaredUnsettable collects the fields the resource declares as backend outputs. A user +// cannot meaningfully set one at all -- they are in the input schema by accident -- so they +// are skipped and the resource's own reason recorded instead of spending six deploys per +// field rediscovering it. +// +// This and declaredIgnoredLocally are the only places the suite reads resources.yml. +func declaredUnsettable(adapter *dresources.Adapter) []dresources.FieldRule { + var rules []dresources.FieldRule + for _, cfg := range lifecycleConfigs(adapter) { + // Only outputs are unsettable. An input_only or managed field is one the backend + // owns on read but the user may still legitimately write, so those stay in. + for _, rule := range cfg.IgnoreRemoteChanges { + if strings.HasSuffix(rule.Reason, "output_only") { + rules = append(rules, rule) + } + } + } + return rules +} + +// declaredIgnoredLocally collects the fields a resource says it drops local changes to. +// These are *not* skipped: the declaration is a claim about behaviour, and the suite is in a +// position to check it. A transition of one should come back suppressed with that same +// reason; anything else means the field is not actually inert and the declaration is wrong. +func declaredIgnoredLocally(adapter *dresources.Adapter) []dresources.FieldRule { + var rules []dresources.FieldRule + for _, cfg := range lifecycleConfigs(adapter) { + rules = append(rules, cfg.IgnoreLocalChanges...) + } + return rules +} + +func lifecycleConfigs(adapter *dresources.Adapter) []*dresources.ResourceLifecycleConfig { + var out []*dresources.ResourceLifecycleConfig + for _, cfg := range []*dresources.ResourceLifecycleConfig{adapter.ResourceConfig(), adapter.GeneratedResourceConfig()} { + if cfg != nil { + out = append(out, cfg) + } + } + return out +} + +// ruleReason reports the reason a field matches one of the given rules. Accepts both a +// concrete path ("tags[0].key") and a pattern ("tags[*].key"): the second form is needed +// for a field whose container the config does not declare, where no concrete path exists. +func ruleReason(rules []dresources.FieldRule, path string) (string, bool) { + if concrete, err := structpath.ParsePath(path); err == nil { + for _, rule := range rules { + // The same match the planner makes, so the suite and the engine agree on scope. + if concrete.HasPatternPrefix(rule.Field) { + return rule.Reason, true + } + } + return "", false + } + + // A pattern: compare textually, since one pattern cannot be matched against another. + for _, rule := range rules { + declared := rule.Field.String() + if path == declared || strings.HasPrefix(path, declared+".") || strings.HasPrefix(path, declared+"[") { + return rule.Reason, true + } + } + return "", false +} + +// cliManagedFields are fields the bundle acts on itself and never sends to any API, so no +// transition of one can appear in a plan. Only fields with no API counterpart at all belong +// here: a field the CLI merely *overwrites* is still worth testing, because the resulting +// verdict is the evidence that it does. +var cliManagedFields = map[string]string{ + "lifecycle.prevent_destroy": "acted on by the bundle, never sent to any API; gates destroy", +} + // loadFieldValues reads testdata/fields/.yml. Parsing goes through the repo's own // yamlloader rather than a yaml package, so the values arrive as dyn.Value -- the same // representation the bundle config uses, which is what they are written into. func loadFieldValues(resourceType string) (*fieldValues, error) { fv := &fieldValues{skip: map[string]string{}, fields: map[string][]dyn.Value{}, base: dyn.InvalidValue} + maps.Copy(fv.skip, cliManagedFields) path := filepath.Join(fieldsDir, resourceType+".yml") data, err := os.ReadFile(path) @@ -98,10 +175,13 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { return fv, nil } -// defaultValues per Go kind. Two values per kind is enough to observe a -// value->value transition on top of add and remove; more would multiply the matrix -// without testing a different code path. -// +// isSubResource reports whether a path belongs to a sub-resource block. +func isSubResource(path string) bool { + name, _, _ := strings.Cut(path, ".") + name, _, _ = strings.Cut(name, "[") + return slices.Contains(subResourceKinds, name) +} + // isContainer reports whether a kind has fields or elements to descend into. func isContainer(kind reflect.Kind) bool { switch kind { @@ -112,6 +192,10 @@ func isContainer(kind reflect.Kind) bool { } } +// defaultValues per Go kind. Two values per kind is enough to observe a value->value +// transition on top of add and remove; more would multiply the matrix without testing a +// different code path. +// //nolint:exhaustive // the default branch covers every kind without a generic value func defaultValues(kind reflect.Kind) []dyn.Value { switch kind { @@ -132,6 +216,11 @@ func defaultValues(kind reflect.Kind) []dyn.Value { // field is one testable leaf of the resource's input struct. type field struct { + // seed drives this field's value order and the order its transitions are walked in. + // Derived from the run seed and the field path, so it is stable for a given commit + // and different for every field. + seed uint64 + path string // structpath/dyn path, e.g. "comment" or "email_notifications.on_failure" kind reflect.Kind values []dyn.Value @@ -173,7 +262,8 @@ func (f field) transitions() []transition { return nil } - rng := rand.New(rand.NewPCG(pathSeed(f.path), 0)) + rng := rand.New(rand.NewPCG(f.seed, 0)) + rng.Shuffle(len(values), func(i, j int) { values[i], values[j] = values[j], values[i] }) unused := make([][]int, len(values)) for from := range values { for to := range values { @@ -210,10 +300,11 @@ func (f field) transitions() []transition { return out } -// pathSeed derives a stable seed from a field path. -func pathSeed(path string) uint64 { +// fieldSeed mixes the run seed with a field path, so every field gets its own order while +// the whole run stays reproducible from the commit alone. +func fieldSeed(runSeed uint64, path string) uint64 { h := fnv.New64a() - _, _ = h.Write([]byte(path)) + _, _ = fmt.Fprintf(h, "%d/%s", runSeed, path) return h.Sum64() } @@ -260,7 +351,8 @@ func isRequired(resourceType, path string) bool { // "tasks[*].description" is expanded to the indices that exist, so the fields inside an // element get the same treatment as any other. A pattern with nothing behind it in the // config is reported as not covered rather than silently tested against nothing. -func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, base dyn.Value) (fields []field, wildcard []string) { +func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, base dyn.Value, runSeed uint64, unsettable []dresources.FieldRule) (fields []field, wildcard []string, inertFields map[string]string) { + inertFields = map[string]string{} add := func(path string, kind reflect.Kind, values []dyn.Value) { if values == nil { return @@ -268,7 +360,12 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue if _, skipped := fv.skipReason(path); skipped { return } + if reason, ok := ruleReason(unsettable, path); ok { + inertFields[path] = reason + return + } fields = append(fields, field{ + seed: fieldSeed(runSeed, path), path: path, kind: kind, values: values, @@ -288,6 +385,12 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue } path := p.String() + if isSubResource(path) { + // permissions and grants are separate plan nodes with their own adapters, and + // this suite strips them from every config. They are out of scope as subjects, + // so they are not fields here and not gaps either. + return false + } isWildcard := strings.Contains(path, "*") if isContainer(typ.Kind()) { @@ -312,7 +415,15 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue concrete := expandPattern(base, path) if len(concrete) == 0 { - wildcard = append(wildcard, path) + // A field the user cannot set is not a coverage gap. Checked on the pattern here + // because there is no concrete path to check: the container is absent. + if reason, ok := ruleReason(unsettable, path); ok { + inertFields[path] = reason + return false + } + if _, skipped := fv.skipReason(path); !skipped { + wildcard = append(wildcard, path) + } return false } for _, c := range concrete { @@ -325,7 +436,14 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue } return false }) - return fields, wildcard + + // Shuffle the fields too. Each field restores the config before the next one starts, + // so the order costs nothing in requests -- but a field that only passes because of + // what ran before it should not keep getting away with it. + rng := rand.New(rand.NewPCG(runSeed, 1)) + rng.Shuffle(len(fields), func(i, j int) { fields[i], fields[j] = fields[j], fields[i] }) + + return fields, wildcard, inertFields } // isNotUserSettable reports whether the bundle marks a field as something the user @@ -422,6 +540,12 @@ func expandPattern(base dyn.Value, pattern string) []string { func splitPattern(pattern string) []string { var segs []string for part := range strings.SplitSeq(pattern, ".") { + // A dot-separated part is either the map wildcard on its own, or a field name + // followed by any number of [*] element wildcards. + if part == "*" { + segs = append(segs, "*") + continue + } for { name, rest, found := strings.Cut(part, "[*]") if name != "" { @@ -433,9 +557,6 @@ func splitPattern(pattern string) []string { segs = append(segs, "[*]") part = rest } - if part == "*" { - segs = append(segs, "*") - } } return segs } diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go index 0b16220a426..2335b398516 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/tests/values_unit_test.go @@ -52,3 +52,36 @@ func TestTransitionsSkipAbsentForRequiredField(t *testing.T) { assert.NotEqual(t, "absent", valueLabel(tr.to)) } } + +// A pattern from the type walk has to expand against the deployed config, or the fields +// inside a container are silently never tested. Map wildcards ("properties.*") and element +// wildcards ("tasks[*]") take different paths through splitPattern, so both are pinned here. +func TestExpandPattern(t *testing.T) { + base := dyn.V(map[string]dyn.Value{ + "properties": dyn.V(map[string]dyn.Value{"team": dyn.V("eng")}), + "tasks": dyn.V([]dyn.Value{ + dyn.V(map[string]dyn.Value{"key": dyn.V("a")}), + dyn.V(map[string]dyn.Value{"key": dyn.V("b")}), + }), + "tags": dyn.V(map[string]dyn.Value{ + "custom": dyn.V([]dyn.Value{dyn.V(map[string]dyn.Value{"key": dyn.V("x")})}), + }), + }) + + for _, tc := range []struct { + pattern string + want []string + }{ + {"name", []string{"name"}}, + {"properties.*", []string{"properties['team']"}}, + {"tasks[*].key", []string{"tasks[0].key", "tasks[1].key"}}, + {"tags.custom[*].key", []string{"tags.custom[0].key"}}, + // Nothing behind it in the config, which is what makes a field "not covered". + {"absent[*].key", nil}, + {"absent.*", nil}, + } { + t.Run(tc.pattern, func(t *testing.T) { + assert.Equal(t, tc.want, expandPattern(base, tc.pattern)) + }) + } +} diff --git a/libs/testserver/apps.go b/libs/testserver/apps.go index c70115d060f..45dcb7d18f6 100644 --- a/libs/testserver/apps.go +++ b/libs/testserver/apps.go @@ -256,6 +256,15 @@ func (s *FakeWorkspace) AppsDelete(name string) Response { return Response{StatusCode: 404} } + if s.SettleAsyncImmediately { + // The real API leaves the app in DELETING for up to ~20 minutes and keeps the name + // reserved, so re-creating it blocks. Delete outright instead, which is what a suite + // doing thousands of updates needs; the DELETING behaviour is covered by the + // acceptance suite, which leaves the simulation on. + delete(s.Apps, name) + return Response{Body: map[string]string{}} + } + if app.ComputeStatus != nil && app.ComputeStatus.State == apps.ComputeStateDeleting { return Response{ StatusCode: http.StatusBadRequest, diff --git a/libs/testserver/fake_workspace.go b/libs/testserver/fake_workspace.go index 2f077b1b631..a9d75257d30 100644 --- a/libs/testserver/fake_workspace.go +++ b/libs/testserver/fake_workspace.go @@ -175,6 +175,11 @@ type FakeWorkspace struct { url string isServicePrincipal bool + // SettleAsyncImmediately turns off the "not ready yet on the first read" simulation + // for asynchronous resources. Off by default, so the CLI's waiters stay exercised. + // See SetSettleAsyncImmediately for when turning it on is worth it. + SettleAsyncImmediately bool + directories map[string]workspace.ObjectInfo files map[string]FileEntry repoIdByPath map[string]int64 @@ -402,6 +407,20 @@ func MapDelete[K comparable, V any](w *FakeWorkspace, collection map[K]V, key K) return Response{} } +// SetSettleAsyncImmediately makes asynchronous resources report themselves ready on the +// first read after a write, instead of reporting in-progress once so the CLI's waiter is +// exercised. +// +// Only turn it on for a suite that performs thousands of updates. The cost is not the +// extra request, it is the sleep before it: the SDK's poller uses a hardcoded backoff of +// attempt*1s plus 50-750ms of jitter (retries.backoff in databricks-sdk-go/retries/ +// retries.go), and retries.Poll does not pass a backoff option -- there is no exported way +// to shorten it. So every update costs at least a second of wall time. +func (s *FakeWorkspace) SetSettleAsyncImmediately(v bool) { + defer s.LockUnlock()() + s.SettleAsyncImmediately = v +} + func NewFakeWorkspace(url, token string) *FakeWorkspace { return &FakeWorkspace{ url: url, diff --git a/libs/testserver/server.go b/libs/testserver/server.go index 7f949ebf5cd..721c563fdc4 100644 --- a/libs/testserver/server.go +++ b/libs/testserver/server.go @@ -73,6 +73,21 @@ type Server struct { RequestCallback func(request *Request) ResponseCallback func(request *Request, response *EncodedResponse) + + // settleAsyncImmediately is applied to every workspace this server creates; see + // FakeWorkspace.SetSettleAsyncImmediately. + settleAsyncImmediately bool +} + +// SettleAsyncImmediately turns off the "not ready yet on the first read" simulation for +// every workspace this server serves. Call it before the first request. +func (s *Server) SettleAsyncImmediately() { + s.mu.Lock() + defer s.mu.Unlock() + s.settleAsyncImmediately = true + for _, workspace := range s.fakeWorkspaces { + workspace.SetSettleAsyncImmediately(true) + } } type Request struct { @@ -349,7 +364,9 @@ func (s *Server) getWorkspaceForToken(token string) *FakeWorkspace { defer s.mu.Unlock() if _, ok := s.fakeWorkspaces[key]; !ok { - s.fakeWorkspaces[key] = NewFakeWorkspace(s.URL, token) + workspace := NewFakeWorkspace(s.URL, token) + workspace.SettleAsyncImmediately = s.settleAsyncImmediately + s.fakeWorkspaces[key] = workspace } return s.fakeWorkspaces[key] diff --git a/libs/testserver/serving_endpoints.go b/libs/testserver/serving_endpoints.go index efa10d47463..72c2dfce313 100644 --- a/libs/testserver/serving_endpoints.go +++ b/libs/testserver/serving_endpoints.go @@ -299,13 +299,19 @@ func (s *FakeWorkspace) ServingEndpointGet(name string) Response { } if endpointUpdating(endpoint) { - // This response stays IN_PROGRESS; settle the stored copy for the next read. settled := endpoint settled.State = &serving.EndpointState{ ConfigUpdate: serving.EndpointStateConfigUpdateNotUpdating, Ready: endpoint.State.Ready, } s.ServingEndpoints[name] = settled + + // By default this response stays IN_PROGRESS and only the stored copy settles, so + // the caller has to poll at least once. SettleAsyncImmediately reports the settled + // state right away instead. + if s.SettleAsyncImmediately { + endpoint = settled + } } return Response{Body: endpoint} From ac5452c968bfafd4ecbd6fb153afe108b156f80b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 19:18:53 +0200 Subject: [PATCH 014/118] Field catalog: give schemas and volumes values a real workspace accepts The generic "x"/"y" defaults are rejected outright on a real workspace for a field that names another object, and the retention period is validated in days, so those fields reported nothing but BACKEND_ERROR on cloud. Co-authored-by: Isaac --- bundle/direct/tests/output/schemas.txt | 4 ++-- bundle/direct/tests/testdata/fields/schemas.yml | 8 ++++++++ bundle/direct/tests/testdata/fields/volumes.yml | 10 +++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index 3653dfd024a..721441ebfcc 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -1,6 +1,6 @@ === schema.yml.tmpl -custom_max_retention_hours 1 absent UPDATE_IGNORED custom_max_retention_hours -custom_max_retention_hours 2 absent UPDATE_IGNORED custom_max_retention_hours +custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours +custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours properties absent keys0 UPDATE_IGNORED properties properties keys0 absent UPDATE_IGNORED properties properties keys1 absent UPDATE_IGNORED properties diff --git a/bundle/direct/tests/testdata/fields/schemas.yml b/bundle/direct/tests/testdata/fields/schemas.yml index 8ac0ca724fe..54669f64e85 100644 --- a/bundle/direct/tests/testdata/fields/schemas.yml +++ b/bundle/direct/tests/testdata/fields/schemas.yml @@ -8,3 +8,11 @@ base: skip: # storage_root must point at a real external location. storage_root: needs an external location + +fields: + # See the note in volumes.yml: a reference has to name something that exists. + catalog_name: [main] + + # The backend takes hours but validates them as days: the retention period must be 0 or + # between 7 and 30 days, so the generic 1 and 2 are rejected. + custom_max_retention_hours: [168, 720] diff --git a/bundle/direct/tests/testdata/fields/volumes.yml b/bundle/direct/tests/testdata/fields/volumes.yml index 2fd5d790e7d..3708f98a02c 100644 --- a/bundle/direct/tests/testdata/fields/volumes.yml +++ b/bundle/direct/tests/testdata/fields/volumes.yml @@ -1,4 +1,4 @@ -# Value library for volumes. See ../README.md. +# Value library for volumes. See ../../README.md. skip: # An external volume needs a storage location backed by a real external location and # cloud IAM, which this suite does not provision. @@ -8,3 +8,11 @@ fields: # EXTERNAL is only valid together with storage_location, so only MANAGED is # deployable on its own. volume_type: [MANAGED] + + # A name that refers to another object has to name one that exists: a real workspace + # rejects the generic "x" outright, so the field would report nothing but BACKEND_ERROR. + # main.default exists in every UC workspace the suite runs against. One value, not two: + # a second catalog and schema would have to be provisioned, and the pairs with absent + # already cover what a single value can show. + catalog_name: [main] + schema_name: [default] From 8c4f01475472fa3f32027f89f8b8a61d222bacc2 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 19:42:49 +0200 Subject: [PATCH 015/118] structaccess: reach fields of a doubly-embedded struct FindStructFieldByKeyType already recursed into embedded structs, but Get and Set stopped after one level, so a field of resources.PostgresProject -- which embeds a config struct that embeds the SDK spec -- was reported as not found. Both now walk embedding recursively and track the struct that declares the field, which is also the one whose ForceSendFields governs it: an outer struct that shadows the name (PostgresProjectConfig) tracks only its own fields. Co-authored-by: Isaac --- libs/structs/structaccess/bundle_test.go | 34 ++++++++++ libs/structs/structaccess/get.go | 84 ++++++++---------------- libs/structs/structaccess/set.go | 64 ++---------------- 3 files changed, 68 insertions(+), 114 deletions(-) diff --git a/libs/structs/structaccess/bundle_test.go b/libs/structs/structaccess/bundle_test.go index 1d75afe0b10..09c39b57122 100644 --- a/libs/structs/structaccess/bundle_test.go +++ b/libs/structs/structaccess/bundle_test.go @@ -76,3 +76,37 @@ func TestGet_ConfigRoot_JobTagsAccess(t *testing.T) { require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.apps.my_app.url.inner")) require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.apps.my_app.url1")) } + +// A bundle resource embeds a config struct that embeds the SDK request struct, so its +// fields sit two levels down. Get, Set and ValidatePath all have to reach them, and +// ForceSendFields belongs to the struct that declares the field -- not to the outer one +// that shadows the name. +func TestGetSet_DoublyEmbeddedField(t *testing.T) { + project := &resources.PostgresProject{} //exhaustruct:ignore + project.ProjectId = "p" + + require.NoError(t, ValidateByString(reflect.TypeOf(project), "budget_policy_id")) + + require.NoError(t, SetByString(project, "budget_policy_id", "abc")) + require.Equal(t, "abc", project.BudgetPolicyId) + + value, err := GetByString(project, "budget_policy_id") + require.NoError(t, err) + require.Equal(t, "abc", value) + + // An explicit empty value is recorded on ProjectSpec, which declares the field. + require.NoError(t, SetByString(project, "budget_policy_id", "")) + require.Contains(t, project.ProjectSpec.ForceSendFields, "BudgetPolicyId") + require.NotContains(t, project.PostgresProjectConfig.ForceSendFields, "BudgetPolicyId") + + value, err = GetByString(project, "budget_policy_id") + require.NoError(t, err) + require.Equal(t, "", value) + + // And dropping it again leaves the field absent. + require.NoError(t, SetByString(project, "budget_policy_id", nil)) + require.NotContains(t, project.ProjectSpec.ForceSendFields, "BudgetPolicyId") + value, err = GetByString(project, "budget_policy_id") + require.NoError(t, err) + require.Nil(t, value) +} diff --git a/libs/structs/structaccess/get.go b/libs/structs/structaccess/get.go index bf8c96df80d..834d3f3f532 100644 --- a/libs/structs/structaccess/get.go +++ b/libs/structs/structaccess/get.go @@ -138,19 +138,13 @@ func Get(v any, path *structpath.PathNode) (any, error) { func accessKey(v reflect.Value, key string, path *structpath.PathNode) (reflect.Value, error) { switch v.Kind() { case reflect.Struct: - // Precalculate ForceSendFields mappings for this struct hierarchy - forceSendFieldsMap := getForceSendFieldsForFromTyped(v) - - fv, sf, embeddedIndex, ok := findStructFieldByKey(v, key) + fv, sf, owner, ok := findStructFieldByKey(v, key) if !ok { return reflect.Value{}, fmt.Errorf("%s: field %q not found in %s", path.String(), key, v.Type()) } - // Check ForceSendFields using precalculated map - var force bool - if fields, exists := forceSendFieldsMap[embeddedIndex]; exists { - force = containsString(fields, sf.Name) - } + // ForceSendFields is only managed by the struct that declares the field. + force := forceSendFieldsContains(owner, sf.Name) // Honor omitempty: if present and value is empty and not forced, treat as omitted (nil). jsonTag := structtag.JSONTag(sf.Tag.Get("json")) @@ -270,15 +264,16 @@ func findFieldInStruct(v reflect.Value, key string) (reflect.Value, reflect.Stru // findStructFieldByKey searches exported fields of struct v for a field matching key. // It matches json tag name (when present and not "-") only. -// It also searches embedded anonymous structs (flattening semantics). -// Returns: fieldValue, structField, embeddedIndex, found -// embeddedIndex is -1 for direct fields, or the index of the embedded struct containing the field. -func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.StructField, int, bool) { +// It also searches embedded anonymous structs recursively (flattening semantics), which +// FindStructFieldByKeyType does too: a bundle resource embeds a config struct that embeds +// the SDK request struct, so its fields sit two levels down. +// Returns: fieldValue, structField, owner (the struct value declaring the field), found +func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.StructField, reflect.Value, bool) { t := v.Type() // First pass: direct fields if fv, sf, found := findFieldInStruct(v, key); found { - return fv, sf, -1, true + return fv, sf, v, true } // Second pass: search embedded anonymous structs (flattening semantics) @@ -299,59 +294,38 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S if fv.Kind() != reflect.Struct { continue } - if out, osf, found := findFieldInStruct(fv, key); found { - return out, osf, i, true + if out, osf, owner, found := findStructFieldByKey(fv, key); found { + return out, osf, owner, true } } - return reflect.Value{}, reflect.StructField{}, -1, false + return reflect.Value{}, reflect.StructField{}, reflect.Value{}, false } -// getForceSendFieldsForFromTyped collects ForceSendFields values for FromTyped operations -// Returns map[structKey][]fieldName where structKey is -1 for direct fields, embedded index for embedded fields -func getForceSendFieldsForFromTyped(v reflect.Value) map[int][]string { - if !v.IsValid() || v.Type().Kind() != reflect.Struct { - return make(map[int][]string) +// forceSendFields returns the ForceSendFields slice a struct declares itself. A struct that +// embeds another shadows it deliberately -- see resources.PostgresProjectConfig -- so only +// the declaring struct tracks a field of its own. +func forceSendFields(owner reflect.Value) reflect.Value { + if !owner.IsValid() || owner.Kind() != reflect.Struct { + return reflect.Value{} } - - result := make(map[int][]string) - - for i := range v.Type().NumField() { - field := v.Type().Field(i) - fieldValue := v.Field(i) - + for i := range owner.Type().NumField() { + field := owner.Type().Field(i) if field.Name == "ForceSendFields" && !field.Anonymous { - // Direct ForceSendFields (structKey = -1) - if fields, ok := fieldValue.Interface().([]string); ok { - result[-1] = fields - } - } else if field.Anonymous { - // Embedded struct - check for ForceSendFields inside it - if embeddedStruct := getEmbeddedStructForReading(fieldValue); embeddedStruct.IsValid() { - if forceSendField := embeddedStruct.FieldByName("ForceSendFields"); forceSendField.IsValid() { - if fields, ok := forceSendField.Interface().([]string); ok { - result[i] = fields - } - } - } + return owner.Field(i) } } - - return result + return reflect.Value{} } -// Helper function for reading - doesn't create nil pointers -func getEmbeddedStructForReading(fieldValue reflect.Value) reflect.Value { - if fieldValue.Kind() == reflect.Pointer { - if fieldValue.IsNil() { - return reflect.Value{} // Don't create, just return invalid - } - fieldValue = fieldValue.Elem() - } - if fieldValue.Kind() == reflect.Struct { - return fieldValue +// forceSendFieldsContains reports whether a struct forces the named field to be sent. +func forceSendFieldsContains(owner reflect.Value, name string) bool { + fsf := forceSendFields(owner) + if !fsf.IsValid() { + return false } - return reflect.Value{} + fields, ok := fsf.Interface().([]string) + return ok && containsString(fields, name) } // containsString checks if a slice contains a specific string diff --git a/libs/structs/structaccess/set.go b/libs/structs/structaccess/set.go index 2285d470fa9..1f72cf96cc9 100644 --- a/libs/structs/structaccess/set.go +++ b/libs/structs/structaccess/set.go @@ -130,7 +130,7 @@ func setFieldOrMapValue(parentVal reflect.Value, key string, valueVal reflect.Va // setStructField sets a field in a struct and handles ForceSendFields func setStructField(parentVal reflect.Value, fieldName string, valueVal reflect.Value) error { - fv, sf, embeddedIndex, ok := findStructFieldByKey(parentVal, fieldName) + fv, sf, owner, ok := findStructFieldByKey(parentVal, fieldName) if !ok { return fmt.Errorf("field %q not found in %s", fieldName, parentVal.Type()) } @@ -141,7 +141,7 @@ func setStructField(parentVal reflect.Value, fieldName string, valueVal reflect. } // Handle ForceSendFields: remove if setting nil, add if setting empty value - err := updateForceSendFields(parentVal, sf.Name, embeddedIndex, valueVal, sf) + err := updateForceSendFields(owner, sf.Name, valueVal, sf) if err != nil { return err } @@ -303,7 +303,7 @@ func convertValue(valueVal reflect.Value, targetType reflect.Type) (reflect.Valu // - If setting nil: remove field from ForceSendFields // - If setting empty value: add field to ForceSendFields (if not already present) // Only applies to fields with omitempty tag -func updateForceSendFields(parentVal reflect.Value, fieldName string, embeddedIndex int, valueVal reflect.Value, structField reflect.StructField) error { +func updateForceSendFields(owner reflect.Value, fieldName string, valueVal reflect.Value, structField reflect.StructField) error { isSettingNil := !valueVal.IsValid() isSettingEmptyValue := valueVal.IsValid() && isEmptyForOmitEmpty(valueVal) @@ -319,8 +319,8 @@ func updateForceSendFields(parentVal reflect.Value, fieldName string, embeddedIn return nil } - // Find the appropriate ForceSendFields slice to modify - forceSendFieldsSlice := findForceSendFieldsForSetting(parentVal, embeddedIndex) + // Only the struct that declares the field tracks it. + forceSendFieldsSlice := forceSendFields(owner) if !forceSendFieldsSlice.IsValid() { // No ForceSendFields to update return nil @@ -337,60 +337,6 @@ func updateForceSendFields(parentVal reflect.Value, fieldName string, embeddedIn return nil } -// findForceSendFieldsForSetting finds the correct ForceSendFields slice to modify -// This should match the logic in get.go's getForceSendFieldsForFromTyped -// Only the struct that contains the ForceSendFields can manage its own fields -// embeddedIndex: -1 for direct fields, or the index of the embedded struct -func findForceSendFieldsForSetting(parentVal reflect.Value, embeddedIndex int) reflect.Value { - if embeddedIndex == -1 { - // Direct field - check if parent struct has its own ForceSendFields - // We need to check the struct type directly, not through field promotion - parentType := parentVal.Type() - for i := range parentType.NumField() { - field := parentType.Field(i) - if field.Name == "ForceSendFields" && !field.Anonymous { - // Parent has direct ForceSendFields - return parentVal.Field(i) - } - } - // Parent struct has no direct ForceSendFields, so no management possible - return reflect.Value{} - } else { - // Embedded field - look for ForceSendFields in the embedded struct - embeddedField := parentVal.Field(embeddedIndex) - embeddedStruct := getEmbeddedStructForSetting(embeddedField) - if !embeddedStruct.IsValid() { - return reflect.Value{} - } - fsf := embeddedStruct.FieldByName("ForceSendFields") - if fsf.IsValid() { - return fsf - } - // Embedded struct has no ForceSendFields, so no management possible - return reflect.Value{} - } -} - -// getEmbeddedStructForSetting gets the embedded struct for setting operations -// Creates nil pointers if needed -func getEmbeddedStructForSetting(fieldValue reflect.Value) reflect.Value { - if fieldValue.Kind() == reflect.Pointer { - if fieldValue.IsNil() { - // Create new instance if needed - if fieldValue.CanSet() { - fieldValue.Set(reflect.New(fieldValue.Type().Elem())) - } else { - return reflect.Value{} - } - } - fieldValue = fieldValue.Elem() - } - if fieldValue.Kind() == reflect.Struct { - return fieldValue - } - return reflect.Value{} -} - // removeFromForceSendFields removes fieldName from the ForceSendFields slice func removeFromForceSendFields(forceSendFieldsSlice reflect.Value, fieldName string) { // Get the original []string slice From 2c59963643ec2737b10209e065709750d6cef48d Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 19:57:25 +0200 Subject: [PATCH 016/118] Field catalog: edit the typed resource instead of the dynamic tree The suite now writes values through libs/structs (structaccess over the resource's own Go type, structpath for paths) and syncs the result into the dynamic tree the way a mutator does, instead of manipulating dyn.Value directly. Nothing in the package depends on libs/dyn any more. That also removes every hand-written substitute for something dyn lacked: a path parser that could hold a map key, a delete-by-path, a parent-creating walk. And it makes "absent" the distinction the API actually sees -- the zero value with the field out of ForceSendFields -- so a list element or map entry now becomes settable where it previously reported UNSETTABLE: 91 of those are gone and the suite records 252 more observations, in a third of the wall time. The order seed is a constant now rather than HEAD. Some verdicts depend on the order -- a field the API cannot clear leaves the remote holding an old value -- and committing a report changes HEAD, so a HEAD-derived seed could never validate at the commit that carried the report. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 17 +- bundle/direct/tests/corpus_test.go | 78 +-- bundle/direct/tests/fields_test.go | 72 ++- bundle/direct/tests/harness_test.go | 493 +++++++++++------- bundle/direct/tests/output/apps.txt | 26 +- .../direct/tests/output/cluster_policies.txt | 7 - bundle/direct/tests/output/clusters.txt | 15 - .../tests/output/database_instances.txt | 2 - bundle/direct/tests/output/instance_pools.txt | 9 - bundle/direct/tests/output/jobs.txt | 13 - .../tests/output/model_serving_endpoints.txt | 10 +- bundle/direct/tests/output/pipelines.txt | 30 -- .../direct/tests/output/postgres_projects.txt | 20 +- .../tests/output/postgres_synced_tables.txt | 6 - bundle/direct/tests/values_test.go | 209 ++++---- bundle/direct/tests/values_unit_test.go | 95 +++- libs/structs/structaccess/bundle_test.go | 5 +- 17 files changed, 599 insertions(+), 508 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index ef19994dc81..4a1f248b079 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -35,6 +35,12 @@ Everything runs in-process against the direct engine's own `CalculatePlan` and `Apply`. There is no CLI subprocess and no bundle-file upload: at thousands of permutations, a `bundle deploy` each would be dominated by sync. +Edits are made to the **typed** resource — `libs/structs/structaccess` over +`*resources.Schema` and friends — and synced into the dynamic tree the planner reads the +way any mutator does. That is also what makes "absent" precise: a field is absent when it +holds the zero value and is not in `ForceSendFields`, which is exactly the distinction the +API sees. + ## Verdicts | verdict | meaning | @@ -70,7 +76,9 @@ The `SUPPRESSED` reason string is the engine explaining itself, which is differe ## Value library `testdata/fields/.yml` supplies values the generic per-kind defaults cannot -guess — enums, ids, anything the backend constrains: +guess — enums, ids, anything the backend constrains. A field naming another object needs a +name that exists: a real workspace rejects the generic `x` outright, so the field would +report nothing but `BACKEND_ERROR`. ```yaml # Seeded into the resource before the first deploy, so this block's fields become @@ -115,9 +123,10 @@ problem verdict: go test ./bundle/direct/tests -run TestFields/pipelines/.*/dry_run/absent_to_true -v ``` -On cloud (`CLOUD_ENV` set) only resource types with `cloud: true` run, and the results -go to `output/..txt`, which is not committed: which values a -real backend accepts depends on the workspace. +On cloud (`CLOUD_ENV` set) the same reports are compared against the same committed +goldens: a divergence means the fake server in `libs/testserver` does not model the API +faithfully, which is worth failing over. The full report goes to +`output/..full.txt` so a cloud run can be read next to a local one. ## Out of scope for now diff --git a/bundle/direct/tests/corpus_test.go b/bundle/direct/tests/corpus_test.go index 2279536d9f7..3913ecc08cc 100644 --- a/bundle/direct/tests/corpus_test.go +++ b/bundle/direct/tests/corpus_test.go @@ -3,6 +3,7 @@ package tests import ( "encoding/json" "fmt" + "maps" "os" "path/filepath" "slices" @@ -12,9 +13,8 @@ import ( bundleconfig "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/direct/dresources" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlloader" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" ) // The suite reuses the acceptance invariant corpus rather than growing a second set @@ -97,15 +97,12 @@ func resourceFingerprint(path string) (string, error) { if err != nil { return "", err } - stripped, err := withoutSubResources(root) - if err != nil { - return "", err - } - resources, err := dyn.Get(stripped, "resources") - if err != nil { - return "", err + for _, resource := range declaredResources(root) { + for _, kind := range subResourceKinds { + delete(resource, kind) + } } - encoded, err := json.Marshal(resources.AsAny()) + encoded, err := json.Marshal(root["resources"]) if err != nil { return "", err } @@ -114,37 +111,58 @@ func resourceFingerprint(path string) (string, error) { // loadTemplate parses a config template with its variables blanked out; only the shape // matters to the callers here. -func loadTemplate(path string) (dyn.Value, error) { +func loadTemplate(path string) (map[string]any, error) { data, err := os.ReadFile(path) if err != nil { - return dyn.InvalidValue, err + return nil, err } yml := os.Expand(string(data), func(string) string { return "x" }) - return yamlloader.LoadYAML(path, strings.NewReader(yml)) + var root map[string]any + if err := yaml.Unmarshal([]byte(yml), &root); err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + return root, nil } -// resourceNodes returns the "resources.." keys declared by a config -// template, ignoring the permissions and grants sub-blocks. +// resourceNodes returns the "resources.." keys declared by a config template. func resourceNodes(path string) ([]string, error) { root, err := loadTemplate(path) if err != nil { return nil, err } - var nodes []string - _, err = dyn.MapByPattern(root, - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - nodes = append(nodes, p.String()) - return v, nil - }) - if err != nil { - return nil, err + for _, group := range sortedKeys(mapAt(root, "resources")) { + for _, name := range sortedKeys(mapAt(mapAt(root, "resources"), group)) { + nodes = append(nodes, "resources."+group+"."+name) + } } - slices.Sort(nodes) return nodes, nil } +// declaredResources returns every resource body of a parsed template. +func declaredResources(root map[string]any) []map[string]any { + var out []map[string]any + resources := mapAt(root, "resources") + for _, group := range sortedKeys(resources) { + byName := mapAt(resources, group) + for _, name := range sortedKeys(byName) { + if body, ok := byName[name].(map[string]any); ok { + out = append(out, body) + } + } + } + return out +} + +func mapAt(parent map[string]any, key string) map[string]any { + value, _ := parent[key].(map[string]any) + return value +} + +func sortedKeys(m map[string]any) []string { + return slices.Sorted(maps.Keys(m)) +} + // soleResourceNode returns the single resource node of an initialized config. func soleResourceNode(root *bundleconfig.Root) (string, error) { nodes, err := initializedNodes(root) @@ -159,12 +177,10 @@ func soleResourceNode(root *bundleconfig.Root) (string, error) { func initializedNodes(root *bundleconfig.Root) ([]string, error) { var nodes []string - _, err := dyn.MapByPattern(root.Value(), - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - nodes = append(nodes, p.String()) - return v, nil - }) + err := forEachResource(root, func(node string, _ any) error { + nodes = append(nodes, node) + return nil + }) slices.Sort(nodes) return nodes, err } diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 61829208224..2dbff41368b 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -35,9 +35,8 @@ import ( "encoding/json" "errors" "fmt" - "hash/fnv" "maps" - "os/exec" + "reflect" "regexp" "slices" "strconv" @@ -47,7 +46,6 @@ import ( "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/stretchr/testify/require" @@ -57,10 +55,9 @@ func TestFields(t *testing.T) { usable, skipped := discoverConfigs(t) // The order fields are tested in, and the order each field's values are visited in, - // come from this seed. It is the commit rather than the clock so that a finding cannot - // be retried away: one commit always produces one order, and a new commit explores a - // different one. - runSeed := commitSeed(t) + // come from this seed. + runSeed := orderSeed + t.Logf("field and value order seeded from orderSeed %d", runSeed) // One config per resource type: the simplest one, which is the shortest name -- a // ".yml.tmpl" sorts before its "_.yml.tmpl" siblings. The variants @@ -118,10 +115,12 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl return } - // Enumerated from the deployed config, not just the type: which slice and map entries + // Enumerated from the deployed resource, not just its type: which slice and map entries // exist decides which fields inside them can be reached at all. - base := h.fieldSnapshot() - fields, wildcard, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, base, runSeed, declaredUnsettable(adapter)) + base := h.snapshot() + resource, err := h.resource() + require.NoError(t, err) + fields, wildcard, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, resource, runSeed, declaredUnsettable(adapter)) rep.addCoverage(fields, wildcard) // Fields the resource declares a user cannot meaningfully set. Recorded with the @@ -153,7 +152,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // What the field is currently deployed as, when that is known. Transitions are // generated in runs that share a starting value, so this skips about half of // the setup deploys -- the single biggest cost in the suite. - deployed, deployedKnown := dyn.InvalidValue, false + deployed, deployedKnown := absent, false for _, tr := range f.transitions() { t.Run(tr.label(), func(t *testing.T) { @@ -176,7 +175,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl t.Skipf("could not rebuild baseline: %s", err) } h = rebuilt - base = h.fieldSnapshot() + base = h.snapshot() }) } }) @@ -184,7 +183,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // clear leaves it drifted for good, which would otherwise be blamed on every // field tested afterwards -- so start over on a fresh resource. A new name is // what makes that cheap: reusing the old one waits out an asynchronous delete. - h.restoreField(base, f.path) + h.restore(base) if !h.converged() { rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h) if err != nil { @@ -192,7 +191,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl return } h = rebuilt - base = h.fieldSnapshot() + base = h.snapshot() } } } @@ -357,17 +356,18 @@ func explainSkip(plan *deployplan.Plan, node, path string) (verdict, string) { // valueLabel renders a value for the subtest name and the report. A slice or map is // labelled by size, since the point of testing one is how many entries it has. -func valueLabel(v dyn.Value) string { - if !v.IsValid() { +func valueLabel(v any) string { + if v == nil { return "absent" } - if items, ok := v.AsSequence(); ok { - return "len" + strconv.Itoa(len(items)) - } - if m, ok := v.AsMap(); ok { - return "keys" + strconv.Itoa(m.Len()) + switch value := reflect.ValueOf(v); value.Kind() { + case reflect.Slice, reflect.Array: + return "len" + strconv.Itoa(value.Len()) + case reflect.Map: + return "keys" + strconv.Itoa(value.Len()) + default: } - switch s := fmt.Sprintf("%v", v.AsAny()); s { + switch s := fmt.Sprintf("%v", v); s { case "": return "empty" default: @@ -561,26 +561,16 @@ func simplerConfig(a, b string) bool { return a < b } -// commitSeed derives the run seed from HEAD, ignoring the working tree: a dirty checkout -// still explores the order of the commit it is based on. Falls back to a fixed value where -// there is no git at all, so the suite still runs. +// orderSeed fixes the order fields are tested in and the order each field's values are +// visited in. Bump it to explore a different order, and regenerate the reports. // -// Tying the seed to the commit rather than the clock is deliberate: a finding cannot be -// made to disappear by running the test again. -func commitSeed(t *testing.T) uint64 { - out, err := exec.Command("git", "rev-parse", "HEAD").Output() - if err != nil { - t.Logf("no git HEAD (%s); falling back to a fixed order seed", err) - return 0 - } - - commit := strings.TrimSpace(string(out)) - h := fnv.New64a() - _, _ = h.Write([]byte(commit)) - seed := h.Sum64() - t.Logf("field and value order seeded from HEAD %s (seed %d)", commit, seed) - return seed -} +// It is a constant rather than something derived -- HEAD was the first attempt -- because +// the reports are committed and some verdicts depend on the order. A field the API cannot +// clear leaves the remote holding an old value, and whether the next transition then reads +// that as an ignored write or as drift depends on what ran before it. A seed taken from the +// tree or the clock moves under the golden: committing the report changes HEAD, so the +// report would never validate at the commit that contains it. +const orderSeed uint64 = 1 // withContext labels an evidence block, so a reader of the full report knows what they are // looking at. The label is its own line, leaving the JSON below it copy-pasteable. diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index e11ff63cd63..911a8eda642 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -2,13 +2,13 @@ package tests import ( "context" + "encoding/json" "fmt" "io" "math" "os" "path/filepath" - "slices" - "strconv" + "reflect" "strings" "testing" "time" @@ -24,10 +24,10 @@ import ( "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/dbr" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/merge" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structaccess" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/testserver" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/iam" @@ -154,7 +154,7 @@ func maxWaitSeconds() string { // built is usually the environment (an expired token, a workspace that rejects the // config), and the run is more useful if that lands in the report as one bad verdict // than if it aborts thousands of pending observations. -func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string, base dyn.Value) (*bundleHarness, error) { +func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string, base any) (*bundleHarness, error) { dir := t.TempDir() if err := copyDir(dataDir, dir); err != nil { return nil, err @@ -236,7 +236,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC // which would be reported against whichever field happened to trigger the recreate, // once per field. That bug has its own coverage in // acceptance/bundle/resources/volumes/recreate. - if err := stripSubResources(&b.Config); err != nil { + if err := stripSubResources(&b.Config, ctx); err != nil { return nil, err } @@ -245,20 +245,22 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC return nil, err } - if base.IsValid() { - if err := mergeBase(&b.Config, node, base); err != nil { - return nil, err - } - } - - return &bundleHarness{ + harness := &bundleHarness{ t: t, ctx: ctx, client: client, bundle: b, node: node, statePath: filepath.Join(dir, "resources.json"), - }, nil + } + + if base != nil { + if err := harness.edit(func(resource any) error { return seedBase(resource, base) }); err != nil { + return nil, err + } + } + + return harness, nil } // cachedUser resolves the workspace user once per process: a harness is built per @@ -379,10 +381,26 @@ func hasDrift(plan *deployplan.Plan) bool { return false } -// mutate edits the bundle's dynamic config, which is what GetResourceConfig reads. -// Editing the typed structs would not be seen by the planner. -func (h *bundleHarness) mutate(fn func(dyn.Value) (dyn.Value, error)) error { - return h.bundle.Config.Mutate(fn) +// edit runs fn over the typed resource under test and syncs the result back into the +// dynamic tree, which is what the planner reads (config.Root.GetResourceConfig). Enter +// and exit around a typed edit is the same contract every bundle mutator follows. +func (h *bundleHarness) edit(fn func(resource any) error) error { + if err := h.bundle.Config.MarkMutatorEntry(h.ctx); err != nil { + return err + } + resource, err := structaccess.GetByString(&h.bundle.Config, h.node) + if err == nil { + err = fn(resource) + } + if exitErr := h.bundle.Config.MarkMutatorExit(h.ctx); exitErr != nil { + return exitErr + } + return err +} + +// resource returns the typed resource under test, e.g. *resources.Schema. +func (h *bundleHarness) resource() (any, error) { + return structaccess.GetByString(&h.bundle.Config, h.node) } func copyDir(src, dst string) error { @@ -407,228 +425,323 @@ func copyDir(src, dst string) error { } // setField writes a value into the resource under test, or removes the field when the -// value is absent. Edits go through the dynamic config because that is what the -// planner reads (config.Root.GetResourceConfig). -func (h *bundleHarness) setField(path string, value dyn.Value) error { - rel, err := parsePath(path) +// value is nil -- which is how "absent" is expressed, since a Go struct has no absent. +func (h *bundleHarness) setField(path string, value any) error { + node, err := structpath.ParsePath(path) if err != nil { return err } - node, err := dyn.NewPathFromString(h.node) - if err != nil { - return err - } - full := append(node, rel...) - return h.mutate(func(root dyn.Value) (dyn.Value, error) { - if !value.IsValid() { - return deletePath(root, full) - } - if err := ensureParents(&root, full); err != nil { - return dyn.InvalidValue, err - } - return dyn.SetByPath(root, full, value) + return h.edit(func(resource any) error { + return setNode(resource, node, value) }) } -// parsePath converts a field path into a dyn.Path. Written by hand rather than handed to -// dyn.NewPathFromString because that parser treats everything in brackets as an integer -// index, so a map key -- which the field paths here render as ['key'], the form structpath -// prints and the form a key containing a dot requires -- is rejected outright. -func parsePath(path string) (dyn.Path, error) { - var out dyn.Path - for len(path) > 0 { - path = strings.TrimPrefix(path, ".") - - switch { - case strings.HasPrefix(path, "['"): - end := strings.Index(path, "']") - if end < 0 { - return nil, fmt.Errorf("unterminated quoted key in %q", path) - } - // '' is how a literal quote is escaped, matching structpath. - out = append(out, dyn.Key(strings.ReplaceAll(path[2:end], "''", "'"))) - path = path[end+2:] - - case strings.HasPrefix(path, "["): - end := strings.Index(path, "]") - if end < 0 { - return nil, fmt.Errorf("unterminated index in %q", path) - } - index, err := strconv.Atoi(path[1:end]) - if err != nil { - return nil, fmt.Errorf("bad index in %q: %w", path, err) - } - out = append(out, dyn.Index(index)) - path = path[end+1:] - - default: - end := strings.IndexAny(path, ".[") - if end < 0 { - out = append(out, dyn.Key(path)) - return out, nil - } - out = append(out, dyn.Key(path[:end])) - path = path[end:] - } +// setNode is setField without the bundle: the edit itself, for the unit tests. +func setNode(resource any, node *structpath.PathNode, value any) error { + if value == nil { + return removeField(resource, node) } - return out, nil -} - -// fieldSnapshot captures the resource node as deployed, so a field can be put back to -// its base value once its transitions are done. -func (h *bundleHarness) fieldSnapshot() dyn.Value { - node, err := dyn.NewPathFromString(h.node) - require.NoError(h.t, err) - v, err := dyn.GetByPath(h.bundle.Config.Value(), node) - require.NoError(h.t, err) - return v -} - -// restoreField resets one field to whatever the base config had, including absent. -func (h *bundleHarness) restoreField(base dyn.Value, path string) { - rel, err := parsePath(path) - if err != nil { - return + if err := ensurePath(resource, node); err != nil { + return err } - value, err := dyn.GetByPath(base, rel) + converted, err := coerce(resource, node, value) if err != nil { - value = dyn.InvalidValue + return err } - _ = h.setField(path, value) + return structaccess.Set(resource, node, converted) } -// ensureParents creates any missing intermediate maps so a nested field can be set, -// which is what writing the same nested key into databricks.yml would do. -func ensureParents(root *dyn.Value, p dyn.Path) error { - for i := 1; i < len(p); i++ { - prefix := p[:i] - if _, err := dyn.GetByPath(*root, prefix); err == nil { +// removeField makes a field absent. For a struct field that means the zero value with the +// field dropped from ForceSendFields, which is what structaccess.Set does with nil; a map +// entry and a slice element have to go instead of being zeroed. +func removeField(resource any, node *structpath.PathNode) error { + parent := node.Parent() + // An error here means the path does not lead anywhere in this config -- a nil object on + // the way down -- which is the same conclusion as an absent parent: nothing to remove. + container, err := structaccess.Get(resource, parent) + if err != nil || isAbsent(container) { + return nil //nolint:nilerr // an unreachable parent means the field is already absent + } + + if index, isIndex := node.Index(); isIndex { + return removeIndex(resource, parent, container, index) + } + if key, hasKey := node.StringKey(); hasKey { + if value := reflect.ValueOf(container); value.Kind() == reflect.Map { + value.SetMapIndex(reflect.ValueOf(key).Convert(value.Type().Key()), reflect.Value{}) + return nil + } + } + return structaccess.Set(resource, node, nil) +} + +// removeIndex rebuilds a slice without one element and writes it back, since a slice +// element cannot be zeroed away. +func removeIndex(resource any, parent *structpath.PathNode, container any, index int) error { + value := reflect.ValueOf(container) + if value.Kind() != reflect.Slice { + return fmt.Errorf("cannot remove %s[%d]: parent is %s", parent, index, value.Kind()) + } + if index < 0 || index >= value.Len() { + return nil + } + // A fresh slice, not a re-slice: appending into the original backing array would also + // change any copy of the slice the suite is holding on to. + trimmed := reflect.MakeSlice(value.Type(), 0, value.Len()-1) + trimmed = reflect.AppendSlice(trimmed, value.Slice(0, index)) + trimmed = reflect.AppendSlice(trimmed, value.Slice(index+1, value.Len())) + return structaccess.Set(resource, parent, trimmed.Interface()) +} + +// ensurePath makes the places a path passes through exist, which is what writing the same +// nested key into databricks.yml would do: an absent object is allocated, and a list too +// short for an index is grown. It works top down, so the type of each missing level comes +// from the level above it, which by then exists. +func ensurePath(resource any, node *structpath.PathNode) error { + nodes := node.AsSlice() + for i, prefix := range nodes { + if index, isIndex := prefix.Index(); isIndex { + if err := growSlice(resource, prefix.Parent(), index); err != nil { + return err + } + continue + } + if i == len(nodes)-1 { + // The leaf is what the caller is about to write. + break + } + if _, nextIsIndex := nodes[i+1].Index(); nextIsIndex { + // growSlice creates the list itself, at the length the index needs. + continue + } + if current, err := structaccess.Get(resource, prefix); err == nil && !isAbsent(current) { continue } - if prefix[len(prefix)-1].Key() == "" { - // An index component: growing a sequence would invent an element the - // config never declared. - return fmt.Errorf("cannot create %s: parent is a sequence", prefix) + typ, err := typeAt(resource, prefix) + if err != nil { + return err } - updated, err := dyn.SetByPath(*root, prefix, dyn.V(map[string]dyn.Value{})) + empty, err := emptyValue(typ) if err != nil { + return fmt.Errorf("cannot create %s: %w", prefix, err) + } + if err := structaccess.Set(resource, prefix, empty); err != nil { return err } - *root = updated } return nil } -// mergeBase folds the value library's base fragment into the resource, so the fields it -// declares are present and can be varied. -func mergeBase(root *bundleconfig.Root, node string, base dyn.Value) error { - path, err := dyn.NewPathFromString(node) +// growSlice extends a list until an index is addressable, appending zero elements. The +// index is either the one the config already had, or the first of a list this suite is +// putting back after removing its only entry. +func growSlice(resource any, parent *structpath.PathNode, index int) error { + container, err := structaccess.Get(resource, parent) if err != nil { return err } - return root.Mutate(func(v dyn.Value) (dyn.Value, error) { - current, err := dyn.GetByPath(v, path) - if err != nil { - return dyn.InvalidValue, err - } - merged, err := merge.Merge(current, base) - if err != nil { - return dyn.InvalidValue, err - } - return dyn.SetByPath(v, path, merged) - }) -} - -// subResourceKinds are the child plan nodes this suite removes from every config; see -// stripSubResources. -var subResourceKinds = []string{"permissions", "grants"} + // An empty list reads back as absent -- omitempty hides it -- so the type comes from the + // declaration rather than from the value. + typ, err := typeAt(resource, parent) + if err != nil { + return err + } + if typ.Kind() != reflect.Slice { + return fmt.Errorf("cannot index %s: %s", parent, typ.Kind()) + } -func stripSubResources(root *bundleconfig.Root) error { - return root.Mutate(withoutSubResources) + grown := reflect.MakeSlice(typ, index+1, index+1) + if !isAbsent(container) { + value := reflect.ValueOf(container) + if value.Len() > index { + return nil + } + reflect.Copy(grown, value) + } + return structaccess.Set(resource, parent, grown.Interface()) } -// withoutSubResources returns the config with every permissions and grants block removed. -func withoutSubResources(v dyn.Value) (dyn.Value, error) { - for _, kind := range subResourceKinds { - pattern := dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey(), dyn.Key(kind)) - - // Collect first: deleting during the walk would mutate what it is walking. - var paths []dyn.Path - _, err := dyn.MapByPattern(v, pattern, func(p dyn.Path, found dyn.Value) (dyn.Value, error) { - paths = append(paths, slices.Clone(p)) - return found, nil - }) +// typeAt returns the declared type of the field at node. The parent has to exist, which +// ensurePath guarantees by allocating top down. +func typeAt(resource any, node *structpath.PathNode) (reflect.Type, error) { + parent := node.Parent() + typ := reflect.TypeOf(resource) + if !parent.IsRoot() { + value, err := structaccess.Get(resource, parent) if err != nil { - return dyn.InvalidValue, err + return nil, err } + if isAbsent(value) { + return nil, fmt.Errorf("%s is absent", parent) + } + typ = reflect.TypeOf(value) + } + for typ.Kind() == reflect.Pointer { + typ = typ.Elem() + } - for _, path := range paths { - v, err = deletePath(v, path) - if err != nil { - return dyn.InvalidValue, err - } + if _, isIndex := node.Index(); isIndex { + if typ.Kind() != reflect.Slice && typ.Kind() != reflect.Array { + return nil, fmt.Errorf("cannot index %s", typ.Kind()) + } + return typ.Elem(), nil + } + key, ok := node.StringKey() + if !ok { + return nil, fmt.Errorf("unsupported path %s", node) + } + switch typ.Kind() { + case reflect.Map: + return typ.Elem(), nil + case reflect.Struct: + field, _, ok := structaccess.FindStructFieldByKeyType(typ, key) + if !ok { + return nil, fmt.Errorf("field %q not found in %s", key, typ) } + return field.Type, nil + default: + return nil, fmt.Errorf("cannot access %q on %s", key, typ.Kind()) } - return v, nil } -// deletePath removes a key from its parent map. dyn has no delete, so the parent is -// rebuilt without the key, preserving order. -func deletePath(root dyn.Value, p dyn.Path) (dyn.Value, error) { - parentPath := p[:len(p)-1] - key := p[len(p)-1].Key() - if key == "" { - // An index: drop that element from its sequence. Removing the only element of a - // scalar list is how "absent" is expressed for a []string entry. - return deleteIndex(root, parentPath, p[len(p)-1].Index()) +// coerce converts a value from the value library into the field's own type. A scalar is +// left to structaccess, which converts between the numeric and string kinds; a map or list +// has to go through the type's own JSON unmarshaller, since []any is assignable to nothing +// and an SDK struct or enum decodes itself. +func coerce(resource any, node *structpath.PathNode, value any) (any, error) { + switch reflect.ValueOf(value).Kind() { + case reflect.Map, reflect.Slice: + default: + return value, nil } - parent, err := dyn.GetByPath(root, parentPath) - if dyn.IsNoSuchKeyError(err) || dyn.IsCannotTraverseNilError(err) { - // The parent object is not in this config, so the field is already absent. - return root, nil - } + typ, err := typeAt(resource, node) if err != nil { - return dyn.InvalidValue, err + return nil, err } - mapping, ok := parent.AsMap() - if !ok { - return dyn.InvalidValue, fmt.Errorf("cannot remove %s: parent is %s", p, parent.Kind()) + for typ.Kind() == reflect.Pointer { + typ = typ.Elem() } - if _, found := mapping.GetByString(key); !found { - return root, nil + if reflect.TypeOf(value).AssignableTo(typ) { + // Already the field's own type: a container value read back off the resource. + return value, nil } - trimmed := dyn.NewMapping() - for _, pair := range mapping.Pairs() { - if k, ok := pair.Key.AsString(); ok && k == key { - continue - } - k, _ := pair.Key.AsString() - trimmed.SetLoc(k, pair.Key.Locations(), pair.Value) + body, err := json.Marshal(value) + if err != nil { + return nil, err } - return dyn.SetByPath(root, parentPath, dyn.NewValue(trimmed, parent.Locations())) + converted := reflect.New(typ) + if err := json.Unmarshal(body, converted.Interface()); err != nil { + return nil, fmt.Errorf("cannot use %v as %s: %w", value, typ, err) + } + return converted.Elem().Interface(), nil } -// deleteIndex rebuilds a sequence without one element. -func deleteIndex(root dyn.Value, parentPath dyn.Path, index int) (dyn.Value, error) { - parent, err := dyn.GetByPath(root, parentPath) - if dyn.IsNoSuchKeyError(err) || dyn.IsCannotTraverseNilError(err) { - return root, nil +// emptyValue builds the empty container to put at an absent level of a path. +func emptyValue(typ reflect.Type) (any, error) { + switch typ.Kind() { + case reflect.Pointer: + return reflect.New(typ.Elem()).Interface(), nil + case reflect.Map: + return reflect.MakeMap(typ).Interface(), nil + case reflect.Slice: + return reflect.MakeSlice(typ, 0, 0).Interface(), nil + case reflect.Struct: + return reflect.New(typ).Elem().Interface(), nil + default: + return nil, fmt.Errorf("%s is not a container", typ) } +} + +// isAbsent reports whether a value read out of the config is not there at all. A nil +// pointer or map reads back as a typed nil, which is not a nil interface. +func isAbsent(value any) bool { + if value == nil { + return true + } + switch v := reflect.ValueOf(value); v.Kind() { + case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Interface: + return v.IsNil() + default: + return false + } +} + +// snapshot serializes the resource under test, so a field can be put back to what the +// base config had once its transitions are done. JSON is the same representation the API +// uses, and the SDK's own unmarshaller restores ForceSendFields, so an absent field comes +// back absent rather than as an explicit empty value. +func (h *bundleHarness) snapshot() []byte { + resource, err := h.resource() + require.NoError(h.t, err) + body, err := json.Marshal(resource) + require.NoError(h.t, err) + return body +} + +// restore puts the resource back to a snapshot taken earlier. +func (h *bundleHarness) restore(snapshot []byte) { + _ = h.edit(func(resource any) error { + value := reflect.ValueOf(resource).Elem() + value.Set(reflect.Zero(value.Type())) + return json.Unmarshal(snapshot, resource) + }) +} + +// seedBase folds the value library's base fragment into the resource, so the fields it +// declares are present and can be varied. Unmarshalling merges: a field the fragment does +// not mention keeps whatever the config gave it. +func seedBase(resource, base any) error { + body, err := json.Marshal(base) if err != nil { - return dyn.InvalidValue, err + return err } - items, ok := parent.AsSequence() - if !ok { - return dyn.InvalidValue, fmt.Errorf("cannot remove %s[%d]: parent is %s", parentPath, index, parent.Kind()) + return json.Unmarshal(body, resource) +} + +// subResourceKinds are the child plan nodes this suite removes from every config; see +// stripSubResources. +var subResourceKinds = []string{"permissions", "grants"} + +// stripSubResources drops the permissions and grants blocks from every resource of an +// initialized config. +func stripSubResources(root *bundleconfig.Root, ctx context.Context) error { + if err := root.MarkMutatorEntry(ctx); err != nil { + return err } - if index < 0 || index >= len(items) { - return root, nil + err := forEachResource(root, func(_ string, resource any) error { + typ := reflect.TypeOf(resource) + for typ.Kind() == reflect.Pointer { + typ = typ.Elem() + } + for _, kind := range subResourceKinds { + if _, _, ok := structaccess.FindStructFieldByKeyType(typ, kind); !ok { + continue + } + if err := structaccess.SetByString(resource, kind, nil); err != nil { + return err + } + } + return nil + }) + if exitErr := root.MarkMutatorExit(ctx); exitErr != nil { + return exitErr } - trimmed := slices.Clone(items[:index]) - trimmed = append(trimmed, items[index+1:]...) - return dyn.SetByPath(root, parentPath, dyn.NewValue(trimmed, parent.Locations())) + return err +} + +// forEachResource visits every declared resource with its config node key. +func forEachResource(root *bundleconfig.Root, fn func(node string, resource any) error) error { + for _, group := range root.Resources.AllResources() { + for key, resource := range group.Resources { + node := "resources." + group.Description.PluralName + "." + key + if err := fn(node, resource); err != nil { + return fmt.Errorf("%s: %w", node, err) + } + } + } + return nil } // uniqueName keeps cloud resources from colliding between runs and between the diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index b2282e5d4ef..014226353f9 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -9,16 +9,10 @@ config.command absent len1 SUPPRESSED n config.command len1 absent SUPPRESSED no active deployment config.command len2 absent SUPPRESSED no active deployment config.command len2 len1 SUPPRESSED no active deployment -config.command[0] absent y UNSETTABLE index out of bounds at "resources.apps.foo.config.command" -config.command[0] x y UNSETTABLE index out of bounds at "resources.apps.foo.config.command" -config.command[0] y absent UNSETTABLE index out of bounds at "resources.apps.foo.config.command" -config.command[0] y x UNSETTABLE index out of bounds at "resources.apps.foo.config.command" -config.command[1] absent x UNSETTABLE index out of bounds at "resources.apps.foo.config.command" -config.command[1] absent y UNSETTABLE index out of bounds at "resources.apps.foo.config.command" -config.command[1] x absent UNSETTABLE index out of bounds at "resources.apps.foo.config.command" -config.command[1] x y UNSETTABLE index out of bounds at "resources.apps.foo.config.command" -config.command[1] y absent UNSETTABLE index out of bounds at "resources.apps.foo.config.command" -config.command[1] y x UNSETTABLE index out of bounds at "resources.apps.foo.config.command" +config.command[1] absent x SUPPRESSED no active deployment +config.command[1] absent y SUPPRESSED no active deployment +config.command[1] x y SUPPRESSED no active deployment +config.command[1] y x SUPPRESSED no active deployment config.env absent len0 SUPPRESSED no active deployment config.env len0 absent SUPPRESSED no active deployment config.env len1 absent SUPPRESSED no active deployment @@ -44,18 +38,6 @@ description y absent UPDATE_IGNORE git_repository.auto_deploy true absent UPDATE_IGNORED git_repository.auto_deploy git_repository.caller_credential_id 1 absent UPDATE_IGNORED git_repository.caller_credential_id git_repository.caller_credential_id 2 absent UPDATE_IGNORED git_repository.caller_credential_id -git_source.branch absent x SUPPRESSED no active deployment -git_source.branch absent y SUPPRESSED no active deployment -git_source.branch x y SUPPRESSED no active deployment -git_source.branch y x SUPPRESSED no active deployment -git_source.resolved_commit absent x SUPPRESSED no active deployment -git_source.resolved_commit absent y SUPPRESSED no active deployment -git_source.resolved_commit x y SUPPRESSED no active deployment -git_source.resolved_commit y x SUPPRESSED no active deployment -git_source.tag absent x SUPPRESSED no active deployment -git_source.tag absent y SUPPRESSED no active deployment -git_source.tag x y SUPPRESSED no active deployment -git_source.tag y x SUPPRESSED no active deployment resources absent len0 UPDATE_IGNORED resources resources len0 absent UPDATE_IGNORED resources resources len1 absent UPDATE_IGNORED resources diff --git a/bundle/direct/tests/output/cluster_policies.txt b/bundle/direct/tests/output/cluster_policies.txt index d3d3756812f..e69de29bb2d 100644 --- a/bundle/direct/tests/output/cluster_policies.txt +++ b/bundle/direct/tests/output/cluster_policies.txt @@ -1,7 +0,0 @@ -=== cluster_policy.yml.tmpl -libraries[1].maven.exclusions[0] absent x UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" -libraries[1].maven.exclusions[0] absent y UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" -libraries[1].maven.exclusions[0] x absent UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" -libraries[1].maven.exclusions[0] x y UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" -libraries[1].maven.exclusions[0] y absent UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" -libraries[1].maven.exclusions[0] y x UNSETTABLE index out of bounds at "resources.cluster_policies.foo.libraries[1].maven.exclusions" diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/tests/output/clusters.txt index 1513ce03d3d..e69de29bb2d 100644 --- a/bundle/direct/tests/output/clusters.txt +++ b/bundle/direct/tests/output/clusters.txt @@ -1,15 +0,0 @@ -=== cluster.yml.tmpl -driver_node_type_flexibility.alternate_node_type_ids[0] absent x UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" -driver_node_type_flexibility.alternate_node_type_ids[0] absent y UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" -driver_node_type_flexibility.alternate_node_type_ids[0] x absent UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" -driver_node_type_flexibility.alternate_node_type_ids[0] x y UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" -driver_node_type_flexibility.alternate_node_type_ids[0] y x UNSETTABLE index out of bounds at "resources.clusters.foo.driver_node_type_flexibility.alternate_node_type_ids" -ssh_public_keys[0] absent x UNSETTABLE index out of bounds at "resources.clusters.foo.ssh_public_keys" -ssh_public_keys[0] absent y UNSETTABLE index out of bounds at "resources.clusters.foo.ssh_public_keys" -ssh_public_keys[0] x absent UNSETTABLE index out of bounds at "resources.clusters.foo.ssh_public_keys" -worker_node_type_flexibility.alternate_node_type_ids[0] absent x UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" -worker_node_type_flexibility.alternate_node_type_ids[0] absent y UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" -worker_node_type_flexibility.alternate_node_type_ids[0] x absent UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" -worker_node_type_flexibility.alternate_node_type_ids[0] x y UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" -worker_node_type_flexibility.alternate_node_type_ids[0] y absent UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" -worker_node_type_flexibility.alternate_node_type_ids[0] y x UNSETTABLE index out of bounds at "resources.clusters.foo.worker_node_type_flexibility.alternate_node_type_ids" diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index 64dcb15fca4..53a513f74f0 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -45,9 +45,7 @@ retention_window_in_days 2 1 COLLATERAL_DR retention_window_in_days 2 absent COLLATERAL_DRIFT capacity retention_window_in_days absent 1 COLLATERAL_DRIFT capacity retention_window_in_days absent 2 COLLATERAL_DRIFT capacity -stopped absent false COLLATERAL_DRIFT capacity stopped absent true COLLATERAL_DRIFT capacity -stopped false absent COLLATERAL_DRIFT capacity stopped false true COLLATERAL_DRIFT capacity stopped true absent COLLATERAL_DRIFT capacity stopped true false COLLATERAL_DRIFT capacity diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt index c19882aad6e..31a6086ddf2 100644 --- a/bundle/direct/tests/output/instance_pools.txt +++ b/bundle/direct/tests/output/instance_pools.txt @@ -1,12 +1,3 @@ === instance_pool.yml.tmpl enable_elastic_disk absent true UPDATE_IGNORED enable_elastic_disk enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk -node_type_flexibility.alternate_node_type_ids[0] absent x UNSETTABLE index out of bounds at "resources.instance_pools.foo.node_type_flexibility.alternate_node_type_ids" -node_type_flexibility.alternate_node_type_ids[0] absent y UNSETTABLE index out of bounds at "resources.instance_pools.foo.node_type_flexibility.alternate_node_type_ids" -node_type_flexibility.alternate_node_type_ids[0] x y UNSETTABLE index out of bounds at "resources.instance_pools.foo.node_type_flexibility.alternate_node_type_ids" -node_type_flexibility.alternate_node_type_ids[0] y absent UNSETTABLE index out of bounds at "resources.instance_pools.foo.node_type_flexibility.alternate_node_type_ids" -preloaded_spark_versions[0] absent x UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" -preloaded_spark_versions[0] absent y UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" -preloaded_spark_versions[0] x y UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" -preloaded_spark_versions[0] y absent UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" -preloaded_spark_versions[0] y x UNSETTABLE index out of bounds at "resources.instance_pools.foo.preloaded_spark_versions" diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index d483f94c813..e69de29bb2d 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -1,13 +0,0 @@ -=== job.yml.tmpl -email_notifications.on_failure[0] absent x UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" -email_notifications.on_failure[0] absent y UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" -email_notifications.on_failure[0] x absent UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" -email_notifications.on_failure[0] x y UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" -email_notifications.on_failure[0] y absent UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" -email_notifications.on_failure[0] y x UNSETTABLE index out of bounds at "resources.jobs.foo.email_notifications.on_failure" -environments[0].spec.dependencies[0] absent x UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" -environments[0].spec.dependencies[0] absent y UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" -environments[0].spec.dependencies[0] x absent UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" -environments[0].spec.dependencies[0] x y UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" -environments[0].spec.dependencies[0] y absent UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" -environments[0].spec.dependencies[0] y x UNSETTABLE index out of bounds at "resources.jobs.foo.environments[0].spec.dependencies" diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index b8fc370e38e..8c81b7148ce 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -5,13 +5,5 @@ config.auto_capture_config.enabled false absent COLLATERAL_DR config.auto_capture_config.enabled false true COLLATERAL_DRIFT config config.auto_capture_config.enabled true absent COLLATERAL_DRIFT config config.auto_capture_config.enabled true false COLLATERAL_DRIFT config +config.auto_capture_config.schema_name x absent COLLATERAL_DRIFT config config.auto_capture_config.table_name_prefix y absent COLLATERAL_DRIFT config -email_notifications.on_update_failure[0] absent x UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_failure" -email_notifications.on_update_failure[0] absent y UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_failure" -email_notifications.on_update_failure[0] x absent UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_failure" -email_notifications.on_update_success[0] absent x UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" -email_notifications.on_update_success[0] absent y UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" -email_notifications.on_update_success[0] x absent UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" -email_notifications.on_update_success[0] x y UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" -email_notifications.on_update_success[0] y absent UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" -email_notifications.on_update_success[0] y x UNSETTABLE index out of bounds at "resources.model_serving_endpoints.foo.email_notifications.on_update_success" diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 6b27e8a64fc..7efd1f9f305 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -1,33 +1,3 @@ === pipeline.yml.tmpl -clusters[0].ssh_public_keys[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.clusters[0].ssh_public_keys" -clusters[0].ssh_public_keys[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.clusters[0].ssh_public_keys" -clusters[0].ssh_public_keys[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.clusters[0].ssh_public_keys" dry_run absent true UPDATE_IGNORED dry_run dry_run false true UPDATE_IGNORED dry_run -environment.dependencies[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" -environment.dependencies[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" -environment.dependencies[0] x absent UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" -environment.dependencies[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" -environment.dependencies[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" -environment.dependencies[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.environment.dependencies" -filters.exclude[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" -filters.exclude[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" -filters.exclude[0] x absent UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" -filters.exclude[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" -filters.exclude[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.exclude" -filters.include[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" -filters.include[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" -filters.include[0] x absent UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" -filters.include[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" -filters.include[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" -filters.include[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.filters.include" -notifications[0].alerts[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" -notifications[0].alerts[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" -notifications[0].alerts[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" -notifications[0].alerts[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" -notifications[0].alerts[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].alerts" -notifications[0].email_recipients[0] absent x UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" -notifications[0].email_recipients[0] absent y UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" -notifications[0].email_recipients[0] x y UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" -notifications[0].email_recipients[0] y absent UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" -notifications[0].email_recipients[0] y x UNSETTABLE index out of bounds at "resources.pipelines.foo.notifications[0].email_recipients" diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index caa99780019..5ab031f75df 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -1,19 +1,18 @@ === postgres_project.yml.tmpl budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... custom_tags len1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... custom_tags len1 len0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... -custom_tags[0].key absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key absent y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key absent x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].key x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].key x y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].key y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].key y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value absent x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].value absent y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].value x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value x y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].value y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].value y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_branch x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... @@ -24,15 +23,18 @@ default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings absent keys0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... +default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true false BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings absent keys0 COLLATERAL_DRIFT default_endpoint_settings +default_endpoint_settings.pg_settings keys0 keys1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings keys1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... default_endpoint_settings.pg_settings keys1 keys0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings['work_mem'] x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings['work_mem'] x y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings['work_mem'] y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... diff --git a/bundle/direct/tests/output/postgres_synced_tables.txt b/bundle/direct/tests/output/postgres_synced_tables.txt index 9d5bca81072..e69de29bb2d 100644 --- a/bundle/direct/tests/output/postgres_synced_tables.txt +++ b/bundle/direct/tests/output/postgres_synced_tables.txt @@ -1,6 +0,0 @@ -=== postgres_synced_table.yml.tmpl -primary_key_columns[0] absent x UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" -primary_key_columns[0] absent y UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" -primary_key_columns[0] x y UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" -primary_key_columns[0] y absent UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" -primary_key_columns[0] y x UNSETTABLE index out of bounds at "resources.postgres_synced_tables.foo.primary_key_columns" diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 2a03ffd5f10..ee411bf73d0 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -1,7 +1,7 @@ package tests import ( - "bytes" + "encoding/json" "errors" "fmt" "hash/fnv" @@ -17,16 +17,17 @@ import ( "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/bundle/internal/validation/generated" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlloader" + "github.com/databricks/cli/libs/structs/structaccess" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/structs/structwalk" + "go.yaml.in/yaml/v3" ) // absent is the "field not present in the config" value. Every transition is a move // between two values drawn from a field's set, and add/remove are just the moves -// with absent on one side. -var absent = dyn.InvalidValue +// with absent on one side. A Go struct has no absent, so nil stands for it: writing it +// means the zero value with the field dropped from ForceSendFields. +var absent any = nil // fieldValues is the per-resource-type value library, e.g. testdata/fields/schemas.yml. // @@ -38,8 +39,9 @@ type fieldValues struct { // skips that whole subtree. skip map[string]string - // fields maps a field path to the values to try. - fields map[string][]dyn.Value + // fields maps a field path to the values to try, as the YAML gave them. They are + // converted to the field's own Go type when the field is enumerated. + fields map[string][]any // base is merged into the resource before the first deploy. It makes a block // reachable that the invariant config does not declare: a coherent git_source, say, @@ -48,7 +50,7 @@ type fieldValues struct { // // Keep path-valued fields out of it: it is merged after the mutator pipeline has run, // so nothing here goes through path translation. - base dyn.Value + base any } // fieldsDir holds the per-resource-type value libraries. @@ -128,11 +130,9 @@ var cliManagedFields = map[string]string{ "lifecycle.prevent_destroy": "acted on by the bundle, never sent to any API; gates destroy", } -// loadFieldValues reads testdata/fields/.yml. Parsing goes through the repo's own -// yamlloader rather than a yaml package, so the values arrive as dyn.Value -- the same -// representation the bundle config uses, which is what they are written into. +// loadFieldValues reads testdata/fields/.yml. func loadFieldValues(resourceType string) (*fieldValues, error) { - fv := &fieldValues{skip: map[string]string{}, fields: map[string][]dyn.Value{}, base: dyn.InvalidValue} + fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, base: nil} maps.Copy(fv.skip, cliManagedFields) path := filepath.Join(fieldsDir, resourceType+".yml") @@ -144,33 +144,17 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { return nil, err } - root, err := yamlloader.LoadYAML(path, bytes.NewReader(data)) - if err != nil { - return nil, fmt.Errorf("%s: %w", path, err) - } - - if v, err := dyn.Get(root, "skip"); err == nil { - m, _ := v.AsMap() - for _, pair := range m.Pairs() { - key, _ := pair.Key.AsString() - reason, _ := pair.Value.AsString() - fv.skip[key] = reason - } + var file struct { + Skip map[string]string `yaml:"skip"` + Fields map[string][]any `yaml:"fields"` + Base any `yaml:"base"` } - if v, err := dyn.Get(root, "base"); err == nil { - fv.base = v - } - if v, err := dyn.Get(root, "fields"); err == nil { - m, _ := v.AsMap() - for _, pair := range m.Pairs() { - key, _ := pair.Key.AsString() - values, ok := pair.Value.AsSequence() - if !ok { - return nil, fmt.Errorf("%s: fields.%s must be a list", path, key) - } - fv.fields[key] = values - } + if err := yaml.Unmarshal(data, &file); err != nil { + return nil, fmt.Errorf("%s: %w", path, err) } + maps.Copy(fv.skip, file.Skip) + maps.Copy(fv.fields, file.Fields) + fv.base = file.Base return fv, nil } @@ -197,16 +181,16 @@ func isContainer(kind reflect.Kind) bool { // different code path. // //nolint:exhaustive // the default branch covers every kind without a generic value -func defaultValues(kind reflect.Kind) []dyn.Value { +func defaultValues(kind reflect.Kind) []any { switch kind { case reflect.Bool: - return []dyn.Value{dyn.V(false), dyn.V(true)} + return []any{false, true} case reflect.String: - return []dyn.Value{dyn.V("x"), dyn.V("y")} + return []any{"x", "y"} case reflect.Int, reflect.Int32, reflect.Int64: - return []dyn.Value{dyn.V(1), dyn.V(2)} + return []any{1, 2} case reflect.Float32, reflect.Float64: - return []dyn.Value{dyn.V(1.0), dyn.V(2.0)} + return []any{1.0, 2.0} default: // The remaining kinds (interface, unsigned, complex, ...) have no meaningful // generic value; such a field needs an entry in the value library. @@ -221,9 +205,9 @@ type field struct { // and different for every field. seed uint64 - path string // structpath/dyn path, e.g. "comment" or "email_notifications.on_failure" + path string // structpath path, e.g. "comment" or "email_notifications.on_failure" kind reflect.Kind - values []dyn.Value + values []any // required fields do not get an "absent" transition: a config missing one is // rejected by bundle validate, so removing it is not something a user can deploy. @@ -232,7 +216,7 @@ type field struct { // transition is one move of a field from one value to another. type transition struct { - from, to dyn.Value + from, to any } // label names the transition for the subtest. Kept free of shell metacharacters so a @@ -256,7 +240,7 @@ func (t transition) label() string { func (f field) transitions() []transition { values := f.values if !f.required { - values = append([]dyn.Value{absent}, values...) + values = append([]any{absent}, values...) } if len(values) < 2 { return nil @@ -345,15 +329,14 @@ func isRequired(resourceType, path string) bool { // enumerateFields walks the resource's input config type the way cmd/bundle/debug // refschema does, and pairs each field with the values to try. // -// base is the resource as the config actually declares it, which is what makes slices and -// maps testable. A slice or map the config populates becomes a field in its own right, +// resource is the deployed resource, which is what makes slices and maps testable. A slice or map the config populates becomes a field in its own right, // with add-an-entry and remove-an-entry transitions; and a pattern like // "tasks[*].description" is expanded to the indices that exist, so the fields inside an // element get the same treatment as any other. A pattern with nothing behind it in the // config is reported as not covered rather than silently tested against nothing. -func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, base dyn.Value, runSeed uint64, unsettable []dresources.FieldRule) (fields []field, wildcard []string, inertFields map[string]string) { +func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, resource any, runSeed uint64, unsettable []dresources.FieldRule) (fields []field, wildcard []string, inertFields map[string]string) { inertFields = map[string]string{} - add := func(path string, kind reflect.Kind, values []dyn.Value) { + add := func(path string, kind reflect.Kind, values []any) { if values == nil { return } @@ -397,8 +380,8 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue // A struct is only a grouping; a slice or map is also a value the user can // grow and shrink, so test it as a field before descending into it. if typ.Kind() != reflect.Struct { - for _, concrete := range expandPattern(base, path) { - add(concrete, typ.Kind(), containerValues(base, concrete)) + for _, concrete := range expandPattern(resource, path) { + add(concrete, typ.Kind(), containerValues(resource, concrete)) } } return true @@ -413,7 +396,7 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue return false } - concrete := expandPattern(base, path) + concrete := expandPattern(resource, path) if len(concrete) == 0 { // A field the user cannot set is not a coverage gap. Checked on the pattern here // because there is no concrete path to check: the container is absent. @@ -455,77 +438,98 @@ func isNotUserSettable(sf *reflect.StructField) bool { sf.Tag.Get("json") == "-" } -// containerValues returns the values to try for a slice or map field that the base config -// actually populates: the config's own value, and that value with its last entry dropped. -// Combined with the implicit "absent", one field then covers adding and removing the whole -// container as well as adding and removing a single entry -- all with data the backend has -// already accepted, so nothing has to be invented. -func containerValues(base dyn.Value, path string) []dyn.Value { - current, err := dyn.Get(base, path) - if err != nil || !current.IsValid() { +// containerValues returns the values to try for a slice or map field that the deployed +// resource actually populates: the resource's own value, and that value with one entry +// dropped. Combined with the implicit "absent", one field then covers adding and removing +// the whole container as well as adding and removing a single entry -- all with data the +// backend has already accepted, so nothing has to be invented. +// +// Both are deep copies: a later edit rewrites the same map or slice in place, which would +// otherwise change a value recorded here. +func containerValues(resource any, path string) []any { + current, err := structaccess.GetByString(resource, path) + if err != nil || isAbsent(current) { return nil } - switch current.Kind() { - case dyn.KindSequence: - items, _ := current.AsSequence() - if len(items) == 0 { + value := reflect.ValueOf(current) + var trimmed reflect.Value + switch value.Kind() { + case reflect.Slice: + if value.Len() == 0 { return nil } - return []dyn.Value{current, dyn.V(slices.Clone(items[:len(items)-1]))} + trimmed = reflect.MakeSlice(value.Type(), 0, value.Len()-1) + trimmed = reflect.AppendSlice(trimmed, value.Slice(0, value.Len()-1)) - case dyn.KindMap: - m, _ := current.AsMap() - pairs := m.Pairs() - if len(pairs) == 0 { + case reflect.Map: + keys := sortedMapKeys(value) + if len(keys) == 0 { return nil } - trimmed := dyn.NewMapping() - for _, pair := range pairs[:len(pairs)-1] { - key, _ := pair.Key.AsString() - trimmed.SetLoc(key, pair.Key.Locations(), pair.Value) + trimmed = reflect.MakeMap(value.Type()) + for _, key := range keys[:len(keys)-1] { + trimmed.SetMapIndex(key, value.MapIndex(key)) } - return []dyn.Value{current, dyn.NewValue(trimmed, current.Locations())} default: return nil } + + full, err := clone(current) + if err != nil { + return nil + } + return []any{full, trimmed.Interface()} } -// expandPattern turns a pattern from the type walk into the concrete paths the base config -// actually has: "tasks[*].description" against a config with one task yields +// clone deep-copies a value through the JSON representation the API uses, which the SDK +// types define themselves -- so ForceSendFields survives the copy. +func clone(value any) (any, error) { + body, err := json.Marshal(value) + if err != nil { + return nil, err + } + copied := reflect.New(reflect.TypeOf(value)) + if err := json.Unmarshal(body, copied.Interface()); err != nil { + return nil, err + } + return copied.Elem().Interface(), nil +} + +// sortedMapKeys orders a map's keys so that which entry gets dropped, and which entries a +// wildcard expands to, is the same on every run. A Go map has no order of its own. +func sortedMapKeys(value reflect.Value) []reflect.Value { + keys := value.MapKeys() + slices.SortFunc(keys, func(a, b reflect.Value) int { return strings.Compare(a.String(), b.String()) }) + return keys +} + +// expandPattern turns a pattern from the type walk into the concrete paths the deployed +// resource actually has: "tasks[*].description" against a resource with one task yields // "tasks[0].description". A pattern with nothing behind it yields nothing, which is how a // field under an absent container is reported as not covered. -func expandPattern(base dyn.Value, pattern string) []string { +func expandPattern(resource any, pattern string) []string { paths := []string{""} for _, seg := range splitPattern(pattern) { var next []string for _, prefix := range paths { switch seg { case "[*]": - value, err := dyn.Get(base, prefix) - if err != nil { - continue - } - items, ok := value.AsSequence() - if !ok { + value, err := valueAt(resource, prefix) + if err != nil || value.Kind() != reflect.Slice { continue } - for i := range items { + for i := range value.Len() { next = append(next, prefix+"["+strconv.Itoa(i)+"]") } case "*": - value, err := dyn.Get(base, prefix) - if err != nil { + value, err := valueAt(resource, prefix) + if err != nil || value.Kind() != reflect.Map { continue } - m, ok := value.AsMap() - if !ok { - continue - } - for _, pair := range m.Pairs() { - key, _ := pair.Key.AsString() - next = append(next, prefix+"["+quoteKey(key)+"]") + for _, key := range sortedMapKeys(value) { + next = append(next, prefix+"["+quoteKey(key.String())+"]") } default: next = append(next, joinPath(prefix, seg)) @@ -536,6 +540,21 @@ func expandPattern(base dyn.Value, pattern string) []string { return paths } +// valueAt reads a path off the resource, with "" meaning the resource itself. +func valueAt(resource any, path string) (reflect.Value, error) { + if path == "" { + return reflect.Indirect(reflect.ValueOf(resource)), nil + } + value, err := structaccess.GetByString(resource, path) + if err != nil { + return reflect.Value{}, err + } + if isAbsent(value) { + return reflect.Value{}, fmt.Errorf("%s is absent", path) + } + return reflect.Indirect(reflect.ValueOf(value)), nil +} + // splitPattern breaks a pattern into field names, "[*]" and "*" segments. func splitPattern(pattern string) []string { var segs []string @@ -568,7 +587,7 @@ func joinPath(prefix, name string) string { return prefix + "." + name } -// quoteKey renders a map key the way structpath and dyn both parse it back. +// quoteKey renders a map key the way structpath parses it back. func quoteKey(key string) string { return "'" + strings.ReplaceAll(key, "'", "''") + "'" } diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go index 2335b398516..8ec4a52c1f2 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/tests/values_unit_test.go @@ -3,7 +3,10 @@ package tests import ( "testing" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" + + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -13,9 +16,9 @@ import ( // coverage, no repeats, and every step starting where the last one ended. func TestTransitionsCoverEveryPairInOneChain(t *testing.T) { for n := 2; n <= 6; n++ { - values := make([]dyn.Value, 0, n-1) + values := make([]any, 0, n-1) for i := 1; i < n; i++ { - values = append(values, dyn.V(i)) + values = append(values, i) } f := field{path: "some.field", values: values} //exhaustruct:ignore @@ -42,7 +45,7 @@ func TestTransitionsCoverEveryPairInOneChain(t *testing.T) { // A required field has no absent value, so its chain is one vertex smaller. func TestTransitionsSkipAbsentForRequiredField(t *testing.T) { - f := field{path: "name", values: []dyn.Value{dyn.V("a"), dyn.V("b")}, required: true} //exhaustruct:ignore + f := field{path: "name", values: []any{"a", "b"}, required: true} //exhaustruct:ignore got := f.transitions() @@ -53,35 +56,81 @@ func TestTransitionsSkipAbsentForRequiredField(t *testing.T) { } } -// A pattern from the type walk has to expand against the deployed config, or the fields -// inside a container are silently never tested. Map wildcards ("properties.*") and element +// A pattern from the type walk has to expand against the deployed resource, or the fields +// inside a container are silently never tested. Map wildcards ("tags.*") and element // wildcards ("tasks[*]") take different paths through splitPattern, so both are pinned here. func TestExpandPattern(t *testing.T) { - base := dyn.V(map[string]dyn.Value{ - "properties": dyn.V(map[string]dyn.Value{"team": dyn.V("eng")}), - "tasks": dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{"key": dyn.V("a")}), - dyn.V(map[string]dyn.Value{"key": dyn.V("b")}), - }), - "tags": dyn.V(map[string]dyn.Value{ - "custom": dyn.V([]dyn.Value{dyn.V(map[string]dyn.Value{"key": dyn.V("x")})}), - }), - }) + //exhaustruct:ignore + job := &resources.Job{JobSettings: jobs.JobSettings{ + Name: "n", + Tags: map[string]string{"team": "eng"}, + Tasks: []jobs.Task{ + {TaskKey: "a"}, //exhaustruct:ignore + {TaskKey: "b"}, //exhaustruct:ignore + }, + EmailNotifications: &jobs.JobEmailNotifications{OnFailure: []string{"a@b.test"}}, //exhaustruct:ignore + }} for _, tc := range []struct { pattern string want []string }{ {"name", []string{"name"}}, - {"properties.*", []string{"properties['team']"}}, - {"tasks[*].key", []string{"tasks[0].key", "tasks[1].key"}}, - {"tags.custom[*].key", []string{"tags.custom[0].key"}}, - // Nothing behind it in the config, which is what makes a field "not covered". - {"absent[*].key", nil}, - {"absent.*", nil}, + {"tags.*", []string{"tags['team']"}}, + {"tasks[*].task_key", []string{"tasks[0].task_key", "tasks[1].task_key"}}, + {"email_notifications.on_failure[*]", []string{"email_notifications.on_failure[0]"}}, + // Nothing behind it in the resource, which is what makes a field "not covered". + {"job_clusters[*].job_cluster_key", nil}, + {"tasks[0].notebook_task.base_parameters.*", nil}, } { t.Run(tc.pattern, func(t *testing.T) { - assert.Equal(t, tc.want, expandPattern(base, tc.pattern)) + assert.Equal(t, tc.want, expandPattern(job, tc.pattern)) }) } } + +// setValue applies one edit the way setField does, without a bundle around it. +func setValue(resource any, path string, value any) error { + node, err := structpath.ParsePath(path) + if err != nil { + return err + } + return setNode(resource, node, value) +} + +// setField writes into the typed resource, where "absent" is the zero value with the field +// dropped from ForceSendFields -- the distinction the API sees. A map entry and a slice +// element have to be removed outright, which is a different code path. +func TestSetFieldAbsentAndEmpty(t *testing.T) { + //exhaustruct:ignore + job := &resources.Job{JobSettings: jobs.JobSettings{ + Name: "n", + Tags: map[string]string{"team": "eng", "env": "dev"}, + Tasks: []jobs.Task{ + {TaskKey: "a"}, //exhaustruct:ignore + {TaskKey: "b"}, //exhaustruct:ignore + }, + }} + + require.NoError(t, setValue(job, "description", "")) + assert.Contains(t, job.ForceSendFields, "Description") + + require.NoError(t, setValue(job, "description", nil)) + assert.NotContains(t, job.ForceSendFields, "Description") + + // A nested object the config leaves out is allocated on the way in. + require.NoError(t, setValue(job, "email_notifications.no_alert_for_skipped_runs", true)) + require.NotNil(t, job.EmailNotifications) + assert.True(t, job.EmailNotifications.NoAlertForSkippedRuns) + + require.NoError(t, setValue(job, "tags['env']", nil)) + assert.Equal(t, map[string]string{"team": "eng"}, job.Tags) + + require.NoError(t, setValue(job, "tasks[0]", nil)) + assert.Equal(t, []jobs.Task{{TaskKey: "b"}}, job.Tasks) //exhaustruct:ignore + + // A list from the value library arrives as []any and has to be decoded into the + // field's own element type. + require.NoError(t, setValue(job, "email_notifications.on_failure", []any{"a@b.test"})) + assert.Equal(t, []string{"a@b.test"}, job.EmailNotifications.OnFailure) +} diff --git a/libs/structs/structaccess/bundle_test.go b/libs/structs/structaccess/bundle_test.go index 09c39b57122..895ef9820db 100644 --- a/libs/structs/structaccess/bundle_test.go +++ b/libs/structs/structaccess/bundle_test.go @@ -97,11 +97,12 @@ func TestGetSet_DoublyEmbeddedField(t *testing.T) { // An explicit empty value is recorded on ProjectSpec, which declares the field. require.NoError(t, SetByString(project, "budget_policy_id", "")) require.Contains(t, project.ProjectSpec.ForceSendFields, "BudgetPolicyId") - require.NotContains(t, project.PostgresProjectConfig.ForceSendFields, "BudgetPolicyId") + require.NotContains(t, project.ForceSendFields, "BudgetPolicyId") value, err = GetByString(project, "budget_policy_id") require.NoError(t, err) - require.Equal(t, "", value) + // The empty string, not nil: that is what separates an explicit "" from an absent field. + require.Equal(t, any(""), value) // And dropping it again leaves the field absent. require.NoError(t, SetByString(project, "budget_policy_id", nil)) From dae6a48f64fcd2ef9f6291f5947eabf35ce03d29 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 23:05:03 +0200 Subject: [PATCH 017/118] testserver: reject an empty MLflow model description MLflow refuses to clear a description, and the update sends the field unconditionally, so a config that drops it fails on a real workspace while the fake server accepted it. readplan-permissions changed the description instead of removing it, since removing it can no longer converge. Co-authored-by: Isaac --- .../models/readplan-permissions/databricks.yml | 2 +- .../resources/models/readplan-permissions/output.txt | 2 +- .../resources/models/readplan-permissions/script | 5 ++++- bundle/direct/tests/output/models.txt | 3 +++ libs/testserver/models.go | 12 ++++++++++++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/acceptance/bundle/resources/models/readplan-permissions/databricks.yml b/acceptance/bundle/resources/models/readplan-permissions/databricks.yml index 1d4e2f53e6a..9e48a0543da 100644 --- a/acceptance/bundle/resources/models/readplan-permissions/databricks.yml +++ b/acceptance/bundle/resources/models/readplan-permissions/databricks.yml @@ -5,7 +5,7 @@ resources: models: mymodel: name: test-model - description: initial # TO_REMOVE + description: initial permissions: - level: CAN_READ user_name: viewer@example.com diff --git a/acceptance/bundle/resources/models/readplan-permissions/output.txt b/acceptance/bundle/resources/models/readplan-permissions/output.txt index e65f9b2a560..853bd8924c6 100644 --- a/acceptance/bundle/resources/models/readplan-permissions/output.txt +++ b/acceptance/bundle/resources/models/readplan-permissions/output.txt @@ -29,7 +29,7 @@ Resources: 2 created, 0 changed, 0 deleted, 0 unchanged } } -=== Remove manager permission and description, deploy from planUploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +=== Remove manager permission, change description, deploy from planUploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Updated models.mymodel Updated models.mymodel.permissions Files: 4 uploaded, 0 deleted diff --git a/acceptance/bundle/resources/models/readplan-permissions/script b/acceptance/bundle/resources/models/readplan-permissions/script index f8be26409e3..e0f44832600 100644 --- a/acceptance/bundle/resources/models/readplan-permissions/script +++ b/acceptance/bundle/resources/models/readplan-permissions/script @@ -15,8 +15,11 @@ trace $CLI bundle deploy register_model_id trace print_requests.py //permissions/registered-models -title "Remove manager permission and description, deploy from plan" +title "Remove manager permission, change description, deploy from plan" grep -v TO_REMOVE databricks.yml > updated.yml && mv updated.yml databricks.yml +# Changed rather than removed: MLflow rejects an empty description, so clearing the field +# fails the update. See libs/testserver/models.go. +update_file.py databricks.yml "description: initial" "description: changed" $CLI bundle plan -o json > tmp.plan.json $CLI bundle deploy $(readplanarg tmp.plan.json) register_model_id diff --git a/bundle/direct/tests/output/models.txt b/bundle/direct/tests/output/models.txt index e69de29bb2d..638a23988e1 100644 --- a/bundle/direct/tests/output/models.txt +++ b/bundle/direct/tests/output/models.txt @@ -0,0 +1,3 @@ +=== model.yml.tmpl +description x absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) +description y absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) diff --git a/libs/testserver/models.go b/libs/testserver/models.go index 3314327d8a7..c4798d6eced 100644 --- a/libs/testserver/models.go +++ b/libs/testserver/models.go @@ -68,6 +68,18 @@ func (s *FakeWorkspace) ModelRegistryUpdateModel(req Request) any { } } + // MLflow refuses to clear a description: the update carries the field unconditionally, + // so a config that drops it sends "" and the real API rejects the request. + if request.Description == "" { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "Description cannot be empty.", + }, + } + } + // Update the model existingModel.Description = request.Description s.ModelRegistryModels[request.Name] = existingModel From e343632c062bd99f3138baa791039ec79f7e6ab8 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 23:10:50 +0200 Subject: [PATCH 018/118] Field catalog: match skip keys as patterns From the AWS comparison: a skip key naming a field inside a slice ("aliases[*].id") never matched the concrete "aliases[0].id" it expands to, so the field was tested anyway and the backend rejected every value. Keys are now matched the way the planner matches its own field rules, which also makes naming a block skip everything beneath it -- so the ".*" suffix is gone. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 3 ++ .../direct/tests/output/registered_models.txt | 8 +++++ .../fields/model_serving_endpoints.yml | 2 +- .../testdata/fields/registered_models.yml | 14 +++++++++ bundle/direct/tests/values_test.go | 24 ++++++++++++-- bundle/direct/tests/values_unit_test.go | 31 +++++++++++++++++++ 6 files changed, 78 insertions(+), 4 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index 4a1f248b079..0ac851ee035 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -102,6 +102,9 @@ Every resource type runs everywhere — local and cloud alike — so most types at all. A field with no entry gets two values for its Go kind, which is enough to see a value-to-value move on top of add and remove. +A `skip` key may be a pattern (`aliases[*].id`), matched the way the planner matches its own +field rules, and naming a block skips everything beneath it. + `skip` is for a field no single-field edit can exercise — one that only validates as part of a set (a job's `git_source`), or whose change is correct but ruinously slow (an app rename waits out an asynchronous delete). Each entry needs a reason, and shows up in diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 10414736d1b..6b27ec71520 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -2,11 +2,19 @@ aliases absent len0 UPDATE_IGNORED aliases aliases len0 absent SUPPRESSED input_only aliases len1 len0 UPDATE_IGNORED aliases +catalog_name absent main BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) +catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) full_name absent x UPDATE_IGNORED full_name full_name absent y UPDATE_IGNORED full_name full_name x y UPDATE_IGNORED full_name full_name y x UPDATE_IGNORED full_name +name absent x BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name absent y BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name x absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name y absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) owner absent x UPDATE_IGNORED owner owner absent y UPDATE_IGNORED owner owner x y UPDATE_IGNORED owner owner y x UPDATE_IGNORED owner +schema_name absent default BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) +schema_name default absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml index 56a239abbc6..89fe8f6c3a8 100644 --- a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml +++ b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml @@ -30,4 +30,4 @@ skip: # is rejected on every later update ("failed to update telemetry config"). The fields are # covered by model_serving_endpoint_telemetry.yml.tmpl, which declares the whole block -- # but that config is not the simplest one, so it is not the one this suite picks. - telemetry_config.*: needs the telemetry corpus config, which is not the simplest one + telemetry_config: needs the telemetry corpus config, which is not the simplest one diff --git a/bundle/direct/tests/testdata/fields/registered_models.yml b/bundle/direct/tests/testdata/fields/registered_models.yml index fc83568da45..30ed9145944 100644 --- a/bundle/direct/tests/testdata/fields/registered_models.yml +++ b/bundle/direct/tests/testdata/fields/registered_models.yml @@ -7,3 +7,17 @@ base: aliases: - alias_name: champion version_num: 1 + +skip: + # The backend assigns an alias its id; a value of ours is rejected as a bad UUID. + aliases[*].id: assigned by the backend + # Same shape: a UUID naming the workspace's metastore, which the backend fills in. + metastore_id: assigned by the backend + # Accepted by the API and then not honoured, so it drifts forever. Testing it properly + # needs a real external location, as with volumes. + storage_location: needs an external location + +fields: + # See the note in volumes.yml: a reference has to name something that exists. + catalog_name: [main] + schema_name: [default] diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index ee411bf73d0..a657774af39 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -304,14 +304,32 @@ func requiredFields(resourceType, parent string) []string { return generated.RequiredFields[key] } -// skipReason returns why a field is excluded, if it is. A ".*" key covers every -// field beneath it, for a block that only works as a whole. +// skipReason returns why a field is excluded, if it is. A key may be a pattern, matched the +// same way the planner matches its own field rules -- so "aliases[*].id" covers +// "aliases[0].id", and a trailing ".*" or "[*]" covers everything beneath a block that only +// works as a whole. func (fv *fieldValues) skipReason(path string) (string, bool) { if reason, ok := fv.skip[path]; ok { return reason, true } + + concrete, err := structpath.ParsePath(path) + if err != nil { + // A pattern, which cannot be matched against another pattern: compare textually. + for key, reason := range fv.skip { + if strings.HasPrefix(path, key+".") || strings.HasPrefix(path, key+"[") { + return reason, true + } + } + return "", false + } + for key, reason := range fv.skip { - if prefix, ok := strings.CutSuffix(key, ".*"); ok && strings.HasPrefix(path, prefix+".") { + pattern, err := structpath.ParsePattern(key) + if err != nil { + continue + } + if concrete.HasPatternPrefix(pattern) { return reason, true } } diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go index 8ec4a52c1f2..9b299ad40c6 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/tests/values_unit_test.go @@ -134,3 +134,34 @@ func TestSetFieldAbsentAndEmpty(t *testing.T) { require.NoError(t, setValue(job, "email_notifications.on_failure", []any{"a@b.test"})) assert.Equal(t, []string{"a@b.test"}, job.EmailNotifications.OnFailure) } + +// A skip key may be a pattern, since a field inside a slice has no fixed index and the +// value library cannot name one. Written as a table because the three key shapes -- exact, +// element wildcard, subtree -- take different paths through the matcher. +func TestSkipReasonMatchesPatterns(t *testing.T) { + fv := &fieldValues{skip: map[string]string{ + "storage_root": "needs an external location", + "aliases[*].id": "assigned by the backend", + "telemetry_config": "only accepted as a whole", + }} //exhaustruct:ignore + + for _, tc := range []struct { + path string + reason string + }{ + {"storage_root", "needs an external location"}, + {"aliases[0].id", "assigned by the backend"}, + {"aliases[3].id", "assigned by the backend"}, + {"telemetry_config.enabled", "only accepted as a whole"}, + // A pattern of its own, which is what a field under an absent container is. + {"telemetry_config.sinks[*].name", "only accepted as a whole"}, + {"aliases[0].alias_name", ""}, + {"comment", ""}, + } { + t.Run(tc.path, func(t *testing.T) { + reason, skipped := fv.skipReason(tc.path) + assert.Equal(t, tc.reason != "", skipped) + assert.Equal(t, tc.reason, reason) + }) + } +} From f24a5e58414470f3d5ad4da391e93d8a5421a471 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 23:14:18 +0200 Subject: [PATCH 019/118] testserver: honor include_aliases on a registered-model read From the AWS comparison: an alias belongs to a model version and is managed through its own API, so UC does not echo aliases on a plain GET -- which is why the direct engine passes include_aliases=false and declares the field input_only. The fake server returned them anyway, so the remote appeared to hold what the config asked for and the field's real behaviour was hidden. registered_models now matches AWS row for row. Co-authored-by: Isaac --- .../direct/tests/output/registered_models.txt | 3 -- libs/testserver/handlers.go | 2 +- libs/testserver/registered_models.go | 33 +++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 6b27ec71520..995ac464d91 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,7 +1,4 @@ === registered_model.yml.tmpl -aliases absent len0 UPDATE_IGNORED aliases -aliases len0 absent SUPPRESSED input_only -aliases len1 len0 UPDATE_IGNORED aliases catalog_name absent main BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) full_name absent x UPDATE_IGNORED full_name diff --git a/libs/testserver/handlers.go b/libs/testserver/handlers.go index 8c44d97eae7..6eb04074465 100644 --- a/libs/testserver/handlers.go +++ b/libs/testserver/handlers.go @@ -583,7 +583,7 @@ func AddDefaultHandlers(server *Server) { // Registered Models: server.Handle("GET", "/api/2.1/unity-catalog/models/{full_name}", func(req Request) any { - return MapGet(req.Workspace, req.Workspace.RegisteredModels, req.Vars["full_name"]) + return req.Workspace.RegisteredModelsGet(req, req.Vars["full_name"]) }) server.Handle("POST", "/api/2.1/unity-catalog/models", func(req Request) any { diff --git a/libs/testserver/registered_models.go b/libs/testserver/registered_models.go index b6fb800a623..fa2e2213e49 100644 --- a/libs/testserver/registered_models.go +++ b/libs/testserver/registered_models.go @@ -23,6 +23,24 @@ func (s *FakeWorkspace) RegisteredModelsCreate(req Request) Response { } } + // UC requires all three parts of the name; without this the fake would store a model + // under a key like "..name" that no read can address. + for _, required := range []struct{ field, value string }{ + {"catalog_name", createRequest.CatalogName}, + {"schema_name", createRequest.SchemaName}, + {"name", createRequest.Name}, + } { + if required.value == "" { + return Response{ + StatusCode: http.StatusBadRequest, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "CreateRegisteredModel Missing required field: " + required.field, + }, + } + } + } + // Build full name from catalog.schema.name fullName := createRequest.CatalogName + "." + createRequest.SchemaName + "." + createRequest.Name @@ -51,6 +69,21 @@ func (s *FakeWorkspace) RegisteredModelsCreate(req Request) Response { } } +// RegisteredModelsGet honors include_aliases, which UC defaults to false: an alias belongs +// to a model version and is managed through its own API, so a plain GET does not echo the +// aliases a create or update was given. Without this the remote appeared to hold them and a +// config that sets aliases looked like it converged. +func (s *FakeWorkspace) RegisteredModelsGet(req Request, fullName string) Response { + response := MapGetUC(s, s.RegisteredModels, fullName, "Registered Model") + model, ok := response.Body.(catalog.RegisteredModelInfo) + if !ok || req.URL.Query().Get("include_aliases") == "true" { + return response + } + model.Aliases = nil + response.Body = model + return response +} + func (s *FakeWorkspace) RegisteredModelsUpdate(req Request, fullName string) Response { defer s.LockUnlock()() From 2ac4be0d6fad66d671ab687696a45b38064461fe Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 23:18:17 +0200 Subject: [PATCH 020/118] Field catalog: give experiments values a real workspace accepts From the AWS comparison: an experiment name is an absolute workspace path and an artifact location needs a scheme, so the generic "x" was rejected outright. The UC trace location needs its catalog and schema together, like a job's git_source, so it is seeded rather than built up one field at a time. experiments matches AWS row for row. Co-authored-by: Isaac --- .../direct/tests/testdata/fields/experiments.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bundle/direct/tests/testdata/fields/experiments.yml b/bundle/direct/tests/testdata/fields/experiments.yml index 3f13ffb2e2a..04603974d31 100644 --- a/bundle/direct/tests/testdata/fields/experiments.yml +++ b/bundle/direct/tests/testdata/fields/experiments.yml @@ -3,7 +3,23 @@ # The invariant config declares no tags, so tags[*] would expand to nothing and its fields # would only be reported as not covered. Seeding one entry makes the whole block testable: # the container itself (add and remove an entry) and the fields inside the entry. +# +# The UC trace location is the same case as a job's git_source: the backend requires the +# catalog and the schema together, so neither can be reached one field at a time. base: tags: - key: team value: eng + trace_location: + uc_trace_location: + catalog: main + schema: default + +fields: + # An experiment name is an absolute workspace path, so the generic "x" is rejected. + name: [/Shared/test-experiment-a, /Shared/test-experiment-b] + # The artifact location needs a scheme. + artifact_location: [dbfs:/tmp/mlflow-a, dbfs:/tmp/mlflow-b] + # See the note in volumes.yml: a reference has to name something that exists. + trace_location.uc_trace_location.catalog: [main] + trace_location.uc_trace_location.schema: [default] From 084431063553961df8b04b17274d972c83ad6649 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 23:19:22 +0200 Subject: [PATCH 021/118] Field catalog: skip catalog blocks that need cloud setup, fix retention values From the AWS comparison. A retention period is validated in days (0, or 7 to 30), so the generic 1 and 2 were rejected. The rest need workspace state this suite does not provision: a Delta Sharing provider and share, a connection, an external location, a customer-managed key. options is skipped for a different reason worth noting: UpdateCatalog rejects the field outright ("UpdateCatalog options can not be provided"), and the engine sends it on every update whenever the config declares it -- so seeding it made every other field of the catalog fail its update too. catalogs matches AWS row for row. Co-authored-by: Isaac --- bundle/direct/tests/output/catalogs.txt | 10 ++----- .../direct/tests/testdata/fields/catalogs.yml | 29 +++++++++++++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index 92df91801fb..b73ab90201b 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -1,12 +1,6 @@ === catalog.yml.tmpl -custom_max_retention_hours 1 absent UPDATE_IGNORED custom_max_retention_hours -custom_max_retention_hours 2 absent UPDATE_IGNORED custom_max_retention_hours -options absent keys0 UPDATE_IGNORED options -options keys0 absent UPDATE_IGNORED options -options keys1 absent UPDATE_IGNORED options -options keys1 keys0 UPDATE_IGNORED options -options['region'] x absent UPDATE_IGNORED options['region'] -options['region'] y absent UPDATE_IGNORED options['region'] +custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours +custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours properties absent keys0 UPDATE_IGNORED properties properties keys0 absent UPDATE_IGNORED properties properties keys1 absent UPDATE_IGNORED properties diff --git a/bundle/direct/tests/testdata/fields/catalogs.yml b/bundle/direct/tests/testdata/fields/catalogs.yml index b5f5c3c4312..36aa6110e70 100644 --- a/bundle/direct/tests/testdata/fields/catalogs.yml +++ b/bundle/direct/tests/testdata/fields/catalogs.yml @@ -1,6 +1,31 @@ # Value library for catalogs. See ../../README.md. + +# properties is a map the config does not declare, so its entries would be unreachable. base: properties: team: eng - options: - region: us-east-1 + +skip: + # options only applies to a foreign catalog, which needs a real connection, and + # UpdateCatalog rejects the field outright ("UpdateCatalog options can not be + # provided") -- so seeding it made every *other* field of the catalog fail its update + # too. Worth a look on its own: the engine sends options on update whenever the config + # declares them, which a foreign catalog cannot survive. + options: rejected by UpdateCatalog; only settable at create on a foreign catalog + # A Delta Sharing catalog needs both of these and a share that exists. + provider_name: needs a Delta Sharing provider and share + share_name: needs a Delta Sharing provider and share + # A foreign catalog needs a connection that exists. + connection_name: needs a connection + # Must point at a real external location. + storage_root: needs an external location + # Needs a customer-managed key registered with the workspace; every field of the block + # is a UUID or key id the backend validates against that registration. + managed_encryption_settings: needs a customer-managed key registered with the workspace + # A UUID naming the workspace's metastore, which the backend fills in. + metastore_id: assigned by the backend + +fields: + # The backend takes hours but validates them as days: the retention period must be 0 or + # between 7 and 30 days, so the generic 1 and 2 are rejected. + custom_max_retention_hours: [168, 720] From 9034259b5c9aeb4ea4f72fe4613982eef081dc2b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 23:22:04 +0200 Subject: [PATCH 022/118] Field catalog: let a resource type declare itself local-only From the AWS comparison: external_locations cannot deploy against a real workspace at all -- it needs a storage credential with cloud IAM behind it -- so every field reported one BASE_ERROR and the golden diverged for a reason that says nothing about the engine. The invariant suite already excludes the same config from its cloud run. A type now declares "local_only: " in its value library and is skipped on cloud; its local golden stands, since a cloud run neither confirms nor contradicts it. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 5 +++++ bundle/direct/tests/fields_test.go | 3 +++ .../testdata/fields/external_locations.yml | 7 +++++++ bundle/direct/tests/values_test.go | 19 +++++++++++++++---- 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 bundle/direct/tests/testdata/fields/external_locations.yml diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index 0ac851ee035..192375a998f 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -102,6 +102,11 @@ Every resource type runs everywhere — local and cloud alike — so most types at all. A field with no entry gets two values for its Go kind, which is enough to see a value-to-value move on top of add and remove. +A type that cannot run against a real workspace at all declares `local_only: ` and +is skipped on cloud — an external location needs a storage credential with cloud IAM behind +it, and Lakebase and Postgres resources are not available in every cloud. This mirrors the +per-config cloud exclusions in `acceptance/bundle/invariant/test.toml`. + A `skip` key may be a pattern (`aliases[*].id`), matched the way the planner matches its own field rules, and naming a block skips everything beneath it. diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 2dbff41368b..a6f05e4c16a 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -86,6 +86,9 @@ func TestFields(t *testing.T) { fv, err := loadFieldValues(resourceType) require.NoError(t, err) + if fv.localOnly != "" && isCloud() { + t.Skipf("local only: %s", fv.localOnly) + } // A testserver per type keeps the parallel runs from sharing workspace // state. On cloud this is the same real workspace either way. client := newClient(t) diff --git a/bundle/direct/tests/testdata/fields/external_locations.yml b/bundle/direct/tests/testdata/fields/external_locations.yml new file mode 100644 index 00000000000..f699b63cd02 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/external_locations.yml @@ -0,0 +1,7 @@ +# Value library for external locations. See ../../README.md. + +# An external location points at cloud storage through a storage credential, which needs +# cloud IAM set up for the workspace. The corpus config names a credential the mock server +# invents, so the whole type is local-only -- the same reason the invariant suite excludes +# this config from its cloud run. +local_only: needs a storage credential with cloud IAM behind it diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index a657774af39..47cf55991a7 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -51,6 +51,15 @@ type fieldValues struct { // Keep path-valued fields out of it: it is merged after the mutator pipeline has run, // so nothing here goes through path translation. base any + + // localOnly is the reason this resource type cannot be driven against a real workspace: + // it needs workspace or cloud state the suite does not provision (a storage credential + // with IAM behind it), or the service is not available in every cloud. Mirrors the + // per-config cloud exclusions in acceptance/bundle/invariant/test.toml. + // + // Such a type is skipped on cloud rather than reported: the local golden stands, and a + // cloud run neither confirms nor contradicts it. + localOnly string } // fieldsDir holds the per-resource-type value libraries. @@ -132,7 +141,7 @@ var cliManagedFields = map[string]string{ // loadFieldValues reads testdata/fields/.yml. func loadFieldValues(resourceType string) (*fieldValues, error) { - fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, base: nil} + fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, base: nil, localOnly: ""} maps.Copy(fv.skip, cliManagedFields) path := filepath.Join(fieldsDir, resourceType+".yml") @@ -145,9 +154,10 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { } var file struct { - Skip map[string]string `yaml:"skip"` - Fields map[string][]any `yaml:"fields"` - Base any `yaml:"base"` + Skip map[string]string `yaml:"skip"` + Fields map[string][]any `yaml:"fields"` + Base any `yaml:"base"` + LocalOnly string `yaml:"local_only"` } if err := yaml.Unmarshal(data, &file); err != nil { return nil, fmt.Errorf("%s: %w", path, err) @@ -155,6 +165,7 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { maps.Copy(fv.skip, file.Skip) maps.Copy(fv.fields, file.Fields) fv.base = file.Base + fv.localOnly = file.LocalOnly return fv, nil } From fc82241a664819b7e90106be15c5ca2c7582cdb5 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Sun, 30 Aug 2026 23:34:34 +0200 Subject: [PATCH 023/118] Field catalog: create the resource path, and template the seeded values Two fixes from the AWS comparison, both about the suite rather than the engine. An alert or dashboard is created inside ${workspace.resource_path} and the backend 404s on a missing parent, which is why a real deploy runs deploy.ResourcePathMkdir first. This suite plans and applies directly, so it now runs that step too -- otherwise the whole type reported one BASE_ERROR that said nothing about any field. A seeded value naming tester@databricks.com only exists on the fake server, so the base fragment is now expanded with the same $VARS the corpus configs use. Also: a base error's evidence went through firstError, which truncates to 140 characters -- so the full report, whose whole purpose is to carry the backend's own words, showed a cut-off message. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 4 ++ bundle/direct/tests/fields_test.go | 6 ++- bundle/direct/tests/harness_test.go | 42 ++++++++++++++++++- .../direct/tests/testdata/fields/alerts.yml | 2 +- .../direct/tests/testdata/fields/clusters.yml | 2 +- .../testdata/fields/database_instances.yml | 4 ++ bundle/direct/tests/testdata/fields/jobs.yml | 4 +- .../fields/model_serving_endpoints.yml | 4 +- .../tests/testdata/fields/pipelines.yml | 4 +- .../testdata/fields/postgres_projects.yml | 4 ++ .../fields/postgres_synced_tables.yml | 4 ++ bundle/direct/tests/values_test.go | 16 +++++-- 12 files changed, 80 insertions(+), 16 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index 192375a998f..0915d42d991 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -110,6 +110,10 @@ per-config cloud exclusions in `acceptance/bundle/invariant/test.toml`. A `skip` key may be a pattern (`aliases[*].id`), matched the way the planner matches its own field rules, and naming a block skips everything beneath it. +`base` is expanded with the same `$VARS` the corpus configs use — `$CURRENT_USER_NAME`, +`$UNIQUE_NAME`, `$NODE_TYPE_ID` and the rest — so a seeded value can name the workspace's +own user rather than a placeholder only the fake server knows. + `skip` is for a field no single-field edit can exercise — one that only validates as part of a set (a job's `git_source`), or whose change is correct but ruinously slow (an app rename waits out an asynchronous delete). Each entry needs a reason, and shows up in diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index a6f05e4c16a..55d09216da7 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -314,7 +314,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // newBaseline builds a harness and deploys the config as written. func newBaseline(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues) (*bundleHarness, error) { - h, err := newHarness(t, ctx, client, user, cfg.name, uniqueName(), fv.base) + h, err := newHarness(t, ctx, client, user, cfg.name, uniqueName(), fv.baseYAML) if err != nil { return nil, err } @@ -322,7 +322,9 @@ func newBaseline(t *testing.T, ctx context.Context, client *databricks.Workspace action, diags := h.deploy() if diags.HasError() { - return nil, errors.New(firstError(diags)) + // The whole diagnostics, not firstError's one-line form: the caller records this as + // evidence in the full report, where the point is to read the backend's own words. + return nil, errors.New(allErrors(diags)) } if action != deployplan.Create { return nil, fmt.Errorf("expected create, got %s", action) diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index 911a8eda642..9caa7194965 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -15,6 +15,7 @@ import ( "github.com/databricks/cli/bundle" bundleconfig "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/deploy" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct" "github.com/databricks/cli/bundle/direct/dstate" @@ -33,6 +34,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/iam" "github.com/google/uuid" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" ) // bundleHarness drives the direct engine over one bundle config in-process. Nothing @@ -154,7 +156,7 @@ func maxWaitSeconds() string { // built is usually the environment (an expired token, a workspace that rejects the // config), and the run is more useful if that lands in the report as one bad verdict // than if it aborts thousands of pending observations. -func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string, base any) (*bundleHarness, error) { +func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string, baseYAML []byte) (*bundleHarness, error) { dir := t.TempDir() if err := copyDir(dataDir, dir); err != nil { return nil, err @@ -245,6 +247,16 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC return nil, err } + // A real deploy runs deploy.ResourcePathMkdir before creating resources, because an + // alert or dashboard is created inside ${workspace.resource_path} and the backend 404s + // on a missing parent. This suite plans and applies directly, so it has to do the same + // step itself -- otherwise the whole type reports one BASE_ERROR that says nothing about + // any field. + bundle.ApplySeq(ctx, b, deploy.ResourcePathMkdir()) + if diags := logdiag.FlushCollected(ctx); diags.HasError() { + return nil, fmt.Errorf("creating the resource path for %s: %s", configName, firstError(diags)) + } + harness := &bundleHarness{ t: t, ctx: ctx, @@ -254,7 +266,11 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC statePath: filepath.Join(dir, "resources.json"), } - if base != nil { + if len(baseYAML) > 0 { + base, err := expandVars(baseYAML, vars) + if err != nil { + return nil, fmt.Errorf("%s: base: %w", configName, err) + } if err := harness.edit(func(resource any) error { return seedBase(resource, base) }); err != nil { return nil, err } @@ -689,6 +705,28 @@ func (h *bundleHarness) restore(snapshot []byte) { }) } +// expandVars renders a value library's base fragment with the same variables the corpus +// configs use, so a seeded value can name the workspace's own user rather than a local +// placeholder no real workspace knows. +func expandVars(body []byte, vars map[string]string) (any, error) { + var missing string + expanded := os.Expand(string(body), func(key string) string { + value, ok := vars[key] + if !ok { + missing = key + } + return value + }) + if missing != "" { + return nil, fmt.Errorf("uses $%s, which this suite does not provide", missing) + } + var out any + if err := yaml.Unmarshal([]byte(expanded), &out); err != nil { + return nil, err + } + return out, nil +} + // seedBase folds the value library's base fragment into the resource, so the fields it // declares are present and can be varied. Unmarshalling merges: a field the fragment does // not mention keeps whatever the config gave it. diff --git a/bundle/direct/tests/testdata/fields/alerts.yml b/bundle/direct/tests/testdata/fields/alerts.yml index 0cc975c74b7..da02d263b9c 100644 --- a/bundle/direct/tests/testdata/fields/alerts.yml +++ b/bundle/direct/tests/testdata/fields/alerts.yml @@ -7,7 +7,7 @@ base: evaluation: notification: subscriptions: - - user_email: tester@databricks.com + - user_email: $CURRENT_USER_NAME parameters: - name: threshold value: "1" diff --git a/bundle/direct/tests/testdata/fields/clusters.yml b/bundle/direct/tests/testdata/fields/clusters.yml index d029196c887..7eff4a217ad 100644 --- a/bundle/direct/tests/testdata/fields/clusters.yml +++ b/bundle/direct/tests/testdata/fields/clusters.yml @@ -14,7 +14,7 @@ base: - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQtest init_scripts: - workspace: - destination: /Users/tester@databricks.com/init.sh + destination: /Users/$CURRENT_USER_NAME/init.sh - s3: destination: s3://bucket/init.sh region: us-east-1 diff --git a/bundle/direct/tests/testdata/fields/database_instances.yml b/bundle/direct/tests/testdata/fields/database_instances.yml index 6ab00f3e91c..bccfd3743b0 100644 --- a/bundle/direct/tests/testdata/fields/database_instances.yml +++ b/bundle/direct/tests/testdata/fields/database_instances.yml @@ -1,4 +1,8 @@ # Value library for database instances. See ../../README.md. + +# Lakebase v1 (database instances, catalogs and synced tables) is not available +# on every cloud, and the invariant suite excludes it from every cloud run for that reason. +local_only: Lakebase v1 is not available in every cloud base: custom_tags: - key: team diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml index 6901e4fa258..e7f31507069 100644 --- a/bundle/direct/tests/testdata/fields/jobs.yml +++ b/bundle/direct/tests/testdata/fields/jobs.yml @@ -28,7 +28,7 @@ base: spark.speculation: "true" init_scripts: - workspace: - destination: /Users/tester@databricks.com/init.sh + destination: /Users/$CURRENT_USER_NAME/init.sh environments: - environment_key: seeded spec: @@ -47,7 +47,7 @@ base: value: 3600 email_notifications: on_failure: - - tester@databricks.com + - $CURRENT_USER_NAME webhook_notifications: on_failure: - id: 00000000-0000-0000-0000-000000000000 diff --git a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml index 89fe8f6c3a8..d5e7be1b685 100644 --- a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml +++ b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml @@ -18,9 +18,9 @@ base: renewal_period: minute email_notifications: on_update_failure: - - tester@databricks.com + - $CURRENT_USER_NAME on_update_success: - - tester@databricks.com + - $CURRENT_USER_NAME tags: - key: team value: eng diff --git a/bundle/direct/tests/testdata/fields/pipelines.yml b/bundle/direct/tests/testdata/fields/pipelines.yml index ea06cb7cf54..0facd8f18b4 100644 --- a/bundle/direct/tests/testdata/fields/pipelines.yml +++ b/bundle/direct/tests/testdata/fields/pipelines.yml @@ -17,7 +17,7 @@ base: - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQtest init_scripts: - workspace: - destination: /Users/tester@databricks.com/init.sh + destination: /Users/$CURRENT_USER_NAME/init.sh - s3: destination: s3://bucket/init.sh region: us-east-1 @@ -38,7 +38,7 @@ base: - alerts: - on-update-failure email_recipients: - - tester@databricks.com + - $CURRENT_USER_NAME restart_window: start_hour: 1 time_zone_id: UTC diff --git a/bundle/direct/tests/testdata/fields/postgres_projects.yml b/bundle/direct/tests/testdata/fields/postgres_projects.yml index c1710251f0c..d83e4f20359 100644 --- a/bundle/direct/tests/testdata/fields/postgres_projects.yml +++ b/bundle/direct/tests/testdata/fields/postgres_projects.yml @@ -1,4 +1,8 @@ # Value library for postgres projects. See ../../README.md. + +# Postgres resources are only available on AWS, and the invariant suite +# excludes them from every cloud run for that reason. +local_only: Postgres resources are only available on AWS base: custom_tags: - key: team diff --git a/bundle/direct/tests/testdata/fields/postgres_synced_tables.yml b/bundle/direct/tests/testdata/fields/postgres_synced_tables.yml index 0c91e1528ba..ec61b871bc9 100644 --- a/bundle/direct/tests/testdata/fields/postgres_synced_tables.yml +++ b/bundle/direct/tests/testdata/fields/postgres_synced_tables.yml @@ -1,4 +1,8 @@ # Value library for postgres synced tables. See ../../README.md. + +# Postgres resources are only available on AWS, and the invariant suite +# excludes them from every cloud run for that reason. +local_only: Postgres resources are only available on AWS base: extra_columns: - column_name: ingested_at diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 47cf55991a7..cd0316ad2f3 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -50,7 +50,10 @@ type fieldValues struct { // // Keep path-valued fields out of it: it is merged after the mutator pipeline has run, // so nothing here goes through path translation. - base any + // + // Held as YAML rather than a parsed value because the same $VARS the corpus configs use + // are expanded into it, and those are only known once a harness exists. + baseYAML []byte // localOnly is the reason this resource type cannot be driven against a real workspace: // it needs workspace or cloud state the suite does not provision (a storage credential @@ -141,7 +144,7 @@ var cliManagedFields = map[string]string{ // loadFieldValues reads testdata/fields/.yml. func loadFieldValues(resourceType string) (*fieldValues, error) { - fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, base: nil, localOnly: ""} + fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, baseYAML: nil, localOnly: ""} maps.Copy(fv.skip, cliManagedFields) path := filepath.Join(fieldsDir, resourceType+".yml") @@ -156,7 +159,7 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { var file struct { Skip map[string]string `yaml:"skip"` Fields map[string][]any `yaml:"fields"` - Base any `yaml:"base"` + Base yaml.Node `yaml:"base"` LocalOnly string `yaml:"local_only"` } if err := yaml.Unmarshal(data, &file); err != nil { @@ -164,8 +167,13 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { } maps.Copy(fv.skip, file.Skip) maps.Copy(fv.fields, file.Fields) - fv.base = file.Base fv.localOnly = file.LocalOnly + if !file.Base.IsZero() { + fv.baseYAML, err = yaml.Marshal(&file.Base) + if err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + } return fv, nil } From 67dda8f305ae8fee1d7f0e69948a5454baed8f56 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 00:55:53 +0200 Subject: [PATCH 024/118] Field catalog: take enum values from the SDK, fix the alerts value library From the AWS comparison. An enum field was getting the generic "x" and "y", which a real backend rejects or silently ignores, so the field reported nothing about whether the engine handles a change to it -- the alerts report was full of those. Every generated SDK enum declares its own values through a Values() method, so they are read from there instead, which fixes enum fields across every resource type. The alerts library also skips what the suite cannot provision: a second warehouse or workspace folder (and the backend refuses to move an alert between folders at all), and a registered notification destination. A subscription and a run_as now name the workspace's own user. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 18 ++-- bundle/direct/tests/harness_test.go | 31 +------ .../direct/tests/testdata/fields/alerts.yml | 16 ++++ bundle/direct/tests/values_test.go | 89 ++++++++++++++----- 4 files changed, 99 insertions(+), 55 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 55d09216da7..4e7812aef3f 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -84,16 +84,22 @@ func TestFields(t *testing.T) { // slowest type then sets the wall time instead of their sum. t.Parallel() - fv, err := loadFieldValues(resourceType) - require.NoError(t, err) - if fv.localOnly != "" && isCloud() { - t.Skipf("local only: %s", fv.localOnly) - } // A testserver per type keeps the parallel runs from sharing workspace // state. On cloud this is the same real workspace either way. client := newClient(t) user := workspaceUser(t, client) + // A value library may name the workspace user or a shared test object, so it is + // rendered with the same variables as a corpus config -- minus UNIQUE_NAME, which + // belongs to one deploy and would be wrong for a value reused across rebuilds. + vars := templateVars("", user.UserName) + delete(vars, "UNIQUE_NAME") + fv, err := loadFieldValues(resourceType, vars) + require.NoError(t, err) + if fv.localOnly != "" && isCloud() { + t.Skipf("local only: %s", fv.localOnly) + } + // Outlives the field-level subtests that rebuild harnesses. ctx := t.Context() @@ -314,7 +320,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // newBaseline builds a harness and deploys the config as written. func newBaseline(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues) (*bundleHarness, error) { - h, err := newHarness(t, ctx, client, user, cfg.name, uniqueName(), fv.baseYAML) + h, err := newHarness(t, ctx, client, user, cfg.name, uniqueName(), fv.base) if err != nil { return nil, err } diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index 9caa7194965..4dccd75b254 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -34,7 +34,6 @@ import ( "github.com/databricks/databricks-sdk-go/service/iam" "github.com/google/uuid" "github.com/stretchr/testify/require" - "go.yaml.in/yaml/v3" ) // bundleHarness drives the direct engine over one bundle config in-process. Nothing @@ -156,7 +155,7 @@ func maxWaitSeconds() string { // built is usually the environment (an expired token, a workspace that rejects the // config), and the run is more useful if that lands in the report as one bad verdict // than if it aborts thousands of pending observations. -func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string, baseYAML []byte) (*bundleHarness, error) { +func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string, base any) (*bundleHarness, error) { dir := t.TempDir() if err := copyDir(dataDir, dir); err != nil { return nil, err @@ -266,11 +265,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC statePath: filepath.Join(dir, "resources.json"), } - if len(baseYAML) > 0 { - base, err := expandVars(baseYAML, vars) - if err != nil { - return nil, fmt.Errorf("%s: base: %w", configName, err) - } + if base != nil { if err := harness.edit(func(resource any) error { return seedBase(resource, base) }); err != nil { return nil, err } @@ -705,28 +700,6 @@ func (h *bundleHarness) restore(snapshot []byte) { }) } -// expandVars renders a value library's base fragment with the same variables the corpus -// configs use, so a seeded value can name the workspace's own user rather than a local -// placeholder no real workspace knows. -func expandVars(body []byte, vars map[string]string) (any, error) { - var missing string - expanded := os.Expand(string(body), func(key string) string { - value, ok := vars[key] - if !ok { - missing = key - } - return value - }) - if missing != "" { - return nil, fmt.Errorf("uses $%s, which this suite does not provide", missing) - } - var out any - if err := yaml.Unmarshal([]byte(expanded), &out); err != nil { - return nil, err - } - return out, nil -} - // seedBase folds the value library's base fragment into the resource, so the fields it // declares are present and can be varied. Unmarshalling merges: a field the fragment does // not mention keeps whatever the config gave it. diff --git a/bundle/direct/tests/testdata/fields/alerts.yml b/bundle/direct/tests/testdata/fields/alerts.yml index da02d263b9c..a8de07d703b 100644 --- a/bundle/direct/tests/testdata/fields/alerts.yml +++ b/bundle/direct/tests/testdata/fields/alerts.yml @@ -11,3 +11,19 @@ base: parameters: - name: threshold value: "1" + +skip: + # Must name an existing SQL warehouse, and alerts require one, so there is no absent + # transition either -- a value-to-value move would need a second warehouse to exist. + warehouse_id: needs a second SQL warehouse to exist + # Must name an existing workspace folder, and the backend refuses to move an alert + # between folders at all ("Updating parent path is not supported"). + parent_path: needs a second workspace folder; the backend refuses to move an alert + # Must be the id of a notification destination registered with the workspace. + evaluation.notification.subscriptions[*].destination_id: needs a notification destination + +fields: + # A subscription, and a run_as, has to name a principal the workspace knows. + evaluation.notification.subscriptions[*].user_email: [$CURRENT_USER_NAME] + run_as.user_name: [$CURRENT_USER_NAME] + run_as_user_name: [$CURRENT_USER_NAME] diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index cd0316ad2f3..a13ef936070 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -51,9 +51,7 @@ type fieldValues struct { // Keep path-valued fields out of it: it is merged after the mutator pipeline has run, // so nothing here goes through path translation. // - // Held as YAML rather than a parsed value because the same $VARS the corpus configs use - // are expanded into it, and those are only known once a harness exists. - baseYAML []byte + base any // localOnly is the reason this resource type cannot be driven against a real workspace: // it needs workspace or cloud state the suite does not provision (a storage credential @@ -142,9 +140,11 @@ var cliManagedFields = map[string]string{ "lifecycle.prevent_destroy": "acted on by the bundle, never sent to any API; gates destroy", } -// loadFieldValues reads testdata/fields/.yml. -func loadFieldValues(resourceType string) (*fieldValues, error) { - fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, baseYAML: nil, localOnly: ""} +// loadFieldValues reads testdata/fields/.yml, expanding the same $VARS the +// corpus configs use so a value can name the workspace's own user rather than a placeholder +// only the fake server knows. +func loadFieldValues(resourceType string, vars map[string]string) (*fieldValues, error) { + fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, base: nil, localOnly: ""} maps.Copy(fv.skip, cliManagedFields) path := filepath.Join(fieldsDir, resourceType+".yml") @@ -156,24 +156,31 @@ func loadFieldValues(resourceType string) (*fieldValues, error) { return nil, err } + var missing string + expanded := os.Expand(string(data), func(key string) string { + value, ok := vars[key] + if !ok { + missing = key + } + return value + }) + if missing != "" { + return nil, fmt.Errorf("%s uses $%s, which this suite does not provide here", path, missing) + } + var file struct { Skip map[string]string `yaml:"skip"` Fields map[string][]any `yaml:"fields"` - Base yaml.Node `yaml:"base"` + Base any `yaml:"base"` LocalOnly string `yaml:"local_only"` } - if err := yaml.Unmarshal(data, &file); err != nil { + if err := yaml.Unmarshal([]byte(expanded), &file); err != nil { return nil, fmt.Errorf("%s: %w", path, err) } maps.Copy(fv.skip, file.Skip) maps.Copy(fv.fields, file.Fields) + fv.base = file.Base fv.localOnly = file.LocalOnly - if !file.Base.IsZero() { - fv.baseYAML, err = yaml.Marshal(&file.Base) - if err != nil { - return nil, fmt.Errorf("%s: %w", path, err) - } - } return fv, nil } @@ -195,12 +202,54 @@ func isContainer(kind reflect.Kind) bool { } } -// defaultValues per Go kind. Two values per kind is enough to observe a value->value -// transition on top of add and remove; more would multiply the matrix without testing a -// different code path. +// defaultValues returns the values to try for a leaf field. Two per field is enough to +// observe a value-to-value transition on top of add and remove; more would multiply the +// matrix without testing a different code path. +func defaultValues(typ reflect.Type) []any { + if values := enumValues(typ); values != nil { + return values + } + return kindValues(typ.Kind()) +} + +// enumValues returns two values of an SDK enum, which declares its own: every generated +// enum type has a Values() method on its pointer receiver. Without this an enum field gets +// the generic "x" and "y", which a real backend rejects or silently ignores -- so the field +// reports nothing about whether the engine handles a change to it. +// +// The values are sorted because the SDK documents no order, and the report is a golden. +func enumValues(typ reflect.Type) []any { + if typ.Kind() != reflect.String || typ == reflect.TypeFor[string]() { + return nil + } + method, ok := reflect.PointerTo(typ).MethodByName("Values") + if !ok || method.Type.NumIn() != 1 || method.Type.NumOut() != 1 { + return nil + } + result := method.Func.Call([]reflect.Value{reflect.New(typ)})[0] + if result.Kind() != reflect.Slice || result.Type().Elem() != typ { + return nil + } + + all := make([]string, 0, result.Len()) + for i := range result.Len() { + if value := result.Index(i).String(); value != "" { + all = append(all, value) + } + } + slices.Sort(all) + if len(all) < 2 { + // One value cannot show a value-to-value move, and add and remove alone would say + // nothing an enum-specific case does not already cover. + return nil + } + return []any{reflect.ValueOf(all[0]).Convert(typ).Interface(), reflect.ValueOf(all[1]).Convert(typ).Interface()} +} + +// kindValues are the fallback values for a plain Go kind. // //nolint:exhaustive // the default branch covers every kind without a generic value -func defaultValues(kind reflect.Kind) []any { +func kindValues(kind reflect.Kind) []any { switch kind { case reflect.Bool: return []any{false, true} @@ -427,7 +476,7 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue if !isWildcard { values := fv.fields[path] if values == nil { - values = defaultValues(typ.Kind()) + values = defaultValues(typ) } add(path, typ.Kind(), values) return false @@ -450,7 +499,7 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue // Explicit values are keyed by the pattern, since an index is incidental. values := fv.fields[path] if values == nil { - values = defaultValues(typ.Kind()) + values = defaultValues(typ) } add(c, typ.Kind(), values) } From c607028f069d9239d09a263ada468bdb1bab6704 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 00:58:33 +0200 Subject: [PATCH 025/118] Field catalog: deep-copy container values, release read-only plan contexts Both from the adversarial review. containerValues recorded a slice or map with its elements shallow-copied, so the pointers inside an element were shared with the live resource: editing a field under clusters.init_scripts[0].workspace then reached into the value saved for the init_scripts container's own transitions, and that container was tested against corrupted data. A plan the caller never applies was dropping its pendingApply on the floor, leaking the operation context and its ten-minute timer -- thousands of them on a cloud run. Such a caller now goes through readPlan, and the two paths that plan and then return early cancel explicitly. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 8 +++++--- bundle/direct/tests/harness_test.go | 11 ++++++++++- bundle/direct/tests/values_test.go | 30 ++++++++++++++++------------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 4e7812aef3f..60e0771f87b 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -235,12 +235,14 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans pending, plan, diags := h.plan() if diags.HasError() { + pending.cancel() res.verdict = verdictPlanError res.detail = firstError(diags) return res } action := nodeAction(plan, h.node) if action == deployplan.Skip { + pending.cancel() res.verdict, res.detail = explainSkip(plan, h.node, path) return res } @@ -259,7 +261,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans return res } - _, after, diags := h.plan() + after, diags := h.readPlan() if diags.HasError() { res.verdict = verdictPlanError res.detail = firstError(diags) @@ -273,7 +275,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // one. If the value has appeared by then the write did land and the first read was // behind; if it still has not, the field really was ignored. if !baseline[path] && wasIgnored(plan, after, h.node, path) { - _, second, diags := h.plan() + second, diags := h.readPlan() if diags.HasError() { res.verdict = verdictPlanError res.detail = firstError(diags) @@ -471,7 +473,7 @@ func checkDeclaredInert(res result, rules []dresources.FieldRule) result { // baselineDrift returns the fields the resource already wants to change with the config // exactly as deployed. func baselineDrift(h *bundleHarness) map[string]bool { - _, plan, diags := h.plan() + plan, diags := h.readPlan() if diags.HasError() || plan == nil { return nil } diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index 4dccd75b254..1ef906963b2 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -317,6 +317,14 @@ func (h *bundleHarness) plan() (*pendingApply, *deployplan.Plan, diag.Diagnostic return &pendingApply{ctx: ctx, cancel: cancel, db: db}, plan, diags } +// readPlan is plan for a caller that will not apply: the operation context is released +// straight away rather than left holding a ten-minute timer until the test ends. +func (h *bundleHarness) readPlan() (*deployplan.Plan, diag.Diagnostics) { + pending, plan, diags := h.plan() + pending.cancel() + return plan, diags +} + // pendingApply carries a planned DeploymentBundle together with the context it was // planned under, so the apply reports into the same isolated diagnostics sink. type pendingApply struct { @@ -348,6 +356,7 @@ func (h *bundleHarness) apply(p *pendingApply, plan *deployplan.Plan) diag.Diagn func (h *bundleHarness) deploy() (deployplan.ActionType, diag.Diagnostics) { pending, plan, diags := h.plan() if diags.HasError() { + pending.cancel() return deployplan.Undefined, diags } return nodeAction(plan, h.node), h.apply(pending, plan) @@ -766,7 +775,7 @@ func (h *bundleHarness) converged() bool { if _, diags := h.deploy(); diags.HasError() { return false } - _, plan, diags := h.plan() + plan, diags := h.readPlan() return !diags.HasError() && plan != nil && !hasDrift(plan) } diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index a13ef936070..40d75750349 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -538,35 +538,39 @@ func containerValues(resource any, path string) []any { return nil } - value := reflect.ValueOf(current) - var trimmed reflect.Value + // Both values are deep copies. A shallow one would share the pointers inside an + // element -- a task's notebook_task, an init script's workspace block -- and a later + // edit to a field under that element would reach into a value recorded here. + full, err := clone(current) + if err != nil { + return nil + } + trimmed, err := clone(current) + if err != nil { + return nil + } + + value := reflect.ValueOf(trimmed) switch value.Kind() { case reflect.Slice: if value.Len() == 0 { return nil } - trimmed = reflect.MakeSlice(value.Type(), 0, value.Len()-1) - trimmed = reflect.AppendSlice(trimmed, value.Slice(0, value.Len()-1)) + trimmed = value.Slice(0, value.Len()-1).Interface() case reflect.Map: keys := sortedMapKeys(value) if len(keys) == 0 { return nil } - trimmed = reflect.MakeMap(value.Type()) - for _, key := range keys[:len(keys)-1] { - trimmed.SetMapIndex(key, value.MapIndex(key)) - } + value.SetMapIndex(keys[len(keys)-1], reflect.Value{}) + trimmed = value.Interface() default: return nil } - full, err := clone(current) - if err != nil { - return nil - } - return []any{full, trimmed.Interface()} + return []any{full, trimmed} } // clone deep-copies a value through the JSON representation the API uses, which the SDK From 0b1d269fb82907056186466098aaa7e4b6fa3b88 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 09:20:18 +0200 Subject: [PATCH 026/118] Field catalog: verify the starting value actually took, add START_NOT_REACHED From the adversarial review: a transition assumed its setup deploy established the starting value, checking only that the API did not error. When the API accepts a write and ignores it -- clearing a field it refuses to clear -- the field kept its old value and the move was then recorded under a label describing something that never happened: "absent to 168" while the remote still held 720. The starting value is now confirmed before the move under test. A transition that cannot be set up is retried once on a fresh resource, since a resource created without the field genuinely starts absent, and only then recorded as START_NOT_REACHED -- its own verdict, because unlike BASE_ERROR nothing was rejected. Also from the review: isRequired compared a concrete path against the generated required-field data, which is keyed by pattern, so a required field inside a slice (tasks[*].task_key) looked optional and got an absent transition no user can deploy. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 1 + bundle/direct/tests/fields_test.go | 24 +++++++++++++++++++ bundle/direct/tests/output/apps.txt | 15 ++++-------- bundle/direct/tests/output/catalogs.txt | 8 ++++--- bundle/direct/tests/output/dashboards.txt | 14 ++++++----- .../tests/output/database_instances.txt | 6 +++-- bundle/direct/tests/output/genie_spaces.txt | 17 +++++++------ bundle/direct/tests/output/instance_pools.txt | 2 ++ bundle/direct/tests/output/pipelines.txt | 2 ++ .../direct/tests/output/registered_models.txt | 12 ++++++---- bundle/direct/tests/output/schemas.txt | 8 ++++--- bundle/direct/tests/output/sql_warehouses.txt | 2 ++ bundle/direct/tests/report_test.go | 14 +++++++++-- bundle/direct/tests/values_test.go | 8 ++++--- bundle/direct/tests/values_unit_test.go | 12 ++++++++++ 15 files changed, 104 insertions(+), 41 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index 0915d42d991..da00dd43eb6 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -58,6 +58,7 @@ API sees. | `PLAN_ERROR` | planning failed | | `UNSETTABLE` | the value could not be written into the config at all | | `BASE_ERROR` | the transition's starting point would not deploy, so nothing was observed | +| `START_NOT_REACHED` | the starting value deployed without error but the field did not end up holding it, so the move under test could not be set up — a field the API refuses to clear cannot start from `absent`. Retried once on a fresh resource before being recorded | | `NOT_IN_STATE` | the field exists in bundle config but not in the state type, so it never reaches the API | `POST_DEPLOY_DRIFT` and `SUPPRESSED` are where the interesting gaps are. `NOT_OBSERVABLE` diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 60e0771f87b..6b44891edc6 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -167,6 +167,18 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl t.Run(tr.label(), func(t *testing.T) { reuse := deployedKnown && valueLabel(deployed) == valueLabel(tr.from) res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline) + + // A starting value the deployed resource will not take -- typically a + // field the API refuses to clear -- is not a dead end: a fresh resource + // created without the field genuinely starts absent. Rebuild and try the + // transition once more, so it is observed rather than written off. + if res.verdict == verdictStartNotReached { + if rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h); err == nil { + h, base = rebuilt, rebuilt.snapshot() + res = runTransition(t, h, cfg.name, f.path, tr, false, baseline) + } + } + res = checkDeclaredInert(res, ignoredLocally) rep.add(res) @@ -224,6 +236,18 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.evidence = withContext("error from the deploy:", allErrors(diags)) return res } + // A deploy the backend accepted does not mean the field now holds "from": a write it + // ignores leaves the old value there, and the transition below would then be reported + // under a label that is not what happened -- "absent to 168" while the remote still + // holds 720. Cheaper to check than to reason about afterwards. + if plan, diags := h.readPlan(); !diags.HasError() { + if change, ok := planChange(plan, h.node, path); ok && change.Action != deployplan.Skip && !baseline[path] { + res.verdict = verdictStartNotReached + res.detail = path + res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) + return res + } + } } // Now the change under test. diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 014226353f9..d626e03481f 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -1,7 +1,6 @@ === app.yml.tmpl budget_policy_id x absent UPDATE_IGNORED budget_policy_id budget_policy_id y absent UPDATE_IGNORED budget_policy_id -compute_max_instances 1 absent UPDATE_IGNORED compute_max_instances compute_max_instances 2 absent UPDATE_IGNORED compute_max_instances compute_min_instances 1 absent UPDATE_IGNORED compute_min_instances compute_min_instances 2 absent UPDATE_IGNORED compute_min_instances @@ -17,11 +16,7 @@ config.env absent len0 SUPPRESSED n config.env len0 absent SUPPRESSED no active deployment config.env len1 absent SUPPRESSED no active deployment config.env len1 len0 SUPPRESSED no active deployment -config.env[0].name absent x SUPPRESSED no active deployment -config.env[0].name absent y SUPPRESSED no active deployment -config.env[0].name x absent SUPPRESSED no active deployment config.env[0].name x y SUPPRESSED no active deployment -config.env[0].name y absent SUPPRESSED no active deployment config.env[0].name y x SUPPRESSED no active deployment config.env[0].value absent x SUPPRESSED no active deployment config.env[0].value absent y SUPPRESSED no active deployment @@ -34,18 +29,18 @@ config.env[0].value_from absent y SUPPRESSED n config.env[0].value_from x y SUPPRESSED no active deployment config.env[0].value_from y x SUPPRESSED no active deployment description x absent UPDATE_IGNORED description -description y absent UPDATE_IGNORED description git_repository.auto_deploy true absent UPDATE_IGNORED git_repository.auto_deploy git_repository.caller_credential_id 1 absent UPDATE_IGNORED git_repository.caller_credential_id git_repository.caller_credential_id 2 absent UPDATE_IGNORED git_repository.caller_credential_id -resources absent len0 UPDATE_IGNORED resources -resources len0 absent UPDATE_IGNORED resources +resources absent len0 START_NOT_REACHED resources +resources len0 len1 START_NOT_REACHED resources resources len1 absent UPDATE_IGNORED resources resources len1 len0 UPDATE_IGNORED resources -resources[0].app.name x absent UPDATE_IGNORED resources[0].app.name resources[0].app.name y absent UPDATE_IGNORED resources[0].app.name resources[0].app.permission x absent UPDATE_IGNORED resources[0].app.permission resources[0].app.permission y absent UPDATE_IGNORED resources[0].app.permission +resources[0].description absent x START_NOT_REACHED resources[0].description +resources[0].description absent y START_NOT_REACHED resources[0].description resources[0].description x absent UPDATE_IGNORED resources[0].description resources[0].description y absent UPDATE_IGNORED resources[0].description resources[0].postgres.branch x absent UPDATE_IGNORED resources[0].postgres.branch @@ -53,12 +48,10 @@ resources[0].postgres.branch y absent UPDATE_IGNORE resources[0].postgres.database x absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.database y absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.permission x absent UPDATE_IGNORED resources[0].postgres.permission -resources[0].postgres.permission y absent UPDATE_IGNORED resources[0].postgres.permission source_code_path absent x SUPPRESSED no active deployment source_code_path absent y SUPPRESSED no active deployment source_code_path x absent SUPPRESSED no active deployment source_code_path x y SUPPRESSED no active deployment source_code_path y absent SUPPRESSED no active deployment source_code_path y x SUPPRESSED no active deployment -usage_policy_id x absent UPDATE_IGNORED usage_policy_id usage_policy_id y absent UPDATE_IGNORED usage_policy_id diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index b73ab90201b..becbdecc22a 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -1,9 +1,11 @@ === catalog.yml.tmpl custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours -properties absent keys0 UPDATE_IGNORED properties -properties keys0 absent UPDATE_IGNORED properties +properties absent keys0 START_NOT_REACHED properties +properties keys0 absent START_NOT_REACHED properties properties keys1 absent UPDATE_IGNORED properties -properties keys1 keys0 UPDATE_IGNORED properties +properties keys1 keys0 POST_DEPLOY_DRIFT properties,properties['team'] +properties['team'] absent x START_NOT_REACHED properties['team'] +properties['team'] absent y START_NOT_REACHED properties['team'] properties['team'] x absent UPDATE_IGNORED properties['team'] properties['team'] y absent UPDATE_IGNORED properties['team'] diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index bd12562eb6f..83f663085d2 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -1,4 +1,6 @@ === dashboard.yml.tmpl +display_name absent x START_NOT_REACHED display_name +display_name absent y START_NOT_REACHED display_name display_name x absent UPDATE_IGNORED display_name display_name y absent UPDATE_IGNORED display_name etag absent x SUPPRESSED custom @@ -7,9 +9,9 @@ etag x absent SUPPRESSED c etag x y SUPPRESSED custom etag y absent SUPPRESSED custom etag y x SUPPRESSED custom -parent_path absent x POST_DEPLOY_DRIFT parent_path -parent_path absent y POST_DEPLOY_DRIFT parent_path -parent_path x absent POST_DEPLOY_DRIFT parent_path -parent_path x y POST_DEPLOY_DRIFT parent_path -parent_path y absent POST_DEPLOY_DRIFT parent_path -parent_path y x POST_DEPLOY_DRIFT parent_path +parent_path absent x START_NOT_REACHED parent_path +parent_path absent y START_NOT_REACHED parent_path +parent_path x absent START_NOT_REACHED parent_path +parent_path x y START_NOT_REACHED parent_path +parent_path y absent START_NOT_REACHED parent_path +parent_path y x START_NOT_REACHED parent_path diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index 53a513f74f0..b551721c2e0 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -1,8 +1,10 @@ === database_instance.yml.tmpl capacity absent x UPDATE_IGNORED capacity capacity absent y UPDATE_IGNORED capacity -capacity x y UPDATE_IGNORED capacity -capacity y x UPDATE_IGNORED capacity +capacity x absent START_NOT_REACHED capacity +capacity x y START_NOT_REACHED capacity +capacity y absent START_NOT_REACHED capacity +capacity y x POST_DEPLOY_DRIFT capacity custom_tags absent len0 COLLATERAL_DRIFT capacity custom_tags absent len1 COLLATERAL_DRIFT capacity custom_tags len0 absent COLLATERAL_DRIFT capacity diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index 263b96b0cf9..93c657e957d 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,13 +1,16 @@ === genie_space.yml.tmpl description x absent UPDATE_IGNORED description -description y absent UPDATE_IGNORED description -parent_path absent x POST_DEPLOY_DRIFT parent_path -parent_path absent y POST_DEPLOY_DRIFT parent_path -parent_path x absent UPDATE_IGNORED parent_path -parent_path x y POST_DEPLOY_DRIFT parent_path -parent_path y absent UPDATE_IGNORED parent_path -parent_path y x POST_DEPLOY_DRIFT parent_path +parent_path absent x START_NOT_REACHED parent_path +parent_path absent y START_NOT_REACHED parent_path +parent_path x absent START_NOT_REACHED parent_path +parent_path x y START_NOT_REACHED parent_path +parent_path y absent START_NOT_REACHED parent_path +parent_path y x START_NOT_REACHED parent_path +title absent x START_NOT_REACHED title +title absent y START_NOT_REACHED title title x absent UPDATE_IGNORED title title y absent UPDATE_IGNORED title +warehouse_id absent x START_NOT_REACHED warehouse_id +warehouse_id absent y START_NOT_REACHED warehouse_id warehouse_id x absent UPDATE_IGNORED warehouse_id warehouse_id y absent UPDATE_IGNORED warehouse_id diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt index 31a6086ddf2..aeb3ac68bcd 100644 --- a/bundle/direct/tests/output/instance_pools.txt +++ b/bundle/direct/tests/output/instance_pools.txt @@ -1,3 +1,5 @@ === instance_pool.yml.tmpl enable_elastic_disk absent true UPDATE_IGNORED enable_elastic_disk enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk +enable_elastic_disk true absent START_NOT_REACHED enable_elastic_disk +enable_elastic_disk true false START_NOT_REACHED enable_elastic_disk diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 7efd1f9f305..c1c3cb17904 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -1,3 +1,5 @@ === pipeline.yml.tmpl dry_run absent true UPDATE_IGNORED dry_run dry_run false true UPDATE_IGNORED dry_run +dry_run true absent START_NOT_REACHED dry_run +dry_run true false START_NOT_REACHED dry_run diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 995ac464d91..7ed3b88132f 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -3,15 +3,19 @@ catalog_name absent main BASE_ERROR c catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) full_name absent x UPDATE_IGNORED full_name full_name absent y UPDATE_IGNORED full_name -full_name x y UPDATE_IGNORED full_name -full_name y x UPDATE_IGNORED full_name +full_name x absent START_NOT_REACHED full_name +full_name x y START_NOT_REACHED full_name +full_name y absent START_NOT_REACHED full_name +full_name y x START_NOT_REACHED full_name name absent x BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name absent y BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name x absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name y absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) owner absent x UPDATE_IGNORED owner owner absent y UPDATE_IGNORED owner -owner x y UPDATE_IGNORED owner -owner y x UPDATE_IGNORED owner +owner x absent START_NOT_REACHED owner +owner x y START_NOT_REACHED owner +owner y absent START_NOT_REACHED owner +owner y x START_NOT_REACHED owner schema_name absent default BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) schema_name default absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index 721441ebfcc..e3c8d8a64c5 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -1,9 +1,11 @@ === schema.yml.tmpl custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours -properties absent keys0 UPDATE_IGNORED properties -properties keys0 absent UPDATE_IGNORED properties +properties absent keys0 START_NOT_REACHED properties +properties keys0 absent START_NOT_REACHED properties properties keys1 absent UPDATE_IGNORED properties -properties keys1 keys0 UPDATE_IGNORED properties +properties keys1 keys0 POST_DEPLOY_DRIFT properties,properties['team'] +properties['team'] absent x START_NOT_REACHED properties['team'] +properties['team'] absent y START_NOT_REACHED properties['team'] properties['team'] x absent UPDATE_IGNORED properties['team'] properties['team'] y absent UPDATE_IGNORED properties['team'] diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index 70a9c6c9e5f..10ff5d11c53 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -1,3 +1,5 @@ === sql_warehouse.yml.tmpl +name absent x START_NOT_REACHED name +name absent y START_NOT_REACHED name name x absent POST_DEPLOY_DRIFT name name y absent POST_DEPLOY_DRIFT name diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index b6518dda242..f357d51d533 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -90,6 +90,13 @@ const ( // The transition's starting value could not be deployed, so the transition // itself was never observed. verdictBaseError verdict = "BASE_ERROR" + // The starting value deployed without error but the field did not end up holding it, so + // the transition under test could not be set up. A field the API refuses to clear is the + // usual case: nothing can start from absent once the field has ever been set, and a fresh + // resource whose base config declares the field cannot start from absent either. Reported + // rather than tested from the wrong starting point, which would label the result a move + // that never happened. + verdictStartNotReached verdict = "START_NOT_REACHED" ) // leavesResourceUsable reports whether the deployed resource is still in a known-good @@ -101,9 +108,12 @@ func (v verdict) leavesResourceUsable() bool { verdictSkipped, verdictInertConfirmed, verdictInertViolated: return true case verdictDrift, verdictCollateral, verdictDriftChild, verdictUpdateIgnored, - verdictStaleRead, verdictBaselineDrift: + verdictStaleRead, verdictBaselineDrift, verdictStartNotReached: // The resource exists and still matches what the engine last sent; only the one // field did not stick. The next transition re-deploys the field anyway. + // + // START_NOT_REACHED is here for a second reason: the caller has already retried it on + // a fresh resource, so rebuilding again would buy nothing. return true default: return false @@ -288,7 +298,7 @@ func (r *report) render(problemsOnly bool) string { verdictBaselineDrift, verdictStaleRead, verdictUpdateIgnored, verdictDrift, verdictCollateral, verdictDriftChild, verdictBackendError, verdictDeployError, verdictTimeout, verdictPlanError, - verdictUnsettable, verdictSkipped, verdictBaseError, + verdictUnsettable, verdictSkipped, verdictBaseError, verdictStartNotReached, } { if counts[v] > 0 { fmt.Fprintf(&sb, "%-14s %d\n", v, counts[v]) diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 40d75750349..1a93f438026 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -405,9 +405,11 @@ func (fv *fieldValues) skipReason(path string) (string, bool) { } func isRequired(resourceType, path string) bool { - parent, name := "", path - if i := strings.LastIndex(path, "."); i >= 0 { - parent, name = path[:i], path[i+1:] + // The generated keys are patterns, so an index has to become a wildcard first: + // "tasks[0].task_key" is declared under "resources.jobs.*.tasks[*]". + parent, name := "", patternOf(path) + if i := strings.LastIndex(name, "."); i >= 0 { + parent, name = name[:i], name[i+1:] } return slices.Contains(requiredFields(resourceType, parent), name) } diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go index 9b299ad40c6..255ad8b5b72 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/tests/values_unit_test.go @@ -165,3 +165,15 @@ func TestSkipReasonMatchesPatterns(t *testing.T) { }) } } + +// The generated required-field data is keyed by pattern, so a concrete index has to be +// turned back into a wildcard before the lookup -- otherwise a required field inside a +// slice looks optional and gets an "absent" transition a user cannot deploy. +func TestIsRequiredInsideSlice(t *testing.T) { + assert.True(t, isRequired("jobs", "tasks[0].task_key")) + assert.True(t, isRequired("jobs", "tasks[3].task_key")) + assert.False(t, isRequired("jobs", "tasks[0].description")) + + assert.True(t, isRequired("alerts", "display_name")) + assert.False(t, isRequired("alerts", "custom_summary")) +} From 32e2bb11acb21b50e4b6128b1b0df247a286f572 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 09:23:23 +0200 Subject: [PATCH 027/118] Field catalog: commit the verdict counts, require the golden to exist Both from the adversarial review. The committed report listed only findings, so a change in passing behaviour was invisible: a field that starts being recreated instead of updated moved one OK to OK_RECREATE and nothing in the compared file mentioned either. That matters most for the cloud run, which is held to the same golden -- a real workspace behaving unlike the fake server in a passing row could not fail the test. The counts of every verdict are now part of the committed file. A missing golden was also treated as an empty one, so adding a resource type without generating its report, or deleting a report, silently passed. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 13 ++++++---- bundle/direct/tests/output/alerts.txt | 6 +++++ bundle/direct/tests/output/apps.txt | 9 +++++++ bundle/direct/tests/output/catalogs.txt | 7 ++++++ .../direct/tests/output/cluster_policies.txt | 6 +++++ bundle/direct/tests/output/clusters.txt | 6 +++++ bundle/direct/tests/output/dashboards.txt | 8 ++++++ .../tests/output/database_instances.txt | 10 ++++++++ bundle/direct/tests/output/experiments.txt | 6 +++++ .../tests/output/external_locations.txt | 7 ++++++ bundle/direct/tests/output/genie_spaces.txt | 7 ++++++ bundle/direct/tests/output/instance_pools.txt | 9 +++++++ bundle/direct/tests/output/jobs.txt | 7 ++++++ .../tests/output/model_serving_endpoints.txt | 9 +++++++ bundle/direct/tests/output/models.txt | 7 ++++++ bundle/direct/tests/output/pipelines.txt | 10 ++++++++ .../direct/tests/output/postgres_projects.txt | 10 ++++++++ .../tests/output/postgres_synced_tables.txt | 6 +++++ .../direct/tests/output/registered_models.txt | 11 ++++++++ bundle/direct/tests/output/schemas.txt | 8 ++++++ bundle/direct/tests/output/secret_scopes.txt | 4 +++ bundle/direct/tests/output/sql_warehouses.txt | 8 ++++++ bundle/direct/tests/output/volumes.txt | 4 +++ bundle/direct/tests/report_test.go | 25 +++++++++++-------- 24 files changed, 188 insertions(+), 15 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index da00dd43eb6..c9498a2fc6d 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -64,11 +64,14 @@ API sees. `POST_DEPLOY_DRIFT` and `SUPPRESSED` are where the interesting gaps are. `NOT_OBSERVABLE` is not a gap; it is the wire format telling the truth. -Two files per resource type: `output/.txt` holds only the results worth looking at, -one line each, so any diff to it is a change in behaviour. `output/.full.txt` holds -every result plus the summary and the not-covered list, and indents the evidence under each -finding — the post-deploy plan for drift, the whole API error for a rejection. It is -gitignored, because it moves whenever a passing row moves and is full of generated ids. +Two files per resource type. `output/.txt` is committed: one line per finding, then +the count of every verdict — including the passing ones, which no line names. Those counts +are what makes a change in *passing* behaviour visible, since a field that starts being +recreated instead of updated moves one `OK` to `OK_RECREATE` and nothing else would show it. + +`output/.full.txt` holds every row, the not-covered list, and the evidence indented +under each finding — the post-deploy plan for drift, the whole API error for a rejection. It +is gitignored, because it is full of generated ids and moves whenever any row moves. The suite deliberately does **not** read `resources.yml`. That file is the engine's answer to many of these cases, so consulting it would just restate the implementation. diff --git a/bundle/direct/tests/output/alerts.txt b/bundle/direct/tests/output/alerts.txt index e69de29bb2d..26755f1d511 100644 --- a/bundle/direct/tests/output/alerts.txt +++ b/bundle/direct/tests/output/alerts.txt @@ -0,0 +1,6 @@ + +=== summary +OK 122 +SUPPRESSED 4 +NOT_OBSERVABLE 10 +SKIPPED 13 diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index d626e03481f..99a7db84ff5 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -55,3 +55,12 @@ source_code_path x y SUPPRESSED n source_code_path y absent SUPPRESSED no active deployment source_code_path y x SUPPRESSED no active deployment usage_policy_id y absent UPDATE_IGNORED usage_policy_id + +=== summary +OK 119 +OK_RECREATE 2 +SUPPRESSED 34 +NOT_OBSERVABLE 65 +UPDATE_IGNORED 22 +SKIPPED 77 +START_NOT_REACHED 4 diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index becbdecc22a..bdda2a61d1a 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -9,3 +9,10 @@ properties['team'] absent x START_NOT_REA properties['team'] absent y START_NOT_REACHED properties['team'] properties['team'] x absent UPDATE_IGNORED properties['team'] properties['team'] y absent UPDATE_IGNORED properties['team'] + +=== summary +OK 16 +UPDATE_IGNORED 5 +POST_DEPLOY_DRIFT 1 +SKIPPED 8 +START_NOT_REACHED 4 diff --git a/bundle/direct/tests/output/cluster_policies.txt b/bundle/direct/tests/output/cluster_policies.txt index e69de29bb2d..91924eb298c 100644 --- a/bundle/direct/tests/output/cluster_policies.txt +++ b/bundle/direct/tests/output/cluster_policies.txt @@ -0,0 +1,6 @@ + +=== summary +OK 148 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +SKIPPED 1 diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/tests/output/clusters.txt index e69de29bb2d..428e6939c54 100644 --- a/bundle/direct/tests/output/clusters.txt +++ b/bundle/direct/tests/output/clusters.txt @@ -0,0 +1,6 @@ + +=== summary +OK 503 +SUPPRESSED 23 +NOT_OBSERVABLE 12 +SKIPPED 1 diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index 83f663085d2..ce1a6844208 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -15,3 +15,11 @@ parent_path x absent START_NOT_REA parent_path x y START_NOT_REACHED parent_path parent_path y absent START_NOT_REACHED parent_path parent_path y x START_NOT_REACHED parent_path + +=== summary +OK 24 +SUPPRESSED 8 +NOT_OBSERVABLE 6 +UPDATE_IGNORED 2 +SKIPPED 6 +START_NOT_REACHED 8 diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index b551721c2e0..b41227ab3a8 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -57,3 +57,13 @@ usage_policy_id x absent COLLATERAL_DR usage_policy_id x y COLLATERAL_DRIFT capacity usage_policy_id y absent COLLATERAL_DRIFT capacity usage_policy_id y x COLLATERAL_DRIFT capacity + +=== summary +OK_RECREATE 32 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +UPDATE_IGNORED 2 +POST_DEPLOY_DRIFT 1 +COLLATERAL_DRIFT 52 +SKIPPED 22 +START_NOT_REACHED 3 diff --git a/bundle/direct/tests/output/experiments.txt b/bundle/direct/tests/output/experiments.txt index e69de29bb2d..a1b5273daac 100644 --- a/bundle/direct/tests/output/experiments.txt +++ b/bundle/direct/tests/output/experiments.txt @@ -0,0 +1,6 @@ + +=== summary +OK 2 +OK_RECREATE 12 +OK_INERT 18 +SKIPPED 2 diff --git a/bundle/direct/tests/output/external_locations.txt b/bundle/direct/tests/output/external_locations.txt index e69de29bb2d..59c5ff2ea14 100644 --- a/bundle/direct/tests/output/external_locations.txt +++ b/bundle/direct/tests/output/external_locations.txt @@ -0,0 +1,7 @@ + +=== summary +OK 26 +OK_RECREATE 110 +SUPPRESSED 5 +NOT_OBSERVABLE 3 +SKIPPED 18 diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index 93c657e957d..c053c7c237c 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -14,3 +14,10 @@ warehouse_id absent x START_NOT_REA warehouse_id absent y START_NOT_REACHED warehouse_id warehouse_id x absent UPDATE_IGNORED warehouse_id warehouse_id y absent UPDATE_IGNORED warehouse_id + +=== summary +OK 9 +NOT_OBSERVABLE 6 +UPDATE_IGNORED 5 +SKIPPED 2 +START_NOT_REACHED 10 diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt index aeb3ac68bcd..35405d4397a 100644 --- a/bundle/direct/tests/output/instance_pools.txt +++ b/bundle/direct/tests/output/instance_pools.txt @@ -3,3 +3,12 @@ enable_elastic_disk absent true UPDATE_IGNORE enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk enable_elastic_disk true absent START_NOT_REACHED enable_elastic_disk enable_elastic_disk true false START_NOT_REACHED enable_elastic_disk + +=== summary +OK 104 +OK_RECREATE 78 +SUPPRESSED 6 +NOT_OBSERVABLE 4 +UPDATE_IGNORED 2 +SKIPPED 1 +START_NOT_REACHED 2 diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index e69de29bb2d..39b8e55acf7 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -0,0 +1,7 @@ + +=== summary +OK 2267 +OK_INERT 6 +SUPPRESSED 12 +NOT_OBSERVABLE 129 +SKIPPED 3 diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index 8c81b7148ce..c9939c6e82b 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -7,3 +7,12 @@ config.auto_capture_config.enabled true absent COLLATERAL_DR config.auto_capture_config.enabled true false COLLATERAL_DRIFT config config.auto_capture_config.schema_name x absent COLLATERAL_DRIFT config config.auto_capture_config.table_name_prefix y absent COLLATERAL_DRIFT config + +=== summary +OK 112 +OK_RECREATE 28 +OK_INERT 22 +SUPPRESSED 10 +NOT_OBSERVABLE 6 +COLLATERAL_DRIFT 8 +SKIPPED 2 diff --git a/bundle/direct/tests/output/models.txt b/bundle/direct/tests/output/models.txt index 638a23988e1..c8a81a801cb 100644 --- a/bundle/direct/tests/output/models.txt +++ b/bundle/direct/tests/output/models.txt @@ -1,3 +1,10 @@ === model.yml.tmpl description x absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) description y absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) + +=== summary +OK 4 +OK_RECREATE 2 +OK_INERT 18 +BACKEND_ERROR 2 +SKIPPED 1 diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index c1c3cb17904..8d9abfe17eb 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -3,3 +3,13 @@ dry_run absent true UPDATE_IGNORE dry_run false true UPDATE_IGNORED dry_run dry_run true absent START_NOT_REACHED dry_run dry_run true false START_NOT_REACHED dry_run + +=== summary +OK 766 +OK_RECREATE 33 +OK_INERT 12 +SUPPRESSED 39 +NOT_OBSERVABLE 20 +UPDATE_IGNORED 2 +SKIPPED 2 +START_NOT_REACHED 2 diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index 5ab031f75df..64d60981526 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -38,3 +38,13 @@ display_name absent y BASE_ERROR c display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... + +=== summary +OK 32 +OK_RECREATE 8 +SUPPRESSED 4 +NOT_OBSERVABLE 3 +COLLATERAL_DRIFT 1 +BACKEND_ERROR 32 +SKIPPED 1 +BASE_ERROR 6 diff --git a/bundle/direct/tests/output/postgres_synced_tables.txt b/bundle/direct/tests/output/postgres_synced_tables.txt index e69de29bb2d..01f1c2fa5bd 100644 --- a/bundle/direct/tests/output/postgres_synced_tables.txt +++ b/bundle/direct/tests/output/postgres_synced_tables.txt @@ -0,0 +1,6 @@ + +=== summary +OK_RECREATE 114 +SUPPRESSED 6 +NOT_OBSERVABLE 4 +SKIPPED 1 diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 7ed3b88132f..08ee9c54c33 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -19,3 +19,14 @@ owner y absent START_NOT_REA owner y x START_NOT_REACHED owner schema_name absent default BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) schema_name default absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) + +=== summary +OK 40 +OK_RECREATE 2 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +UPDATE_IGNORED 4 +BACKEND_ERROR 4 +SKIPPED 9 +BASE_ERROR 4 +START_NOT_REACHED 8 diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index e3c8d8a64c5..7cb413f4e81 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -9,3 +9,11 @@ properties['team'] absent x START_NOT_REA properties['team'] absent y START_NOT_REACHED properties['team'] properties['team'] x absent UPDATE_IGNORED properties['team'] properties['team'] y absent UPDATE_IGNORED properties['team'] + +=== summary +OK 14 +OK_RECREATE 2 +UPDATE_IGNORED 5 +POST_DEPLOY_DRIFT 1 +SKIPPED 2 +START_NOT_REACHED 4 diff --git a/bundle/direct/tests/output/secret_scopes.txt b/bundle/direct/tests/output/secret_scopes.txt index e69de29bb2d..99a51707ab3 100644 --- a/bundle/direct/tests/output/secret_scopes.txt +++ b/bundle/direct/tests/output/secret_scopes.txt @@ -0,0 +1,4 @@ + +=== summary +OK_RECREATE 12 +SKIPPED 1 diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index 10ff5d11c53..8cc54918684 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -3,3 +3,11 @@ name absent x START_NOT_REA name absent y START_NOT_REACHED name name x absent POST_DEPLOY_DRIFT name name y absent POST_DEPLOY_DRIFT name + +=== summary +OK 85 +SUPPRESSED 5 +NOT_OBSERVABLE 2 +POST_DEPLOY_DRIFT 2 +SKIPPED 2 +START_NOT_REACHED 2 diff --git a/bundle/direct/tests/output/volumes.txt b/bundle/direct/tests/output/volumes.txt index e69de29bb2d..db4540165a2 100644 --- a/bundle/direct/tests/output/volumes.txt +++ b/bundle/direct/tests/output/volumes.txt @@ -0,0 +1,4 @@ + +=== summary +OK 8 +SKIPPED 2 diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index f357d51d533..7bde33a4f0e 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -240,9 +240,12 @@ func (r *report) uncoveredPaths() []string { return out } -// render produces a report body. Problems-only is the committed form: one line per -// finding and nothing else, so a diff is always a change in behaviour. The full form -// adds the passing rows, the summary, and what was not covered. +// render produces a report body. Problems-only is the committed form: one line per finding, +// plus the count of every verdict -- including the passing ones, which no line names. Those +// counts are what makes a change in *passing* behaviour visible: a field that starts being +// recreated instead of updated moves one OK to OK_RECREATE and nothing else would show it, +// on either the fake server or a real workspace. The full form adds every row, the evidence +// behind each finding, and what was not covered. func (r *report) render(problemsOnly bool) string { r.mu.Lock() defer r.mu.Unlock() @@ -287,10 +290,6 @@ func (r *report) render(problemsOnly bool) string { } } - if problemsOnly { - return sb.String() - } - sb.WriteString("\n=== summary\n") for _, v := range []verdict{ verdictOK, verdictRecreate, verdictInertConfirmed, verdictSuppressed, @@ -301,10 +300,14 @@ func (r *report) render(problemsOnly bool) string { verdictUnsettable, verdictSkipped, verdictBaseError, verdictStartNotReached, } { if counts[v] > 0 { - fmt.Fprintf(&sb, "%-14s %d\n", v, counts[v]) + fmt.Fprintf(&sb, "%-18s %d\n", v, counts[v]) } } + if problemsOnly { + return sb.String() + } + if gaps := r.uncoveredPaths(); len(gaps) > 0 { paths := gaps fmt.Fprintf(&sb, "\n=== not covered: %d fields no config of this type declares\n", len(paths)) @@ -343,9 +346,11 @@ func (r *report) write(t testutil.TestingT) { return } + // Not tolerated as missing: a report with no golden would silently pass, so adding a + // resource type without generating its report, or deleting one, would go unnoticed. expected, err := os.ReadFile(name) - if err != nil && !errors.Is(err, fs.ErrNotExist) { - t.Errorf("reading %s: %s", name, err) + if err != nil { + t.Errorf("reading %s: %s (run ./task test-update-fields)", name, err) return } testdiff.AssertEqualTexts(t, name, name, testdiff.NormalizeNewlines(string(expected)), body) From c001c7cd1787ed9b02e6fce36d1bf12c41469034 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 09:28:41 +0200 Subject: [PATCH 028/118] Field catalog: report fields with nothing to test, cover empty scalar lists From the adversarial review: a field that ended up with no transitions disappeared without a trace -- no verdict, no coverage gap. Two shapes did this. A type with no generic value (an `any` field like serialized_dashboard) got none, and a required field the library gives a single value has no second value to move to and no absent to move from, so volumes catalog_name, schema_name and volume_type ran nothing while counting as covered. Both are reported as not covered now. While there: a list of scalars the config leaves empty had nothing to trim, so the whole field and everything under it was a gap. Two elements of its own type need no invention and cover adding, growing, shrinking and removing -- 75 gaps closed and 444 more observations. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 7 ++- bundle/direct/tests/output/apps.txt | 7 +-- .../direct/tests/output/cluster_policies.txt | 2 +- .../tests/output/database_instances.txt | 2 +- bundle/direct/tests/output/jobs.txt | 2 +- .../tests/output/model_serving_endpoints.txt | 8 +-- bundle/direct/tests/output/pipelines.txt | 2 +- bundle/direct/tests/report_test.go | 11 +++-- bundle/direct/tests/values_test.go | 49 +++++++++++++++---- 9 files changed, 65 insertions(+), 25 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index c9498a2fc6d..bf2ae8c8957 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -23,8 +23,13 @@ complete digraph, so a single Eulerian circuit covers every ordered pair exactly each move starts where the last one ended. That halves the deploys, and `TestTransitionsCoverEveryPairInOneChain` is what guarantees nothing is missed. +A field whose type has no generic value at all — an `any` like `serialized_dashboard` — and a +required field the library gives a single value are reported as not covered rather than +quietly skipped: there is no second value to move to and no absent to move from. + **Slices and maps** are covered two ways. The container is a field in its own right, whose -values are the config's own and that value with its last entry dropped — so with `absent` +values are the config's own and that value with one entry dropped — or, for a list of +scalars the config leaves empty, one and two elements of the element's own type — so with `absent` in the set, one field covers adding and removing an entry as well as adding and removing the whole container, all with data the backend has already accepted. Separately, a pattern like `tasks[*].description` is expanded against the deployed config to the indices that diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 99a7db84ff5..5f802f12e73 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -55,12 +55,13 @@ source_code_path x y SUPPRESSED n source_code_path y absent SUPPRESSED no active deployment source_code_path y x SUPPRESSED no active deployment usage_policy_id y absent UPDATE_IGNORED usage_policy_id +user_api_scopes len2 absent UPDATE_IGNORED user_api_scopes === summary -OK 119 +OK 124 OK_RECREATE 2 SUPPRESSED 34 NOT_OBSERVABLE 65 -UPDATE_IGNORED 22 -SKIPPED 77 +UPDATE_IGNORED 23 +SKIPPED 82 START_NOT_REACHED 4 diff --git a/bundle/direct/tests/output/cluster_policies.txt b/bundle/direct/tests/output/cluster_policies.txt index 91924eb298c..ebe3c0fd689 100644 --- a/bundle/direct/tests/output/cluster_policies.txt +++ b/bundle/direct/tests/output/cluster_policies.txt @@ -1,6 +1,6 @@ === summary -OK 148 +OK 154 SUPPRESSED 1 NOT_OBSERVABLE 1 SKIPPED 1 diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index b41227ab3a8..543401978f5 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -65,5 +65,5 @@ NOT_OBSERVABLE 1 UPDATE_IGNORED 2 POST_DEPLOY_DRIFT 1 COLLATERAL_DRIFT 52 -SKIPPED 22 +SKIPPED 24 START_NOT_REACHED 3 diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index 39b8e55acf7..bb2a3394d90 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -1,6 +1,6 @@ === summary -OK 2267 +OK 2621 OK_INERT 6 SUPPRESSED 12 NOT_OBSERVABLE 129 diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index c9939c6e82b..783bed33a85 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -1,4 +1,6 @@ === model_serving_endpoint.yml.tmpl +config.auto_capture_config.catalog_name absent y COLLATERAL_DRIFT config +config.auto_capture_config.catalog_name y absent COLLATERAL_DRIFT config config.auto_capture_config.enabled absent false COLLATERAL_DRIFT config config.auto_capture_config.enabled absent true COLLATERAL_DRIFT config config.auto_capture_config.enabled false absent COLLATERAL_DRIFT config @@ -9,10 +11,10 @@ config.auto_capture_config.schema_name x absent COLLATERAL_DR config.auto_capture_config.table_name_prefix y absent COLLATERAL_DRIFT config === summary -OK 112 -OK_RECREATE 28 +OK 136 +OK_RECREATE 26 OK_INERT 22 SUPPRESSED 10 NOT_OBSERVABLE 6 -COLLATERAL_DRIFT 8 +COLLATERAL_DRIFT 10 SKIPPED 2 diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 8d9abfe17eb..5c96b9c107a 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -5,7 +5,7 @@ dry_run true absent START_NOT_REA dry_run true false START_NOT_REACHED dry_run === summary -OK 766 +OK 820 OK_RECREATE 33 OK_INERT 12 SUPPRESSED 39 diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index 7bde33a4f0e..b4dd7390453 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -185,7 +185,7 @@ func (r *report) add(res result) { // reported as not covered when *no* config of the type reached it: several configs of one // resource declare different blocks, so a union of the per-config gaps would list fields // that are in fact tested -- jobs has eight configs and only some declare tasks. -func (r *report) addCoverage(covered []field, notCovered []string) { +func (r *report) addCoverage(covered []field, uncovered []string) { r.mu.Lock() defer r.mu.Unlock() if r.wildcard == nil { @@ -194,7 +194,7 @@ func (r *report) addCoverage(covered []field, notCovered []string) { if r.covered == nil { r.covered = map[string]bool{} } - for _, p := range notCovered { + for _, p := range uncovered { r.wildcard[p] = true } for _, f := range covered { @@ -309,9 +309,10 @@ func (r *report) render(problemsOnly bool) string { } if gaps := r.uncoveredPaths(); len(gaps) > 0 { - paths := gaps - fmt.Fprintf(&sb, "\n=== not covered: %d fields no config of this type declares\n", len(paths)) - for _, p := range paths { + fmt.Fprintf(&sb, "\n=== not covered: %d fields with nothing to test\n", len(gaps)) + fmt.Fprintf(&sb, "# either no config of this type declares the slice or map they sit in,\n") + fmt.Fprintf(&sb, "# or their type has no generic value and the value library gives them none\n") + for _, p := range gaps { fmt.Fprintf(&sb, "%s\n", p) } } diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 1a93f438026..a173efec7d0 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -206,9 +206,23 @@ func isContainer(kind reflect.Kind) bool { // observe a value-to-value transition on top of add and remove; more would multiply the // matrix without testing a different code path. func defaultValues(typ reflect.Type) []any { + for typ.Kind() == reflect.Pointer { + typ = typ.Elem() + } if values := enumValues(typ); values != nil { return values } + // A list of scalars is a value in its own right, and the config often leaves it empty -- + // a job's email_notifications.on_start, say -- so containerValues has nothing to trim and + // the fields inside it do not exist. One and two elements give the same coverage from + // nothing: with absent in the set, that is add, grow, shrink and remove. + if typ.Kind() == reflect.Slice { + elements := defaultValues(typ.Elem()) + if len(elements) < 2 { + return nil + } + return []any{[]any{elements[0]}, []any{elements[0], elements[1]}} + } return kindValues(typ.Kind()) } @@ -422,12 +436,9 @@ func isRequired(resourceType, path string) bool { // "tasks[*].description" is expanded to the indices that exist, so the fields inside an // element get the same treatment as any other. A pattern with nothing behind it in the // config is reported as not covered rather than silently tested against nothing. -func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, resource any, runSeed uint64, unsettable []dresources.FieldRule) (fields []field, wildcard []string, inertFields map[string]string) { +func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, resource any, runSeed uint64, unsettable []dresources.FieldRule) (fields []field, uncovered []string, inertFields map[string]string) { inertFields = map[string]string{} add := func(path string, kind reflect.Kind, values []any) { - if values == nil { - return - } if _, skipped := fv.skipReason(path); skipped { return } @@ -435,13 +446,22 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue inertFields[path] = reason return } - fields = append(fields, field{ + f := field{ seed: fieldSeed(runSeed, path), path: path, kind: kind, values: values, required: isRequired(resourceType, path), - }) + } + // A field with nothing to walk is a gap, not something to drop on the floor: no + // generic value exists for its type (an `any` field like serialized_dashboard), or + // it is required and the library gives it one value, so there is no second value to + // move to and no absent to move from. + if len(f.transitions()) == 0 { + uncovered = append(uncovered, patternOf(path)) + return + } + fields = append(fields, f) } _ = structwalk.WalkType(inputType, func(p *structpath.PatternNode, typ reflect.Type, sf *reflect.StructField) bool { @@ -469,7 +489,17 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue // grow and shrink, so test it as a field before descending into it. if typ.Kind() != reflect.Struct { for _, concrete := range expandPattern(resource, path) { - add(concrete, typ.Kind(), containerValues(resource, concrete)) + values := fv.fields[path] + if values == nil { + values = containerValues(resource, concrete) + } + if values == nil { + // The config leaves it empty, so there is nothing to trim -- but a + // list of scalars needs no invention: two of its element type cover + // adding, growing, shrinking and removing. + values = defaultValues(typ) + } + add(concrete, typ.Kind(), values) } } return true @@ -493,7 +523,7 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue return false } if _, skipped := fv.skipReason(path); !skipped { - wildcard = append(wildcard, path) + uncovered = append(uncovered, path) } return false } @@ -514,7 +544,8 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue rng := rand.New(rand.NewPCG(runSeed, 1)) rng.Shuffle(len(fields), func(i, j int) { fields[i], fields[j] = fields[j], fields[i] }) - return fields, wildcard, inertFields + slices.Sort(uncovered) + return fields, slices.Compact(uncovered), inertFields } // isNotUserSettable reports whether the bundle marks a field as something the user From 5a9d9b7f642558633b6301be53317b445cb341ad Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 11:43:34 +0200 Subject: [PATCH 029/118] Field catalog: drop the documented NOT_IN_STATE verdict From the adversarial review, which found the verdict documented but never produced. I first implemented it as "the path does not exist in the adapter's state type", and it was unsound: secret_scopes renames its fields in PrepareState (name to scope, backend_type to scope_backend_type), so three fields that deploy fine -- OK_RECREATE on every transition -- were relabelled as never reaching the API. A name-identity check is not a reachability check. There is no sound way to derive this from the types: what makes an alert's file_path invisible is that the engine reads the file during initialize and its contents become other fields, so the path itself is genuinely absent from the state and NOT_OBSERVABLE is accurate. Documented that case there and removed the verdict. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 3 +-- bundle/direct/tests/fields_test.go | 4 ++-- bundle/direct/tests/report_test.go | 4 +++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index bf2ae8c8957..4d22918d7a6 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -53,7 +53,7 @@ API sees. | `OK` | planned, applied, next plan clean | | `OK_RECREATE` | same, but the engine replaced the resource | | `SUPPRESSED` | the planner diffed the field and dropped the change; detail is the engine's own reason | -| `NOT_OBSERVABLE` | the two values are identical in the state sent to the API, so there is nothing to see — an unset bool and an explicit `false` are the usual case | +| `NOT_OBSERVABLE` | the two values are identical in the state sent to the API, so there is nothing to see — an unset bool and an explicit `false` are the usual case, as is a field the engine consumes before planning (an alert's `file_path`) | | `NO_PLAN` | the field diff exists but no action was planned | | `POST_DEPLOY_DRIFT` | applied, yet the plan taken straight afterwards still wants the same change: deploying never converges | | `POST_DEPLOY_DRIFT_CHILD` | the field converged but another node of the resource did not; a guard that should stay empty | @@ -64,7 +64,6 @@ API sees. | `UNSETTABLE` | the value could not be written into the config at all | | `BASE_ERROR` | the transition's starting point would not deploy, so nothing was observed | | `START_NOT_REACHED` | the starting value deployed without error but the field did not end up holding it, so the move under test could not be set up — a field the API refuses to clear cannot start from `absent`. Retried once on a fresh resource before being recorded | -| `NOT_IN_STATE` | the field exists in bundle config but not in the state type, so it never reaches the API | `POST_DEPLOY_DRIFT` and `SUPPRESSED` are where the interesting gaps are. `NOT_OBSERVABLE` is not a gap; it is the wire format telling the truth. diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 6b44891edc6..244a68ad85f 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -129,8 +129,8 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl base := h.snapshot() resource, err := h.resource() require.NoError(t, err) - fields, wildcard, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, resource, runSeed, declaredUnsettable(adapter)) - rep.addCoverage(fields, wildcard) + fields, uncovered, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, resource, runSeed, declaredUnsettable(adapter)) + rep.addCoverage(fields, uncovered) // Fields the resource declares a user cannot meaningfully set. Recorded with the // resource's own reason rather than tested: every transition would come back SUPPRESSED diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index b4dd7390453..e2cf2edbf87 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -31,7 +31,9 @@ const ( verdictSuppressed verdict = "SUPPRESSED" // The plan has no entry for the field at all: the two values are indistinguishable // in the state the engine sends, so there is nothing to observe. An unset bool and - // an explicit false are the common case. + // an explicit false are the common case. A field the engine consumes before it plans + // anything also lands here -- an alert's file_path is read during initialize and its + // contents become other fields, so the path itself is not in the state at all. verdictNotObservable verdict = "NOT_OBSERVABLE" // The config changed, the field diff exists, but the plan produced no action at all. verdictNoPlan verdict = "NO_PLAN" From 879cfe4d676e729714b0673a8290ef8e72e25822 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 11:48:29 +0200 Subject: [PATCH 030/118] testserver: require Azure Key Vault metadata on a secret scope From the AWS comparison: an AZURE_KEYVAULT scope is backed by a real vault, so the API rejects one created without its metadata. The fake server accepted it, so the suite reported the backend type as freely settable. secret_scopes now matches AWS row for row. Co-authored-by: Isaac --- bundle/direct/tests/output/secret_scopes.txt | 9 ++++++++- libs/testserver/secret_scopes.go | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/bundle/direct/tests/output/secret_scopes.txt b/bundle/direct/tests/output/secret_scopes.txt index 99a51707ab3..6c91b18e642 100644 --- a/bundle/direct/tests/output/secret_scopes.txt +++ b/bundle/direct/tests/output/secret_scopes.txt @@ -1,4 +1,11 @@ +=== secret_scope.yml.tmpl +backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot create resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARAME... +backend_type AZURE_KEYVAULT absent BASE_ERROR cannot create resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARAME... +backend_type DATABRICKS AZURE_KEYVAULT BACKEND_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... +backend_type absent AZURE_KEYVAULT BACKEND_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... === summary -OK_RECREATE 12 +OK_RECREATE 8 +BACKEND_ERROR 2 SKIPPED 1 +BASE_ERROR 2 diff --git a/libs/testserver/secret_scopes.go b/libs/testserver/secret_scopes.go index 8dab8217384..a20b734f1a4 100644 --- a/libs/testserver/secret_scopes.go +++ b/libs/testserver/secret_scopes.go @@ -32,6 +32,18 @@ func (s *FakeWorkspace) SecretsCreateScope(req Request) Response { backendType = workspace.ScopeBackendTypeDatabricks } + // An Azure Key Vault scope is backed by a real vault, so the API requires its metadata + // and rejects the scope without it. + if backendType == workspace.ScopeBackendTypeAzureKeyvault && request.BackendAzureKeyvault == nil { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined!", + }, + } + } + scope := workspace.SecretScope{ Name: request.Scope, BackendType: backendType, From a5e6a685bcdd4b6691e178eccd38111f7cb9ff0a Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 12:07:43 +0200 Subject: [PATCH 031/118] Field catalog: five correctness fixes from the second review round - A nil pendingApply was returned when opening the state file failed, and every caller cancels what plan hands back, so a state-file problem became a panic instead of a PLAN_ERROR. Introduced by the readPlan change in this branch. - Removing a slice element that is not the last one shifted its successor into the same path, so the field never became absent and the move was recorded as one from absent that never happened. Only the tail can be made absent; anything else is UNSETTABLE, which is what config.command[0] now reports. - An SDK enum with a single valid value fell back to the generic "x" and "y", so the suite tested values the backend rejects -- a pipeline's deployment.kind only permits BUNDLE. One value plus absent still gives an add and a remove, and invents nothing. - The starting-value check accepted any skipped change as proof the value had landed, including one the planner dropped: an app whose compute is stopped suppresses a command with "no active deployment" and the command never reached the remote. Only the planner's benign reasons count now. - A remote that moved on the second read was called STALE_READ even when the plan still wanted a change -- that is drift to a different value, and it is now classified as such. - A rebuild that fails was silently ignored in the retry path and skipped only the current subtest in the recovery path, leaving later transitions running against a resource in an unknown state. Both record the failure, and the remaining transitions of that type are reported rather than run. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 68 ++++++++++++++++--- bundle/direct/tests/harness_test.go | 17 +++-- bundle/direct/tests/output/apps.txt | 58 +++++++++------- bundle/direct/tests/output/clusters.txt | 2 +- bundle/direct/tests/output/dashboards.txt | 22 +++--- .../tests/output/database_instances.txt | 44 ++++++------ bundle/direct/tests/output/experiments.txt | 16 ++++- bundle/direct/tests/output/jobs.txt | 9 ++- .../tests/output/model_serving_endpoints.txt | 14 +++- bundle/direct/tests/output/models.txt | 4 +- bundle/direct/tests/output/pipelines.txt | 9 ++- .../direct/tests/output/postgres_projects.txt | 17 +++-- .../tests/output/postgres_synced_tables.txt | 7 +- bundle/direct/tests/output/sql_warehouses.txt | 8 +-- .../tests/testdata/fields/sql_warehouses.yml | 29 ++++++-- bundle/direct/tests/values_test.go | 15 ++-- bundle/direct/tests/values_unit_test.go | 7 +- 17 files changed, 241 insertions(+), 105 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 244a68ad85f..d3d8a297f85 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -157,6 +157,11 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } for _, f := range fields { + // Set once the resource is in an unknown state and could not be replaced. Every + // remaining transition would be observing that rather than the field, and so would + // every later field, so they are recorded as such instead of run. + var broken error + t.Run(f.path, func(t *testing.T) { // What the field is currently deployed as, when that is known. Transitions are // generated in runs that share a starting value, so this skips about half of @@ -165,6 +170,11 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl for _, tr := range f.transitions() { t.Run(tr.label(), func(t *testing.T) { + if broken != nil { + rep.add(result{cfg.name, f.path, valueLabel(tr.from), valueLabel(tr.to), verdictBaseError, oneLine(broken.Error()), broken.Error()}) + return + } + reuse := deployedKnown && valueLabel(deployed) == valueLabel(tr.from) res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline) @@ -173,7 +183,16 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // created without the field genuinely starts absent. Rebuild and try the // transition once more, so it is observed rather than written off. if res.verdict == verdictStartNotReached { - if rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h); err == nil { + rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h) + if err != nil { + // Nothing was observed and there is no resource left to observe + // it on, which is a different statement from the field refusing + // its starting value. + res.verdict = verdictBaseError + res.detail = oneLine(err.Error()) + res.evidence = err.Error() + broken = err + } else { h, base = rebuilt, rebuilt.snapshot() res = runTransition(t, h, cfg.name, f.path, tr, false, baseline) } @@ -185,7 +204,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // Only a clean apply leaves the field provably at "to". deployed, deployedKnown = tr.to, res.verdict == verdictOK || res.verdict == verdictRecreate - if res.verdict.leavesResourceUsable() { + if broken != nil || res.verdict.leavesResourceUsable() { return } deployedKnown = false @@ -193,13 +212,19 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // transition would turn one failure into a run of them. rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h) if err != nil { - t.Skipf("could not rebuild baseline: %s", err) + broken = err + return } h = rebuilt base = h.snapshot() }) } }) + if broken != nil { + // The type has no usable resource left, so no later field can be observed either. + rep.add(result{cfg.name, "(rebuild)", "", "", verdictBaseError, oneLine(broken.Error()), broken.Error()}) + return + } // Put the field back and confirm the resource converged. A field the API cannot // clear leaves it drifted for good, which would otherwise be blamed on every // field tested afterwards -- so start over on a fresh resource. A new name is @@ -217,6 +242,20 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } } +// converged reports whether a plan no longer wants to change the field. +func converged(plan *deployplan.Plan, node, path string) bool { + change, ok := planChange(plan, node, path) + return !ok || change.Action == deployplan.Skip +} + +// reachedValue reports whether a plan entry for the field means the remote now holds what +// the config asked for. A pending change means it does not; so does a change the planner +// dropped for a reason other than the value already being there -- an app whose compute is +// stopped suppresses a command with "no active deployment", and the command never landed. +func reachedValue(change *deployplan.ChangeDesc) bool { + return change.Action == deployplan.Skip && benignSuppressions[change.Reason] +} + // runTransition moves one field from one value to another and reports what happened. // startsDeployed says the field already holds tr.from, so the setup deploy can be skipped. func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool, baseline map[string]bool) result { @@ -241,7 +280,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // under a label that is not what happened -- "absent to 168" while the remote still // holds 720. Cheaper to check than to reason about afterwards. if plan, diags := h.readPlan(); !diags.HasError() { - if change, ok := planChange(plan, h.node, path); ok && change.Action != deployplan.Skip && !baseline[path] { + if change, ok := planChange(plan, h.node, path); ok && !reachedValue(change) && !baseline[path] { res.verdict = verdictStartNotReached res.detail = path res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) @@ -305,17 +344,26 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.detail = firstError(diags) return res } - if !wasIgnored(plan, second, h.node, path) { + switch { + case wasIgnored(plan, second, h.node, path): + res.verdict = verdictUpdateIgnored + res.detail = path + res.evidence = withContext("plan taken twice after the deploy, still unchanged:", planJSON(second)) + t.Logf("the write was accepted but the remote value did not move on two reads:\n%s", res.evidence) + return res + + case converged(second, h.node, path): + // The remote moved and the plan is now clean, so the first read was behind. res.verdict = verdictStaleRead res.detail = path res.evidence = withContext("plan taken right after the deploy:", planJSON(after)) return res + + default: + // The remote moved, but to something other than what was asked for, so this is + // drift rather than a stale read. Classified below, against the later plan. + after = second } - res.verdict = verdictUpdateIgnored - res.detail = path - res.evidence = withContext("plan taken twice after the deploy, still unchanged:", planJSON(second)) - t.Logf("the write was accepted but the remote value did not move on two reads:\n%s", res.evidence) - return res } if own, child := driftDetail(after, h.node, baseline); own != "" || child != "" { diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index 1ef906963b2..a208d34e431 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -306,8 +306,9 @@ func (h *bundleHarness) plan() (*pendingApply, *deployplan.Plan, diag.Diagnostic ctx, cancel := h.opCtx() db := &direct.DeploymentBundle{} //exhaustruct:ignore if err := db.StateDB.Open(ctx, h.statePath, dstate.WithRecovery(false), dstate.WithWrite(false)); err != nil { - cancel() - return nil, nil, diag.FromErr(err) + // A pendingApply even here: every caller cancels what plan hands back, and a nil one + // would turn a state-file problem into a panic. + return &pendingApply{ctx: ctx, cancel: cancel, db: db}, nil, diag.FromErr(err) } plan, err := db.CalculatePlan(ctx, h.client, &h.bundle.Config) diags := logdiag.FlushCollected(ctx) @@ -495,8 +496,10 @@ func removeField(resource any, node *structpath.PathNode) error { return structaccess.Set(resource, node, nil) } -// removeIndex rebuilds a slice without one element and writes it back, since a slice -// element cannot be zeroed away. +// removeIndex drops the last element of a slice, which is the only index that can be made +// absent: removing any earlier one shifts its successor into the same path, so the field +// would still be there holding a different value and the transition would be recorded as a +// move from absent that never happened. func removeIndex(resource any, parent *structpath.PathNode, container any, index int) error { value := reflect.ValueOf(container) if value.Kind() != reflect.Slice { @@ -505,11 +508,13 @@ func removeIndex(resource any, parent *structpath.PathNode, container any, index if index < 0 || index >= value.Len() { return nil } + if index != value.Len()-1 { + return fmt.Errorf("cannot make %s[%d] absent: %s[%d] would shift into it", parent, index, parent, index+1) + } // A fresh slice, not a re-slice: appending into the original backing array would also // change any copy of the slice the suite is holding on to. - trimmed := reflect.MakeSlice(value.Type(), 0, value.Len()-1) + trimmed := reflect.MakeSlice(value.Type(), 0, index) trimmed = reflect.AppendSlice(trimmed, value.Slice(0, index)) - trimmed = reflect.AppendSlice(trimmed, value.Slice(index+1, value.Len())) return structaccess.Set(resource, parent, trimmed.Interface()) } diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 5f802f12e73..87ad18aee8f 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -4,29 +4,38 @@ budget_policy_id y absent UPDATE_IGNORE compute_max_instances 2 absent UPDATE_IGNORED compute_max_instances compute_min_instances 1 absent UPDATE_IGNORED compute_min_instances compute_min_instances 2 absent UPDATE_IGNORED compute_min_instances -config.command absent len1 SUPPRESSED no active deployment +config.command absent len1 START_NOT_REACHED config.command +config.command absent len2 START_NOT_REACHED config.command config.command len1 absent SUPPRESSED no active deployment +config.command len1 len2 START_NOT_REACHED config.command config.command len2 absent SUPPRESSED no active deployment config.command len2 len1 SUPPRESSED no active deployment -config.command[1] absent x SUPPRESSED no active deployment -config.command[1] absent y SUPPRESSED no active deployment -config.command[1] x y SUPPRESSED no active deployment -config.command[1] y x SUPPRESSED no active deployment -config.env absent len0 SUPPRESSED no active deployment -config.env len0 absent SUPPRESSED no active deployment +config.command[0] absent x UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it +config.command[0] absent y UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it +config.command[0] x absent START_NOT_REACHED config.command[0] +config.command[0] x y SUPPRESSED no active deployment +config.command[0] y absent START_NOT_REACHED config.command[0] +config.command[0] y x SUPPRESSED no active deployment +config.command[1] y absent START_NOT_REACHED config.command[1] +config.env absent len0 START_NOT_REACHED config.env +config.env absent len1 SUPPRESSED no active deployment +config.env len0 absent START_NOT_REACHED config.env +config.env len0 len1 START_NOT_REACHED config.env config.env len1 absent SUPPRESSED no active deployment config.env len1 len0 SUPPRESSED no active deployment -config.env[0].name x y SUPPRESSED no active deployment +config.env[0].name x y START_NOT_REACHED config.env[0].name config.env[0].name y x SUPPRESSED no active deployment config.env[0].value absent x SUPPRESSED no active deployment config.env[0].value absent y SUPPRESSED no active deployment -config.env[0].value x absent SUPPRESSED no active deployment -config.env[0].value x y SUPPRESSED no active deployment -config.env[0].value y absent SUPPRESSED no active deployment +config.env[0].value x absent START_NOT_REACHED config.env[0].value +config.env[0].value x y START_NOT_REACHED config.env[0].value +config.env[0].value y absent START_NOT_REACHED config.env[0].value config.env[0].value y x SUPPRESSED no active deployment config.env[0].value_from absent x SUPPRESSED no active deployment config.env[0].value_from absent y SUPPRESSED no active deployment -config.env[0].value_from x y SUPPRESSED no active deployment +config.env[0].value_from x absent SUPPRESSED no active deployment +config.env[0].value_from x y START_NOT_REACHED config.env[0].value_from +config.env[0].value_from y absent SUPPRESSED no active deployment config.env[0].value_from y x SUPPRESSED no active deployment description x absent UPDATE_IGNORED description git_repository.auto_deploy true absent UPDATE_IGNORED git_repository.auto_deploy @@ -37,8 +46,7 @@ resources len0 len1 START_NOT_REA resources len1 absent UPDATE_IGNORED resources resources len1 len0 UPDATE_IGNORED resources resources[0].app.name y absent UPDATE_IGNORED resources[0].app.name -resources[0].app.permission x absent UPDATE_IGNORED resources[0].app.permission -resources[0].app.permission y absent UPDATE_IGNORED resources[0].app.permission +resources[0].app.permission CAN_USE absent UPDATE_IGNORED resources[0].app.permission resources[0].description absent x START_NOT_REACHED resources[0].description resources[0].description absent y START_NOT_REACHED resources[0].description resources[0].description x absent UPDATE_IGNORED resources[0].description @@ -47,21 +55,23 @@ resources[0].postgres.branch x absent UPDATE_IGNORE resources[0].postgres.branch y absent UPDATE_IGNORED resources[0].postgres.branch resources[0].postgres.database x absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.database y absent UPDATE_IGNORED resources[0].postgres.database -resources[0].postgres.permission x absent UPDATE_IGNORED resources[0].postgres.permission +resources[0].postgres.permission CAN_CONNECT_AND_CREATE absent UPDATE_IGNORED resources[0].postgres.permission source_code_path absent x SUPPRESSED no active deployment source_code_path absent y SUPPRESSED no active deployment -source_code_path x absent SUPPRESSED no active deployment -source_code_path x y SUPPRESSED no active deployment -source_code_path y absent SUPPRESSED no active deployment -source_code_path y x SUPPRESSED no active deployment +source_code_path x absent START_NOT_REACHED source_code_path +source_code_path x y START_NOT_REACHED source_code_path +source_code_path y absent START_NOT_REACHED source_code_path +source_code_path y x START_NOT_REACHED source_code_path +space y x START_NOT_REACHED space usage_policy_id y absent UPDATE_IGNORED usage_policy_id user_api_scopes len2 absent UPDATE_IGNORED user_api_scopes === summary -OK 124 +OK 114 OK_RECREATE 2 -SUPPRESSED 34 -NOT_OBSERVABLE 65 -UPDATE_IGNORED 23 +SUPPRESSED 23 +NOT_OBSERVABLE 56 +UPDATE_IGNORED 22 +UNSETTABLE 2 SKIPPED 82 -START_NOT_REACHED 4 +START_NOT_REACHED 23 diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/tests/output/clusters.txt index 428e6939c54..b086175dfd1 100644 --- a/bundle/direct/tests/output/clusters.txt +++ b/bundle/direct/tests/output/clusters.txt @@ -1,6 +1,6 @@ === summary -OK 503 +OK 499 SUPPRESSED 23 NOT_OBSERVABLE 12 SKIPPED 1 diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index ce1a6844208..d5ed133332e 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -1,14 +1,18 @@ === dashboard.yml.tmpl +dataset_catalog y absent START_NOT_REACHED dataset_catalog +dataset_schema x y START_NOT_REACHED dataset_schema +dataset_schema y absent START_NOT_REACHED dataset_schema +dataset_schema y x START_NOT_REACHED dataset_schema display_name absent x START_NOT_REACHED display_name display_name absent y START_NOT_REACHED display_name display_name x absent UPDATE_IGNORED display_name display_name y absent UPDATE_IGNORED display_name -etag absent x SUPPRESSED custom -etag absent y SUPPRESSED custom -etag x absent SUPPRESSED custom -etag x y SUPPRESSED custom -etag y absent SUPPRESSED custom -etag y x SUPPRESSED custom +etag absent x START_NOT_REACHED etag +etag absent y START_NOT_REACHED etag +etag x absent START_NOT_REACHED etag +etag x y START_NOT_REACHED etag +etag y absent START_NOT_REACHED etag +etag y x START_NOT_REACHED etag parent_path absent x START_NOT_REACHED parent_path parent_path absent y START_NOT_REACHED parent_path parent_path x absent START_NOT_REACHED parent_path @@ -17,9 +21,9 @@ parent_path y absent START_NOT_REA parent_path y x START_NOT_REACHED parent_path === summary -OK 24 -SUPPRESSED 8 +OK 20 +SUPPRESSED 2 NOT_OBSERVABLE 6 UPDATE_IGNORED 2 SKIPPED 6 -START_NOT_REACHED 8 +START_NOT_REACHED 18 diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index 543401978f5..68a9942cc9f 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -5,9 +5,7 @@ capacity x absent START_NOT_REA capacity x y START_NOT_REACHED capacity capacity y absent START_NOT_REACHED capacity capacity y x POST_DEPLOY_DRIFT capacity -custom_tags absent len0 COLLATERAL_DRIFT capacity custom_tags absent len1 COLLATERAL_DRIFT capacity -custom_tags len0 absent COLLATERAL_DRIFT capacity custom_tags len0 len1 COLLATERAL_DRIFT capacity custom_tags len1 absent COLLATERAL_DRIFT capacity custom_tags len1 len0 COLLATERAL_DRIFT capacity @@ -23,47 +21,43 @@ custom_tags[0].value x absent COLLATERAL_DR custom_tags[0].value x y COLLATERAL_DRIFT capacity custom_tags[0].value y absent COLLATERAL_DRIFT capacity custom_tags[0].value y x COLLATERAL_DRIFT capacity -enable_pg_native_login absent false COLLATERAL_DRIFT capacity enable_pg_native_login absent true COLLATERAL_DRIFT capacity -enable_pg_native_login false absent COLLATERAL_DRIFT capacity enable_pg_native_login false true COLLATERAL_DRIFT capacity -enable_pg_native_login true absent COLLATERAL_DRIFT capacity -enable_pg_native_login true false COLLATERAL_DRIFT capacity -enable_readable_secondaries absent false COLLATERAL_DRIFT capacity +enable_pg_native_login true absent START_NOT_REACHED enable_pg_native_login +enable_pg_native_login true false START_NOT_REACHED enable_pg_native_login enable_readable_secondaries absent true COLLATERAL_DRIFT capacity -enable_readable_secondaries false absent COLLATERAL_DRIFT capacity enable_readable_secondaries false true COLLATERAL_DRIFT capacity -enable_readable_secondaries true absent COLLATERAL_DRIFT capacity -enable_readable_secondaries true false COLLATERAL_DRIFT capacity +enable_readable_secondaries true absent START_NOT_REACHED enable_readable_secondaries +enable_readable_secondaries true false START_NOT_REACHED enable_readable_secondaries node_count 1 2 COLLATERAL_DRIFT capacity -node_count 1 absent COLLATERAL_DRIFT capacity -node_count 2 1 COLLATERAL_DRIFT capacity -node_count 2 absent COLLATERAL_DRIFT capacity +node_count 1 absent START_NOT_REACHED node_count +node_count 2 1 START_NOT_REACHED node_count +node_count 2 absent START_NOT_REACHED node_count node_count absent 1 COLLATERAL_DRIFT capacity node_count absent 2 COLLATERAL_DRIFT capacity -retention_window_in_days 1 2 COLLATERAL_DRIFT capacity +retention_window_in_days 1 2 START_NOT_REACHED retention_window_in_days retention_window_in_days 1 absent COLLATERAL_DRIFT capacity -retention_window_in_days 2 1 COLLATERAL_DRIFT capacity -retention_window_in_days 2 absent COLLATERAL_DRIFT capacity +retention_window_in_days 2 1 START_NOT_REACHED retention_window_in_days +retention_window_in_days 2 absent START_NOT_REACHED retention_window_in_days retention_window_in_days absent 1 COLLATERAL_DRIFT capacity retention_window_in_days absent 2 COLLATERAL_DRIFT capacity stopped absent true COLLATERAL_DRIFT capacity stopped false true COLLATERAL_DRIFT capacity -stopped true absent COLLATERAL_DRIFT capacity -stopped true false COLLATERAL_DRIFT capacity +stopped true absent START_NOT_REACHED stopped +stopped true false START_NOT_REACHED stopped usage_policy_id absent x COLLATERAL_DRIFT capacity usage_policy_id absent y COLLATERAL_DRIFT capacity -usage_policy_id x absent COLLATERAL_DRIFT capacity +usage_policy_id x absent START_NOT_REACHED usage_policy_id usage_policy_id x y COLLATERAL_DRIFT capacity -usage_policy_id y absent COLLATERAL_DRIFT capacity -usage_policy_id y x COLLATERAL_DRIFT capacity +usage_policy_id y absent START_NOT_REACHED usage_policy_id +usage_policy_id y x START_NOT_REACHED usage_policy_id === summary OK_RECREATE 32 -SUPPRESSED 1 -NOT_OBSERVABLE 1 +SUPPRESSED 5 +NOT_OBSERVABLE 3 UPDATE_IGNORED 2 POST_DEPLOY_DRIFT 1 -COLLATERAL_DRIFT 52 +COLLATERAL_DRIFT 31 SKIPPED 24 -START_NOT_REACHED 3 +START_NOT_REACHED 18 diff --git a/bundle/direct/tests/output/experiments.txt b/bundle/direct/tests/output/experiments.txt index a1b5273daac..05418c58a45 100644 --- a/bundle/direct/tests/output/experiments.txt +++ b/bundle/direct/tests/output/experiments.txt @@ -1,6 +1,20 @@ +=== experiment.yml.tmpl +tags absent len0 START_NOT_REACHED tags (declared inert: terraform_compat) +tags absent len1 START_NOT_REACHED tags (declared inert: terraform_compat) +tags len0 absent START_NOT_REACHED tags (declared inert: terraform_compat) +tags len0 len1 START_NOT_REACHED tags (declared inert: terraform_compat) +tags len1 absent START_NOT_REACHED tags (declared inert: terraform_compat) +tags len1 len0 START_NOT_REACHED tags (declared inert: terraform_compat) +tags[0].key x absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].key x y START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].key y absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].value absent x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value y absent START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value y x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) === summary OK 2 OK_RECREATE 12 -OK_INERT 18 +OK_INERT 6 SKIPPED 2 +START_NOT_REACHED 12 diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index bb2a3394d90..4eeed220057 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -1,7 +1,12 @@ +=== job.yml.tmpl +deployment.version_id x absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) +deployment.version_id x y START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) +deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) === summary -OK 2621 -OK_INERT 6 +OK 2607 +OK_INERT 3 SUPPRESSED 12 NOT_OBSERVABLE 129 SKIPPED 3 +START_NOT_REACHED 3 diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index 783bed33a85..00968b54f9a 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -1,4 +1,6 @@ === model_serving_endpoint.yml.tmpl +budget_policy_id x absent START_NOT_REACHED budget_policy_id (declared inert: no_update_api) +budget_policy_id x y START_NOT_REACHED budget_policy_id (declared inert: no_update_api) config.auto_capture_config.catalog_name absent y COLLATERAL_DRIFT config config.auto_capture_config.catalog_name y absent COLLATERAL_DRIFT config config.auto_capture_config.enabled absent false COLLATERAL_DRIFT config @@ -9,12 +11,20 @@ config.auto_capture_config.enabled true absent COLLATERAL_DR config.auto_capture_config.enabled true false COLLATERAL_DRIFT config config.auto_capture_config.schema_name x absent COLLATERAL_DRIFT config config.auto_capture_config.table_name_prefix y absent COLLATERAL_DRIFT config +rate_limits len0 absent START_NOT_REACHED rate_limits (declared inert: not_implemented) +rate_limits len1 absent START_NOT_REACHED rate_limits (declared inert: not_implemented) +rate_limits len1 len0 START_NOT_REACHED rate_limits (declared inert: not_implemented) +rate_limits[0].calls 1 2 START_NOT_REACHED rate_limits[0].calls (declared inert: not_implemented) +rate_limits[0].key endpoint absent START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) +rate_limits[0].key user absent START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) +rate_limits[0].key user endpoint START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) === summary -OK 136 +OK 134 OK_RECREATE 26 -OK_INERT 22 +OK_INERT 11 SUPPRESSED 10 NOT_OBSERVABLE 6 COLLATERAL_DRIFT 10 SKIPPED 2 +START_NOT_REACHED 9 diff --git a/bundle/direct/tests/output/models.txt b/bundle/direct/tests/output/models.txt index c8a81a801cb..793b301c662 100644 --- a/bundle/direct/tests/output/models.txt +++ b/bundle/direct/tests/output/models.txt @@ -1,10 +1,12 @@ === model.yml.tmpl description x absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) description y absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) +tags len0 absent START_NOT_REACHED tags (declared inert: terraform_compat) === summary OK 4 OK_RECREATE 2 -OK_INERT 18 +OK_INERT 17 BACKEND_ERROR 2 SKIPPED 1 +START_NOT_REACHED 1 diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 5c96b9c107a..a6a256554fc 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -1,15 +1,18 @@ === pipeline.yml.tmpl +deployment.version_id x absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) +deployment.version_id x y START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) +deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) dry_run absent true UPDATE_IGNORED dry_run dry_run false true UPDATE_IGNORED dry_run dry_run true absent START_NOT_REACHED dry_run dry_run true false START_NOT_REACHED dry_run === summary -OK 820 +OK 818 OK_RECREATE 33 -OK_INERT 12 +OK_INERT 9 SUPPRESSED 39 NOT_OBSERVABLE 20 UPDATE_IGNORED 2 SKIPPED 2 -START_NOT_REACHED 2 +START_NOT_REACHED 5 diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index 64d60981526..fe040fb4b8c 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -1,8 +1,8 @@ === postgres_project.yml.tmpl -budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +budget_policy_id x absent START_NOT_REACHED budget_policy_id budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... custom_tags len1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... -custom_tags len1 len0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +custom_tags len1 len0 START_NOT_REACHED custom_tags custom_tags[0].key absent x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].key absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].key x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... @@ -36,15 +36,20 @@ default_endpoint_settings.pg_settings['work_mem'] y absent BACKEND_ default_endpoint_settings.pg_settings['work_mem'] y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +display_name y absent START_NOT_REACHED display_name enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... +enable_pg_native_login true false START_NOT_REACHED enable_pg_native_login +pg_version 16 absent START_NOT_REACHED pg_version +pg_version 17 16 START_NOT_REACHED pg_version +purge_on_delete true absent START_NOT_REACHED purge_on_delete === summary -OK 32 -OK_RECREATE 8 +OK 30 +OK_RECREATE 6 SUPPRESSED 4 NOT_OBSERVABLE 3 COLLATERAL_DRIFT 1 -BACKEND_ERROR 32 +BACKEND_ERROR 29 SKIPPED 1 BASE_ERROR 6 +START_NOT_REACHED 7 diff --git a/bundle/direct/tests/output/postgres_synced_tables.txt b/bundle/direct/tests/output/postgres_synced_tables.txt index 01f1c2fa5bd..32dc99dd90a 100644 --- a/bundle/direct/tests/output/postgres_synced_tables.txt +++ b/bundle/direct/tests/output/postgres_synced_tables.txt @@ -1,6 +1,11 @@ +=== postgres_synced_table.yml.tmpl +existing_pipeline_id x absent START_NOT_REACHED existing_pipeline_id +primary_key_columns len1 absent START_NOT_REACHED primary_key_columns +timeseries_key y absent START_NOT_REACHED timeseries_key === summary -OK_RECREATE 114 +OK_RECREATE 107 SUPPRESSED 6 NOT_OBSERVABLE 4 SKIPPED 1 +START_NOT_REACHED 3 diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index 8cc54918684..02700b7452f 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -5,9 +5,9 @@ name x absent POST_DEPLOY_D name y absent POST_DEPLOY_DRIFT name === summary -OK 85 -SUPPRESSED 5 -NOT_OBSERVABLE 2 +OK 53 +SUPPRESSED 2 +NOT_OBSERVABLE 1 POST_DEPLOY_DRIFT 2 -SKIPPED 2 +SKIPPED 5 START_NOT_REACHED 2 diff --git a/bundle/direct/tests/testdata/fields/sql_warehouses.yml b/bundle/direct/tests/testdata/fields/sql_warehouses.yml index 7306a2a3fdb..0f5cce93fcf 100644 --- a/bundle/direct/tests/testdata/fields/sql_warehouses.yml +++ b/bundle/direct/tests/testdata/fields/sql_warehouses.yml @@ -1,6 +1,25 @@ # Value library for SQL warehouses. See ../../README.md. -base: - tags: - custom_tags: - - key: team - value: eng + +skip: + # Must be an ARN of an instance profile registered with the workspace. + instance_profile_arn: needs an instance profile registered with the workspace + # Only accepted on the CUSTOM channel, so the version and the channel name have to move + # together; either alone is rejected. + channel.dbsql_version: only valid on the CUSTOM channel, so it cannot move alone + # Serverless is only offered on a PRO warehouse, and the corpus config is CLASSIC, so + # this flag cannot be moved on its own -- the pair would have to change together. + enable_serverless_compute: only valid on a PRO warehouse, which the config is not + +fields: + # The backend validates these as ranges rather than accepting any integer: + # auto_stop_mins is 0 (never) or at least 10, and a cluster count is at least 1 -- + # with max_num_clusters at least 2 once it is being changed at all. + auto_stop_mins: [10, 20] + min_num_clusters: [1, 2] + max_num_clusters: [2, 3] + + # A t-shirt size, not free text. + cluster_size: [2X-Small, X-Small] + + # A tag key has to be non-empty, so "" is not one of the values to try. + tags.custom_tags[*].key: [team, owner] diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index a173efec7d0..45ed5c48ede 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -252,12 +252,19 @@ func enumValues(typ reflect.Type) []any { } } slices.Sort(all) - if len(all) < 2 { - // One value cannot show a value-to-value move, and add and remove alone would say - // nothing an enum-specific case does not already cover. + if len(all) == 0 { return nil } - return []any{reflect.ValueOf(all[0]).Convert(typ).Interface(), reflect.ValueOf(all[1]).Convert(typ).Interface()} + // One is enough: with absent in the set, the field still gets an add and a remove. There + // is no valid second value to invent, and falling back to the generic "x" and "y" would + // test values the backend rejects -- a pipeline's deployment.kind only permits BUNDLE. + all = all[:min(2, len(all))] + + values := make([]any, 0, len(all)) + for _, value := range all { + values = append(values, reflect.ValueOf(value).Convert(typ).Interface()) + } + return values } // kindValues are the fallback values for a plain Go kind. diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go index 255ad8b5b72..65ebd255949 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/tests/values_unit_test.go @@ -126,8 +126,13 @@ func TestSetFieldAbsentAndEmpty(t *testing.T) { require.NoError(t, setValue(job, "tags['env']", nil)) assert.Equal(t, map[string]string{"team": "eng"}, job.Tags) + // Only the last element can be made absent: removing an earlier one would shift its + // successor into the same path, so the field would still be there holding another value. + require.ErrorContains(t, setValue(job, "tasks[0]", nil), "would shift into it") + require.NoError(t, setValue(job, "tasks[1]", nil)) + assert.Equal(t, []jobs.Task{{TaskKey: "a"}}, job.Tasks) //exhaustruct:ignore require.NoError(t, setValue(job, "tasks[0]", nil)) - assert.Equal(t, []jobs.Task{{TaskKey: "b"}}, job.Tasks) //exhaustruct:ignore + assert.Empty(t, job.Tasks) // A list from the value library arrives as []any and has to be decoded into the // field's own element type. From e94f5feaaba09fe95f2c941b807227654cb96cd2 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 12:09:28 +0200 Subject: [PATCH 032/118] testserver: never store aliases on a registered model From the second review round: the generic PATCH handler copied any field the request body carried, so aliases sent on an update were persisted -- and the include_aliases branch added earlier in this branch then exposed them. An alias belongs to a model version and is created through its own API; neither create nor UpdateRegisteredModel honours the field, even though the request type has it. Not storing it is both simpler and what the backend does, so the include_aliases branch is gone and the report is unchanged -- it still matches AWS row for row. Co-authored-by: Isaac --- libs/testserver/handlers.go | 2 +- libs/testserver/registered_models.go | 22 +++++++--------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/libs/testserver/handlers.go b/libs/testserver/handlers.go index 6eb04074465..f366455bee0 100644 --- a/libs/testserver/handlers.go +++ b/libs/testserver/handlers.go @@ -583,7 +583,7 @@ func AddDefaultHandlers(server *Server) { // Registered Models: server.Handle("GET", "/api/2.1/unity-catalog/models/{full_name}", func(req Request) any { - return req.Workspace.RegisteredModelsGet(req, req.Vars["full_name"]) + return MapGetUC(req.Workspace, req.Workspace.RegisteredModels, req.Vars["full_name"], "Registered Model") }) server.Handle("POST", "/api/2.1/unity-catalog/models", func(req Request) any { diff --git a/libs/testserver/registered_models.go b/libs/testserver/registered_models.go index fa2e2213e49..7e7a73497f9 100644 --- a/libs/testserver/registered_models.go +++ b/libs/testserver/registered_models.go @@ -44,6 +44,7 @@ func (s *FakeWorkspace) RegisteredModelsCreate(req Request) Response { // Build full name from catalog.schema.name fullName := createRequest.CatalogName + "." + createRequest.SchemaName + "." + createRequest.Name + // Aliases are not settable here; see the note in RegisteredModelsUpdate. registeredModel := catalog.RegisteredModelInfo{ CatalogName: createRequest.CatalogName, Comment: createRequest.Comment, @@ -69,21 +70,6 @@ func (s *FakeWorkspace) RegisteredModelsCreate(req Request) Response { } } -// RegisteredModelsGet honors include_aliases, which UC defaults to false: an alias belongs -// to a model version and is managed through its own API, so a plain GET does not echo the -// aliases a create or update was given. Without this the remote appeared to hold them and a -// config that sets aliases looked like it converged. -func (s *FakeWorkspace) RegisteredModelsGet(req Request, fullName string) Response { - response := MapGetUC(s, s.RegisteredModels, fullName, "Registered Model") - model, ok := response.Body.(catalog.RegisteredModelInfo) - if !ok || req.URL.Query().Get("include_aliases") == "true" { - return response - } - model.Aliases = nil - response.Body = model - return response -} - func (s *FakeWorkspace) RegisteredModelsUpdate(req Request, fullName string) Response { defer s.LockUnlock()() @@ -118,6 +104,12 @@ func (s *FakeWorkspace) RegisteredModelsUpdate(req Request, fullName string) Res fullName = existing.CatalogName + "." + existing.SchemaName + "." + updateRequest.NewName } + // An alias belongs to a model version and is created through its own API, so neither + // create nor UpdateRegisteredModel sets one -- the request field exists but the backend + // does not honour it. Keeping whatever the model already had (nothing, since create does + // not set aliases either) is what makes a config that sets aliases report the truth. + existing.Aliases = nil + existing.UpdatedAt = nowMilli() s.RegisteredModels[fullName] = existing return Response{ From 24f1a4c15d9b9ded1d5af24625bf9c28cd086b8e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 12:57:20 +0200 Subject: [PATCH 033/118] acc: fix the invariant mutation targets Four things, all in work this branch added and none of them passing until now. apply_mutation.py and state_snapshot.py were committed without the execute bit, so every mutation run died with "Permission denied". invariant_deploy_quiet only checked its log for panics, so a deploy that failed passed silently and surfaced later as unexplained drift, with the reason in a LOG file the diff excludes. It now fails and prints the log. That exposed the postgres_endpoint mutation: the engine puts a oneof member's own name in update_mask, but the API only accepts the group name ("spec.suspension"), so the update is rejected. Both targets require the mutation to deploy, so neither can express that -- the config is excluded and the annotation left in place as the mutation to run once the mask is fixed. The same annotation also carried EXPECT: skip for a bug in diffing SDK durations that #6377 has since fixed. A config change is planned now; a remote change is still dropped on purpose, the field being declared spec:input_only, so that expectation moves to EXPECT_REMOTE_UPDATE. Co-authored-by: Isaac --- acceptance/bin/apply_mutation.py | 0 acceptance/bin/state_snapshot.py | 0 .../invariant/apply_remote_update/out.test.toml | 7 +------ .../invariant/apply_remote_update/test.toml | 7 ++++++- .../bundle/invariant/apply_update/out.test.toml | 7 +------ .../bundle/invariant/apply_update/test.toml | 7 ++++++- .../configs/postgres_endpoint.yml.tmpl | 17 ++++++++++------- acceptance/bundle/invariant/script.prepare | 9 ++++++++- 8 files changed, 32 insertions(+), 22 deletions(-) mode change 100644 => 100755 acceptance/bin/apply_mutation.py mode change 100644 => 100755 acceptance/bin/state_snapshot.py diff --git a/acceptance/bin/apply_mutation.py b/acceptance/bin/apply_mutation.py old mode 100644 new mode 100755 diff --git a/acceptance/bin/state_snapshot.py b/acceptance/bin/state_snapshot.py old mode 100644 new mode 100755 diff --git a/acceptance/bundle/invariant/apply_remote_update/out.test.toml b/acceptance/bundle/invariant/apply_remote_update/out.test.toml index 60545df787b..4ed4a6d98e8 100644 --- a/acceptance/bundle/invariant/apply_remote_update/out.test.toml +++ b/acceptance/bundle/invariant/apply_remote_update/out.test.toml @@ -1,8 +1,3 @@ Cloud = true EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] -EnvMatrix.INPUT_CONFIG = [ - "app.yml.tmpl", - "job.yml.tmpl", - "postgres_endpoint.yml.tmpl", - "schema.yml.tmpl" -] +EnvMatrix.INPUT_CONFIG = ["app.yml.tmpl", "job.yml.tmpl", "schema.yml.tmpl"] diff --git a/acceptance/bundle/invariant/apply_remote_update/test.toml b/acceptance/bundle/invariant/apply_remote_update/test.toml index 798a2a9376b..6b79f65b10a 100644 --- a/acceptance/bundle/invariant/apply_remote_update/test.toml +++ b/acceptance/bundle/invariant/apply_remote_update/test.toml @@ -6,9 +6,14 @@ EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] # Only configs carrying "# ACTION:" annotations are worth running here; without any # the target degenerates into a second copy of no_drift. Overrides the parent list. +# +# postgres_endpoint is left out: its annotated field is a oneof member, and the engine puts +# the member name in update_mask where the API only accepts the group name +# ("spec.suspension"), so the deploy is rejected. Both targets require the mutation to +# deploy, so neither can express that -- the annotation stays as the mutation to run once +# the mask is fixed. EnvMatrix.INPUT_CONFIG = [ "app.yml.tmpl", "job.yml.tmpl", - "postgres_endpoint.yml.tmpl", "schema.yml.tmpl", ] diff --git a/acceptance/bundle/invariant/apply_update/out.test.toml b/acceptance/bundle/invariant/apply_update/out.test.toml index 60545df787b..4ed4a6d98e8 100644 --- a/acceptance/bundle/invariant/apply_update/out.test.toml +++ b/acceptance/bundle/invariant/apply_update/out.test.toml @@ -1,8 +1,3 @@ Cloud = true EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] -EnvMatrix.INPUT_CONFIG = [ - "app.yml.tmpl", - "job.yml.tmpl", - "postgres_endpoint.yml.tmpl", - "schema.yml.tmpl" -] +EnvMatrix.INPUT_CONFIG = ["app.yml.tmpl", "job.yml.tmpl", "schema.yml.tmpl"] diff --git a/acceptance/bundle/invariant/apply_update/test.toml b/acceptance/bundle/invariant/apply_update/test.toml index 798a2a9376b..6b79f65b10a 100644 --- a/acceptance/bundle/invariant/apply_update/test.toml +++ b/acceptance/bundle/invariant/apply_update/test.toml @@ -6,9 +6,14 @@ EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] # Only configs carrying "# ACTION:" annotations are worth running here; without any # the target degenerates into a second copy of no_drift. Overrides the parent list. +# +# postgres_endpoint is left out: its annotated field is a oneof member, and the engine puts +# the member name in update_mask where the API only accepts the group name +# ("spec.suspension"), so the deploy is rejected. Both targets require the mutation to +# deploy, so neither can express that -- the annotation stays as the mutation to run once +# the mask is fixed. EnvMatrix.INPUT_CONFIG = [ "app.yml.tmpl", "job.yml.tmpl", - "postgres_endpoint.yml.tmpl", "schema.yml.tmpl", ] diff --git a/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl b/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl index 94104effb10..b9096d552cf 100644 --- a/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl +++ b/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl @@ -19,10 +19,13 @@ resources: endpoint_id: test-endpoint-$UNIQUE_NAME endpoint_type: ENDPOINT_TYPE_READ_WRITE # Mutations for apply_update / apply_remote_update; see acceptance/bin/apply_mutation.py. - # BUG: the change is detected (old "300s", new "600s") but then suppressed with - # reason "empty". isEmptyStruct in bundle/direct/bundle_plan.go only looks at - # exported fields, and SDK duration.Duration wraps a single unexported - # *durationpb.Duration -- so every value of it looks like an empty struct and - # allEmpty(old, new) is always true. Expecting "skip" locks in the broken - # behaviour; flip to non-skip once isEmptyStruct handles these SDK types. - suspend_timeout_duration: 300s # ACTION: SET 600s EXPECT: skip + # The mutation apply_update and apply_remote_update would run, if they could: the + # engine puts this field's own name in update_mask, but it is a oneof member and the + # API only accepts the group name ("spec.suspension"), so the update is rejected with + # "Unknown field path in update_mask". Both targets therefore exclude this config; see + # their test.toml. + # + # The planning half does work: an SDK duration used to look like an empty struct to + # isEmptyStruct, so every change to one was suppressed with reason "empty", until #6377. + # A remote change is still dropped on purpose, the field being declared spec:input_only. + suspend_timeout_duration: 300s # ACTION: SET 600s EXPECT_REMOTE_UPDATE: skip diff --git a/acceptance/bundle/invariant/script.prepare b/acceptance/bundle/invariant/script.prepare index b08007028cf..5723191f916 100644 --- a/acceptance/bundle/invariant/script.prepare +++ b/acceptance/bundle/invariant/script.prepare @@ -78,7 +78,14 @@ invariant_verify_mutation() { # base deploy that runs before any mutation. invariant_deploy_quiet() { local logfile="$1" - trace $CLI bundle deploy &> "$logfile" + # Not just no panic: a deploy that fails here would otherwise pass silently and surface + # later as unexplained drift, since the remote never moved. On failure the log is the + # only record of why, so print it. + if ! trace $CLI bundle deploy &> "$logfile"; then + echo "deploy failed:" + cat "$logfile" + return 1 + fi cat "$logfile" | contains.py '!panic:' '!internal error' > /dev/null } From 751753437abe8b4f1347c1efb561aba6753abd34 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 13:08:51 +0200 Subject: [PATCH 034/118] Field catalog: four fixes from the third review round - A field that names the resource was given the generic "x" and "y", so two runs against one workspace asked for the same name and the second got "already exists" -- which is what several sql_warehouses and experiments rows on AWS actually were. Such values now carry the run's unique suffix, redacted in the report so the golden stays stable. A field counts as naming the resource when the resource declares it as part of its id, or when the corpus config's own value for it carries the suffix: a warehouse name is the second kind, since the id is a generated uuid yet the name must still be free. - The corpus report listed every usable config as covered, though only the simplest one per resource type is driven. The others are now listed as not covered, naming the one that won. - baselineDrift returned no drift when its plan failed, leaving every later verdict measured against an unknown baseline -- pre-existing drift would be blamed on whichever transition ran. It is now a PLAN_ERROR for the config. - Seeded cluster values hard-coded an AWS node type and an S3 init script, which would fail before any field ran on Azure or GCP. They use the same variables the corpus configs do, and the S3 init script is gone: the workspace member already covers the union, and every other member names cloud-specific storage. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 6 ++ bundle/direct/tests/fields_test.go | 39 +++++++++---- bundle/direct/tests/harness_test.go | 5 ++ bundle/direct/tests/output/apps.txt | 12 ++-- bundle/direct/tests/output/clusters.txt | 2 +- bundle/direct/tests/output/configs.txt | 32 +++++----- bundle/direct/tests/output/dashboards.txt | 20 +++---- bundle/direct/tests/output/genie_spaces.txt | 20 +++---- bundle/direct/tests/output/pipelines.txt | 2 +- .../direct/tests/output/registered_models.txt | 16 ++--- bundle/direct/tests/output/sql_warehouses.txt | 34 ++++++++--- bundle/direct/tests/report_test.go | 23 ++++---- .../direct/tests/testdata/fields/clusters.yml | 8 +-- bundle/direct/tests/testdata/fields/jobs.yml | 2 +- .../tests/testdata/fields/pipelines.yml | 8 +-- .../tests/testdata/fields/sql_warehouses.yml | 12 +++- bundle/direct/tests/values_test.go | 58 ++++++++++++++++++- 17 files changed, 202 insertions(+), 97 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index 4d22918d7a6..dc18941e62e 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -118,6 +118,12 @@ per-config cloud exclusions in `acceptance/bundle/invariant/test.toml`. A `skip` key may be a pattern (`aliases[*].id`), matched the way the planner matches its own field rules, and naming a block skips everything beneath it. +A field that names the resource gets the run's own unique suffix appended to its values, so +two runs against one workspace never ask for the same name — the second would get "already +exists", which says nothing about the engine. Such a field is either declared as part of the +resource's id, or the corpus config's own value for it carries the run's suffix. The suffix is +redacted in the report, so the golden is the same on every run. + `base` is expanded with the same `$VARS` the corpus configs use — `$CURRENT_USER_NAME`, `$UNIQUE_NAME`, `$NODE_TYPE_ID` and the rest — so a seeded value can name the workspace's own user rather than a placeholder only the fake server knows. diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index d3d8a297f85..73219a1d7c9 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -75,8 +75,14 @@ func TestFields(t *testing.T) { } byType[c.resourceType] = c } + // Whatever lost the pick is not covered, and saying so is the point of the report. + for _, c := range usable { + if winner := byType[c.resourceType]; winner.name != c.name { + skipped[c.name] = "a simpler config of this type is driven instead: " + winner.name + } + } - writeCorpusReport(t, usable, skipped) + writeCorpusReport(t, byType, skipped) for resourceType, cfg := range byType { t.Run(resourceType, func(t *testing.T) { @@ -129,7 +135,8 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl base := h.snapshot() resource, err := h.resource() require.NoError(t, err) - fields, uncovered, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, resource, runSeed, declaredUnsettable(adapter)) + fields, uncovered, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, resource, runSeed, + declaredUnsettable(adapter), declaredIdentity(adapter), h.unique) rep.addCoverage(fields, uncovered) // Fields the resource declares a user cannot meaningfully set. Recorded with the @@ -151,7 +158,11 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // echoes diffs forever. Measure that once, against the field that causes it, and leave // it out of every transition's drift -- otherwise it dirties every post-deploy plan and // gets blamed on whichever field happened to be under test. - baseline := baselineDrift(h) + baseline, err := baselineDrift(h) + if err != nil { + rep.add(result{cfg.name, "(baseline)", "", "", verdictPlanError, oneLine(err.Error()), err.Error()}) + return + } for _, path := range slices.Sorted(maps.Keys(baseline)) { rep.add(result{cfg.name, path, "", "", verdictBaselineDrift, "drifts with no config change", ""}) } @@ -440,7 +451,9 @@ func explainSkip(plan *deployplan.Plan, node, path string) (verdict, string) { } // valueLabel renders a value for the subtest name and the report. A slice or map is -// labelled by size, since the point of testing one is how many entries it has. +// labelled by size, since the point of testing one is how many entries it has. An identity +// field's value carries the run's own suffix, which is redacted here so the label -- and so +// the golden -- is the same on every run. func valueLabel(v any) string { if v == nil { return "absent" @@ -452,7 +465,7 @@ func valueLabel(v any) string { return "keys" + strconv.Itoa(value.Len()) default: } - switch s := fmt.Sprintf("%v", v); s { + switch s := oneLine(fmt.Sprintf("%v", v)); s { case "": return "empty" default: @@ -544,14 +557,20 @@ func checkDeclaredInert(res result, rules []dresources.FieldRule) result { // baselineDrift returns the fields the resource already wants to change with the config // exactly as deployed. -func baselineDrift(h *bundleHarness) map[string]bool { +func baselineDrift(h *bundleHarness) (map[string]bool, error) { plan, diags := h.readPlan() - if diags.HasError() || plan == nil { - return nil + if diags.HasError() { + // Returning no drift would leave every later verdict measured against an unknown + // baseline: a field that was already drifting would be blamed on whichever + // transition happened to run. + return nil, errors.New(firstError(diags)) + } + if plan == nil { + return nil, errors.New("no plan") } entry, ok := plan.Plan[h.node] if !ok { - return nil + return nil, nil } out := map[string]bool{} for path, change := range entry.Changes { @@ -559,7 +578,7 @@ func baselineDrift(h *bundleHarness) map[string]bool { out[path] = true } } - return out + return out, nil } // wasIgnored reports whether the write for one field was accepted and then had no effect: diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index a208d34e431..b9005a14713 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -47,6 +47,10 @@ type bundleHarness struct { bundle *bundle.Bundle node string // "resources.schemas.foo" statePath string + + // unique is the suffix this harness gave its resource, reused for the values of identity + // fields so no two runs against one workspace ask for the same name. + unique string } func newClient(t *testing.T) *databricks.WorkspaceClient { @@ -263,6 +267,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC bundle: b, node: node, statePath: filepath.Join(dir, "resources.json"), + unique: uniqueName, } if base != nil { diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 87ad18aee8f..60845221160 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -56,12 +56,12 @@ resources[0].postgres.branch y absent UPDATE_IGNORE resources[0].postgres.database x absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.database y absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.permission CAN_CONNECT_AND_CREATE absent UPDATE_IGNORED resources[0].postgres.permission -source_code_path absent x SUPPRESSED no active deployment -source_code_path absent y SUPPRESSED no active deployment -source_code_path x absent START_NOT_REACHED source_code_path -source_code_path x y START_NOT_REACHED source_code_path -source_code_path y absent START_NOT_REACHED source_code_path -source_code_path y x START_NOT_REACHED source_code_path +source_code_path absent x-[UNIQUE_NAME] SUPPRESSED no active deployment +source_code_path absent y-[UNIQUE_NAME] SUPPRESSED no active deployment +source_code_path x-[UNIQUE_NAME] absent START_NOT_REACHED source_code_path +source_code_path x-[UNIQUE_NAME] y-[UNIQUE_NAME] START_NOT_REACHED source_code_path +source_code_path y-[UNIQUE_NAME] absent START_NOT_REACHED source_code_path +source_code_path y-[UNIQUE_NAME] x-[UNIQUE_NAME] START_NOT_REACHED source_code_path space y x START_NOT_REACHED space usage_policy_id y absent UPDATE_IGNORED usage_policy_id user_api_scopes len2 absent UPDATE_IGNORED user_api_scopes diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/tests/output/clusters.txt index b086175dfd1..d0d01becf4a 100644 --- a/bundle/direct/tests/output/clusters.txt +++ b/bundle/direct/tests/output/clusters.txt @@ -1,6 +1,6 @@ === summary -OK 499 +OK 449 SUPPRESSED 23 NOT_OBSERVABLE 12 SKIPPED 1 diff --git a/bundle/direct/tests/output/configs.txt b/bundle/direct/tests/output/configs.txt index 5a415688616..bfc63525f72 100644 --- a/bundle/direct/tests/output/configs.txt +++ b/bundle/direct/tests/output/configs.txt @@ -2,10 +2,8 @@ alert.yml.tmpl alerts app.yml.tmpl apps catalog.yml.tmpl catalogs -catalog_optional_fields.yml.tmpl catalogs -cluster.yml.tmpl clusters -cluster_apply_policy_default_values.yml.tmpl clusters cluster_policy.yml.tmpl cluster_policies +cluster.yml.tmpl clusters dashboard.yml.tmpl dashboards database_instance.yml.tmpl database_instances experiment.yml.tmpl experiments @@ -13,48 +11,50 @@ external_location.yml.tmpl external_locations genie_space.yml.tmpl genie_spaces instance_pool.yml.tmpl instance_pools job.yml.tmpl jobs -job_apply_policy_default_values_for_each_task.yml.tmpl jobs -job_apply_policy_default_values_job_cluster.yml.tmpl jobs -job_apply_policy_default_values_task_cluster.yml.tmpl jobs -job_with_depends_on.yml.tmpl jobs -job_with_task.yml.tmpl jobs -model.yml.tmpl models model_serving_endpoint.yml.tmpl model_serving_endpoints -model_serving_endpoint_telemetry.yml.tmpl model_serving_endpoints +model.yml.tmpl models pipeline.yml.tmpl pipelines -pipeline_allow_duplicate_names.yml.tmpl pipelines -pipeline_apply_policy_default_values.yml.tmpl pipelines postgres_project.yml.tmpl postgres_projects postgres_synced_table.yml.tmpl postgres_synced_tables registered_model.yml.tmpl registered_models schema.yml.tmpl schemas -schema_empty_grants.yml.tmpl schemas -schema_uppercase_name.yml.tmpl schemas secret_scope.yml.tmpl secret_scopes -secret_scope_default_backend_type.yml.tmpl secret_scopes sql_warehouse.yml.tmpl sql_warehouses volume.yml.tmpl volumes -volume_external.yml.tmpl volumes === not covered app_lifecycle_ref.yml.tmpl has 2 resources +catalog_optional_fields.yml.tmpl a simpler config of this type is driven instead: catalog.yml.tmpl +cluster_apply_policy_default_values.yml.tmpl a simpler config of this type is driven instead: cluster.yml.tmpl database_catalog.yml.tmpl has 2 resources +job_apply_policy_default_values_for_each_task.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl +job_apply_policy_default_values_job_cluster.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl +job_apply_policy_default_values_task_cluster.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl job_cross_resource_ref.yml.tmpl has 3 resources job_permission_ref.yml.tmpl has 2 resources job_pydabs_1000_tasks.yml.tmpl has -init.sh job_pydabs_10_tasks.yml.tmpl has -init.sh job_run.yml.tmpl has 2 resources job_run_job_ref.yml.tmpl has 2 resources +job_with_depends_on.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl +job_with_task.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl +model_serving_endpoint_telemetry.yml.tmpl a simpler config of this type is driven instead: model_serving_endpoint.yml.tmpl +pipeline_allow_duplicate_names.yml.tmpl a simpler config of this type is driven instead: pipeline.yml.tmpl +pipeline_apply_policy_default_values.yml.tmpl a simpler config of this type is driven instead: pipeline.yml.tmpl pipeline_config_dots.yml.tmpl has 2 resources postgres_branch.yml.tmpl has 2 resources postgres_catalog.yml.tmpl has 2 resources postgres_database.yml.tmpl has 4 resources postgres_endpoint.yml.tmpl has 3 resources postgres_role.yml.tmpl has 3 resources +schema_empty_grants.yml.tmpl a simpler config of this type is driven instead: schema.yml.tmpl schema_grant_ref.yml.tmpl has 2 resources +schema_uppercase_name.yml.tmpl a simpler config of this type is driven instead: schema.yml.tmpl secret.yml.tmpl has -init.sh +secret_scope_default_backend_type.yml.tmpl a simpler config of this type is driven instead: secret_scope.yml.tmpl synced_database_table.yml.tmpl has -init.sh vector_search_endpoint.yml.tmpl has 2 resources vector_search_index.yml.tmpl has -init.sh +volume_external.yml.tmpl a simpler config of this type is driven instead: volume.yml.tmpl volume_path_job_ref.yml.tmpl has 3 resources volume_uppercase_name.yml.tmpl has 2 resources diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index d5ed133332e..6a5494e53ba 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -3,22 +3,22 @@ dataset_catalog y absent START_NOT_REA dataset_schema x y START_NOT_REACHED dataset_schema dataset_schema y absent START_NOT_REACHED dataset_schema dataset_schema y x START_NOT_REACHED dataset_schema -display_name absent x START_NOT_REACHED display_name -display_name absent y START_NOT_REACHED display_name -display_name x absent UPDATE_IGNORED display_name -display_name y absent UPDATE_IGNORED display_name +display_name absent x-[UNIQUE_NAME] START_NOT_REACHED display_name +display_name absent y-[UNIQUE_NAME] START_NOT_REACHED display_name +display_name x-[UNIQUE_NAME] absent UPDATE_IGNORED display_name +display_name y-[UNIQUE_NAME] absent UPDATE_IGNORED display_name etag absent x START_NOT_REACHED etag etag absent y START_NOT_REACHED etag etag x absent START_NOT_REACHED etag etag x y START_NOT_REACHED etag etag y absent START_NOT_REACHED etag etag y x START_NOT_REACHED etag -parent_path absent x START_NOT_REACHED parent_path -parent_path absent y START_NOT_REACHED parent_path -parent_path x absent START_NOT_REACHED parent_path -parent_path x y START_NOT_REACHED parent_path -parent_path y absent START_NOT_REACHED parent_path -parent_path y x START_NOT_REACHED parent_path +parent_path absent x-[UNIQUE_NAME] START_NOT_REACHED parent_path +parent_path absent y-[UNIQUE_NAME] START_NOT_REACHED parent_path +parent_path x-[UNIQUE_NAME] absent START_NOT_REACHED parent_path +parent_path x-[UNIQUE_NAME] y-[UNIQUE_NAME] START_NOT_REACHED parent_path +parent_path y-[UNIQUE_NAME] absent START_NOT_REACHED parent_path +parent_path y-[UNIQUE_NAME] x-[UNIQUE_NAME] START_NOT_REACHED parent_path === summary OK 20 diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index c053c7c237c..6d1ef416036 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,15 +1,15 @@ === genie_space.yml.tmpl description x absent UPDATE_IGNORED description -parent_path absent x START_NOT_REACHED parent_path -parent_path absent y START_NOT_REACHED parent_path -parent_path x absent START_NOT_REACHED parent_path -parent_path x y START_NOT_REACHED parent_path -parent_path y absent START_NOT_REACHED parent_path -parent_path y x START_NOT_REACHED parent_path -title absent x START_NOT_REACHED title -title absent y START_NOT_REACHED title -title x absent UPDATE_IGNORED title -title y absent UPDATE_IGNORED title +parent_path absent x-[UNIQUE_NAME] START_NOT_REACHED parent_path +parent_path absent y-[UNIQUE_NAME] START_NOT_REACHED parent_path +parent_path x-[UNIQUE_NAME] absent START_NOT_REACHED parent_path +parent_path x-[UNIQUE_NAME] y-[UNIQUE_NAME] START_NOT_REACHED parent_path +parent_path y-[UNIQUE_NAME] absent START_NOT_REACHED parent_path +parent_path y-[UNIQUE_NAME] x-[UNIQUE_NAME] START_NOT_REACHED parent_path +title absent x-[UNIQUE_NAME] START_NOT_REACHED title +title absent y-[UNIQUE_NAME] START_NOT_REACHED title +title x-[UNIQUE_NAME] absent UPDATE_IGNORED title +title y-[UNIQUE_NAME] absent UPDATE_IGNORED title warehouse_id absent x START_NOT_REACHED warehouse_id warehouse_id absent y START_NOT_REACHED warehouse_id warehouse_id x absent UPDATE_IGNORED warehouse_id diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index a6a256554fc..20db49dbc0b 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -8,7 +8,7 @@ dry_run true absent START_NOT_REA dry_run true false START_NOT_REACHED dry_run === summary -OK 818 +OK 768 OK_RECREATE 33 OK_INERT 9 SUPPRESSED 39 diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 08ee9c54c33..143e09e1dab 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,24 +1,24 @@ === registered_model.yml.tmpl -catalog_name absent main BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) -catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) +catalog_name absent main-[UNIQUE_NAME] BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) +catalog_name main-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) full_name absent x UPDATE_IGNORED full_name full_name absent y UPDATE_IGNORED full_name full_name x absent START_NOT_REACHED full_name full_name x y START_NOT_REACHED full_name full_name y absent START_NOT_REACHED full_name full_name y x START_NOT_REACHED full_name -name absent x BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name absent y BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name x absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name y absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name absent x-[UNIQUE_NAME] BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name absent y-[UNIQUE_NAME] BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name x-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name y-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) owner absent x UPDATE_IGNORED owner owner absent y UPDATE_IGNORED owner owner x absent START_NOT_REACHED owner owner x y START_NOT_REACHED owner owner y absent START_NOT_REACHED owner owner y x START_NOT_REACHED owner -schema_name absent default BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) -schema_name default absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) +schema_name absent default-[UNIQUE_NAME] BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) +schema_name default-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) === summary OK 40 diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index 02700b7452f..bafd55aba2a 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -1,13 +1,31 @@ === sql_warehouse.yml.tmpl -name absent x START_NOT_REACHED name -name absent y START_NOT_REACHED name -name x absent POST_DEPLOY_DRIFT name -name y absent POST_DEPLOY_DRIFT name +auto_stop_mins 10 absent UPDATE_IGNORED auto_stop_mins +auto_stop_mins 20 absent UPDATE_IGNORED auto_stop_mins +auto_stop_mins absent 10 START_NOT_REACHED auto_stop_mins +auto_stop_mins absent 20 START_NOT_REACHED auto_stop_mins +cluster_size 2X-Small absent UPDATE_IGNORED cluster_size +cluster_size X-Small absent UPDATE_IGNORED cluster_size +cluster_size absent 2X-Small START_NOT_REACHED cluster_size +cluster_size absent X-Small START_NOT_REACHED cluster_size +enable_photon absent false START_NOT_REACHED enable_photon +enable_photon true absent UPDATE_IGNORED enable_photon +max_num_clusters 2 absent UPDATE_IGNORED max_num_clusters +max_num_clusters 3 absent UPDATE_IGNORED max_num_clusters +max_num_clusters absent 2 START_NOT_REACHED max_num_clusters +max_num_clusters absent 3 START_NOT_REACHED max_num_clusters +name absent x-[UNIQUE_NAME] START_NOT_REACHED name +name absent y-[UNIQUE_NAME] START_NOT_REACHED name +name x-[UNIQUE_NAME] absent UPDATE_IGNORED name +name y-[UNIQUE_NAME] absent UPDATE_IGNORED name +spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy +spot_instance_policy POLICY_UNSPECIFIED absent UPDATE_IGNORED spot_instance_policy +spot_instance_policy absent COST_OPTIMIZED START_NOT_REACHED spot_instance_policy +spot_instance_policy absent POLICY_UNSPECIFIED START_NOT_REACHED spot_instance_policy === summary -OK 53 -SUPPRESSED 2 +OK 32 +SUPPRESSED 1 NOT_OBSERVABLE 1 -POST_DEPLOY_DRIFT 2 +UPDATE_IGNORED 11 SKIPPED 5 -START_NOT_REACHED 2 +START_NOT_REACHED 11 diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index e2cf2edbf87..5fd8e795f06 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io/fs" + "maps" "os" "path/filepath" "slices" @@ -369,20 +370,22 @@ func writeReport(t testutil.TestingT, name, body string) { } } -// writeCorpusReport records which configs the suite drives and why each of the others -// is left out, so the catalog accounts for the whole corpus rather than just the part -// it happens to cover. -func writeCorpusReport(t testutil.TestingT, usable []testConfig, skipped map[string]string) { - var sb strings.Builder - sb.WriteString("=== covered\n") - for _, c := range usable { - fmt.Fprintf(&sb, "%-48s %s\n", c.name, c.resourceType) - } - +// writeCorpusReport records which configs the suite drives and why each of the others is +// left out, so the catalog accounts for the whole corpus rather than just the part it +// happens to cover. Only one config per resource type is driven, so a corpus config that +// merely lost the pick is listed as not covered too -- naming the one that won. +func writeCorpusReport(t testutil.TestingT, driven map[string]testConfig, skipped map[string]string) { names := make([]string, 0, len(skipped)) for name := range skipped { names = append(names, name) } + + var sb strings.Builder + sb.WriteString("=== covered\n") + for _, resourceType := range slices.Sorted(maps.Keys(driven)) { + fmt.Fprintf(&sb, "%-48s %s\n", driven[resourceType].name, resourceType) + } + slices.Sort(names) fmt.Fprintf(&sb, "\n=== not covered\n") for _, name := range names { diff --git a/bundle/direct/tests/testdata/fields/clusters.yml b/bundle/direct/tests/testdata/fields/clusters.yml index 7eff4a217ad..1182d5b9209 100644 --- a/bundle/direct/tests/testdata/fields/clusters.yml +++ b/bundle/direct/tests/testdata/fields/clusters.yml @@ -1,8 +1,9 @@ # Value library for clusters. See ../../README.md. # init_scripts is a union: an entry carries exactly one of workspace, volumes, s3, abfss, -# gcs, dbfs or file. Two entries make the two most-configured members reachable; the rest -# stay uncovered for this config, which the report says. +# gcs, dbfs or file. Only the workspace member is seeded -- every other one names +# cloud-specific storage, and the suite runs against all three clouds -- so the rest stay +# uncovered for this config, which the report says. base: custom_tags: team: eng @@ -15,9 +16,6 @@ base: init_scripts: - workspace: destination: /Users/$CURRENT_USER_NAME/init.sh - - s3: - destination: s3://bucket/init.sh - region: us-east-1 worker_node_type_flexibility: alternate_node_type_ids: - i3.2xlarge diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml index e7f31507069..1044d26a8e1 100644 --- a/bundle/direct/tests/testdata/fields/jobs.yml +++ b/bundle/direct/tests/testdata/fields/jobs.yml @@ -20,7 +20,7 @@ base: - job_cluster_key: seeded new_cluster: spark_version: 13.3.x-scala2.12 - node_type_id: i3.xlarge + node_type_id: $NODE_TYPE_ID num_workers: 1 custom_tags: team: eng diff --git a/bundle/direct/tests/testdata/fields/pipelines.yml b/bundle/direct/tests/testdata/fields/pipelines.yml index 0facd8f18b4..4b2db2deb9c 100644 --- a/bundle/direct/tests/testdata/fields/pipelines.yml +++ b/bundle/direct/tests/testdata/fields/pipelines.yml @@ -1,11 +1,12 @@ # Value library for pipelines. See ../../README.md. # No pipeline config in the corpus declares a clusters block, so its whole spec -- 346 -# fields, including the init_scripts union and the tag maps -- would go untested. +# fields, including the init_scripts union and the tag maps -- would go untested. Values +# that differ per cloud come from the same variables the corpus configs use. base: clusters: - label: default - node_type_id: i3.xlarge + node_type_id: $NODE_TYPE_ID num_workers: 1 custom_tags: team: eng @@ -18,9 +19,6 @@ base: init_scripts: - workspace: destination: /Users/$CURRENT_USER_NAME/init.sh - - s3: - destination: s3://bucket/init.sh - region: us-east-1 configuration: my.key: value environment: diff --git a/bundle/direct/tests/testdata/fields/sql_warehouses.yml b/bundle/direct/tests/testdata/fields/sql_warehouses.yml index 0f5cce93fcf..9cf8e365fb8 100644 --- a/bundle/direct/tests/testdata/fields/sql_warehouses.yml +++ b/bundle/direct/tests/testdata/fields/sql_warehouses.yml @@ -12,14 +12,20 @@ skip: fields: # The backend validates these as ranges rather than accepting any integer: - # auto_stop_mins is 0 (never) or at least 10, and a cluster count is at least 1 -- - # with max_num_clusters at least 2 once it is being changed at all. + # auto_stop_mins is 0 (never) or at least 10, and max_num_clusters is at least 2 once it + # is being changed at all. min_num_clusters cannot exceed max_num_clusters, which the + # corpus config sets to 1, so 1 is its only deployable value -- with absent in the set + # that is still an add and a remove. auto_stop_mins: [10, 20] - min_num_clusters: [1, 2] + min_num_clusters: [1] max_num_clusters: [2, 3] # A t-shirt size, not free text. cluster_size: [2X-Small, X-Small] + # CHANNEL_NAME_CUSTOM is only accepted together with a dbsql_version, so the two would + # have to move together; the other names stand alone. + channel.name: [CHANNEL_NAME_CURRENT, CHANNEL_NAME_PREVIEW] + # A tag key has to be non-empty, so "" is not one of the values to try. tags.custom_tags[*].key: [team, owner] diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 45ed5c48ede..5cb445e9ae2 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -86,6 +86,20 @@ func declaredUnsettable(adapter *dresources.Adapter) []dresources.FieldRule { return rules } +// declaredIdentity collects the fields that compose the resource's name-based id, whether a +// change to one recreates it or renames it in place. A value for such a field has to be +// unique to the run: two runs against the same workspace, or a run whose earlier resources +// have not been cleaned up yet, would otherwise both ask for the same name and the second +// gets "already exists" -- a finding about this suite rather than about the engine. +func declaredIdentity(adapter *dresources.Adapter) []dresources.FieldRule { + var rules []dresources.FieldRule + for _, cfg := range lifecycleConfigs(adapter) { + rules = append(rules, cfg.ProvidedIDFields...) + rules = append(rules, cfg.UpdatableIDFields...) + } + return rules +} + // declaredIgnoredLocally collects the fields a resource says it drops local changes to. // These are *not* skipped: the declaration is a claim about behaviour, and the suite is in a // position to check it. A transition of one should come back suppressed with that same @@ -443,7 +457,7 @@ func isRequired(resourceType, path string) bool { // "tasks[*].description" is expanded to the indices that exist, so the fields inside an // element get the same treatment as any other. A pattern with nothing behind it in the // config is reported as not covered rather than silently tested against nothing. -func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, resource any, runSeed uint64, unsettable []dresources.FieldRule) (fields []field, uncovered []string, inertFields map[string]string) { +func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, resource any, runSeed uint64, unsettable, identity []dresources.FieldRule, unique string) (fields []field, uncovered []string, inertFields map[string]string) { inertFields = map[string]string{} add := func(path string, kind reflect.Kind, values []any) { if _, skipped := fv.skipReason(path); skipped { @@ -517,7 +531,7 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue if values == nil { values = defaultValues(typ) } - add(path, typ.Kind(), values) + add(path, typ.Kind(), uniqueIdentityValues(values, path, identity, unique, resource)) return false } @@ -540,7 +554,7 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue if values == nil { values = defaultValues(typ) } - add(c, typ.Kind(), values) + add(c, typ.Kind(), uniqueIdentityValues(values, path, identity, unique, resource)) } return false }) @@ -555,6 +569,44 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue return fields, slices.Compact(uncovered), inertFields } +// uniqueIdentityValues appends the run's own suffix to a string value of a field that names +// the resource, so no two runs against one workspace ask for the same name: the second would +// get "already exists", which says nothing about the engine. +// +// Two things mark such a field. The resource may declare it as part of its id. Failing that, +// the corpus config's own value for it carries the run's unique suffix -- which is the config +// author saying the value has to be unique, whether or not it is the id. A warehouse name is +// the second kind: the id is a generated uuid, yet the name still has to be free. +// +// The report stays stable because the suffix is redacted the way every other generated id is. +func uniqueIdentityValues(values []any, path string, identity []dresources.FieldRule, unique string, resource any) []any { + if !isIdentityField(path, identity, unique, resource) { + return values + } + out := make([]any, 0, len(values)) + for _, value := range values { + text, ok := value.(string) + if !ok || text == "" { + // Only a name can carry a suffix; an id field of another type keeps its value. + return values + } + out = append(out, text+"-"+unique) + } + return out +} + +func isIdentityField(path string, identity []dresources.FieldRule, unique string, resource any) bool { + if _, ok := ruleReason(identity, path); ok { + return true + } + current, err := structaccess.GetByString(resource, path) + if err != nil { + return false + } + text, ok := current.(string) + return ok && strings.Contains(text, unique) +} + // isNotUserSettable reports whether the bundle marks a field as something the user // never writes: an output the CLI fills in (id, url) or an internal bookkeeping field. func isNotUserSettable(sf *reflect.StructField) bool { From c19802f41710b9b1cce82baa369f573b94fb568f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 13:18:31 +0200 Subject: [PATCH 035/118] Field catalog: only the config's own value marks a field as naming the resource Caught by re-running against AWS: I had also treated the resource's declared provided_id_fields as names needing the run's unique suffix, but those include the catalog and schema a registered model lives in -- which name *other* objects, so "main" became "main-" and no such catalog exists. The evidence-based half of the rule is the sound one on its own: the corpus config templating a value with $UNIQUE_NAME is the author saying it has to be unique. It is also the broader one where it matters, since a warehouse name must be free even though the id is a generated uuid. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 2 +- .../direct/tests/output/registered_models.txt | 8 ++-- bundle/direct/tests/values_test.go | 38 ++++++------------- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 73219a1d7c9..1317c5f990b 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -136,7 +136,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl resource, err := h.resource() require.NoError(t, err) fields, uncovered, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, resource, runSeed, - declaredUnsettable(adapter), declaredIdentity(adapter), h.unique) + declaredUnsettable(adapter), h.unique) rep.addCoverage(fields, uncovered) // Fields the resource declares a user cannot meaningfully set. Recorded with the diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 143e09e1dab..0f86ad7a604 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,6 +1,6 @@ === registered_model.yml.tmpl -catalog_name absent main-[UNIQUE_NAME] BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) -catalog_name main-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) +catalog_name absent main BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) +catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) full_name absent x UPDATE_IGNORED full_name full_name absent y UPDATE_IGNORED full_name full_name x absent START_NOT_REACHED full_name @@ -17,8 +17,8 @@ owner x absent START_NOT_REA owner x y START_NOT_REACHED owner owner y absent START_NOT_REACHED owner owner y x START_NOT_REACHED owner -schema_name absent default-[UNIQUE_NAME] BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) -schema_name default-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) +schema_name absent default BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) +schema_name default absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) === summary OK 40 diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 5cb445e9ae2..20e096c1bff 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -86,20 +86,6 @@ func declaredUnsettable(adapter *dresources.Adapter) []dresources.FieldRule { return rules } -// declaredIdentity collects the fields that compose the resource's name-based id, whether a -// change to one recreates it or renames it in place. A value for such a field has to be -// unique to the run: two runs against the same workspace, or a run whose earlier resources -// have not been cleaned up yet, would otherwise both ask for the same name and the second -// gets "already exists" -- a finding about this suite rather than about the engine. -func declaredIdentity(adapter *dresources.Adapter) []dresources.FieldRule { - var rules []dresources.FieldRule - for _, cfg := range lifecycleConfigs(adapter) { - rules = append(rules, cfg.ProvidedIDFields...) - rules = append(rules, cfg.UpdatableIDFields...) - } - return rules -} - // declaredIgnoredLocally collects the fields a resource says it drops local changes to. // These are *not* skipped: the declaration is a claim about behaviour, and the suite is in a // position to check it. A transition of one should come back suppressed with that same @@ -457,7 +443,7 @@ func isRequired(resourceType, path string) bool { // "tasks[*].description" is expanded to the indices that exist, so the fields inside an // element get the same treatment as any other. A pattern with nothing behind it in the // config is reported as not covered rather than silently tested against nothing. -func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, resource any, runSeed uint64, unsettable, identity []dresources.FieldRule, unique string) (fields []field, uncovered []string, inertFields map[string]string) { +func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, resource any, runSeed uint64, unsettable []dresources.FieldRule, unique string) (fields []field, uncovered []string, inertFields map[string]string) { inertFields = map[string]string{} add := func(path string, kind reflect.Kind, values []any) { if _, skipped := fv.skipReason(path); skipped { @@ -531,7 +517,7 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue if values == nil { values = defaultValues(typ) } - add(path, typ.Kind(), uniqueIdentityValues(values, path, identity, unique, resource)) + add(path, typ.Kind(), uniqueIdentityValues(values, path, unique, resource)) return false } @@ -554,7 +540,7 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue if values == nil { values = defaultValues(typ) } - add(c, typ.Kind(), uniqueIdentityValues(values, path, identity, unique, resource)) + add(c, typ.Kind(), uniqueIdentityValues(values, path, unique, resource)) } return false }) @@ -573,14 +559,15 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue // the resource, so no two runs against one workspace ask for the same name: the second would // get "already exists", which says nothing about the engine. // -// Two things mark such a field. The resource may declare it as part of its id. Failing that, -// the corpus config's own value for it carries the run's unique suffix -- which is the config -// author saying the value has to be unique, whether or not it is the id. A warehouse name is -// the second kind: the id is a generated uuid, yet the name still has to be free. +// Such a field is recognised by the corpus config templating its value with $UNIQUE_NAME, +// which is the config author saying the value has to be unique. That is a narrower rule than +// the resource's own provided_id_fields, deliberately: those include the catalog and schema a +// registered model lives in, which name *other* objects and must be left alone -- while a +// warehouse name has to be unique despite the id being a generated uuid. // // The report stays stable because the suffix is redacted the way every other generated id is. -func uniqueIdentityValues(values []any, path string, identity []dresources.FieldRule, unique string, resource any) []any { - if !isIdentityField(path, identity, unique, resource) { +func uniqueIdentityValues(values []any, path, unique string, resource any) []any { + if !namesThisResource(path, unique, resource) { return values } out := make([]any, 0, len(values)) @@ -595,10 +582,7 @@ func uniqueIdentityValues(values []any, path string, identity []dresources.Field return out } -func isIdentityField(path string, identity []dresources.FieldRule, unique string, resource any) bool { - if _, ok := ruleReason(identity, path); ok { - return true - } +func namesThisResource(path, unique string, resource any) bool { current, err := structaccess.GetByString(resource, path) if err != nil { return false From b1925a5fb2cd909c8a05cbe2a2b84e512d8b6ef0 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 13:25:24 +0200 Subject: [PATCH 036/118] Field catalog: three fixes from the fourth review round - A change the planner dropped for the field under test was reported as OK whenever some other field kept the node's action at "update": the node action was the only thing consulted, and the other field is filtered out of the post-deploy plan as known baseline drift. The field's own entries decide now, matched at whatever granularity the planner diffed at -- it records a whole "tasks" entry, not "tasks[0].description". - Drift the planner reports against an ancestor of the field was blamed on some other field: a change to config.auto_capture_config.catalog_name surfaces as drift on "config", which exact-path matching read as collateral. Ten rows move from COLLATERAL_DRIFT to POST_DEPLOY_DRIFT, which is where the fix belongs. - A list's values skipped the explicit empty list, which is not the same as absent: it is what "on_start: []" produces and the only way to clear a list the backend already holds. 346 more observations. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 45 ++++++++++++++++++- bundle/direct/tests/output/apps.txt | 10 +++-- .../direct/tests/output/cluster_policies.txt | 6 +-- bundle/direct/tests/output/jobs.txt | 6 +-- .../tests/output/model_serving_endpoints.txt | 28 ++++++------ bundle/direct/tests/output/pipelines.txt | 6 +-- .../direct/tests/output/postgres_projects.txt | 4 +- bundle/direct/tests/values_test.go | 5 ++- 8 files changed, 78 insertions(+), 32 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 1317c5f990b..5c41561f135 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -253,6 +253,43 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } } +// sameField reports whether a drifting path is the field under test. The planner may report +// a change against an ancestor rather than the leaf -- a whole "config" block instead of +// "config.auto_capture_config.catalog_name" -- and that is still the field's own drift, not +// some other field's. +func sameField(drifting, path string) bool { + return drifting == path || + strings.HasPrefix(path, drifting+".") || strings.HasPrefix(path, drifting+"[") || + strings.HasPrefix(drifting, path+".") || strings.HasPrefix(drifting, path+"[") +} + +// fieldWasDropped reports whether the plan mentions the field and yet will not act on it. +// The planner records a change at whatever granularity it diffed, which is not always the +// leaf -- a whole "tasks" entry rather than "tasks[0].description" -- so every entry naming +// the field, an ancestor of it, or something under it counts. A field the plan does not +// mention at all is not "dropped": there is nothing to conclude from its absence, and the +// node's own action decides. +func fieldWasDropped(plan *deployplan.Plan, node, path string) bool { + if plan == nil { + return false + } + entry, ok := plan.Plan[node] + if !ok { + return false + } + mentioned := false + for key, change := range entry.Changes { + if !sameField(key, path) { + continue + } + if change.Action != deployplan.Skip { + return false + } + mentioned = true + } + return mentioned +} + // converged reports whether a plan no longer wants to change the field. func converged(plan *deployplan.Plan, node, path string) bool { change, ok := planChange(plan, node, path) @@ -315,7 +352,11 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans return res } action := nodeAction(plan, h.node) - if action == deployplan.Skip { + // The field's own entries, not just the node's action: another field already drifting keeps + // the node at "update", and reading only that would let a change to *this* field be + // dropped and still come out as OK once the other field is filtered from the post-deploy + // plan. + if action == deployplan.Skip || fieldWasDropped(plan, h.node, path) { pending.cancel() res.verdict, res.detail = explainSkip(plan, h.node, path) return res @@ -388,7 +429,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans case own == "": // A child node left behind by a recreate: a different problem, different fix. res.verdict, res.detail = verdictDriftChild, child - case slices.Contains(drifted, path): + case slices.ContainsFunc(drifted, func(p string) bool { return sameField(p, path) }): res.verdict, res.detail = verdictDrift, own default: res.verdict, res.detail = verdictCollateral, own diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 60845221160..c43b49371e5 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -64,14 +64,16 @@ source_code_path y-[UNIQUE_NAME] absent START_NO source_code_path y-[UNIQUE_NAME] x-[UNIQUE_NAME] START_NOT_REACHED source_code_path space y x START_NOT_REACHED space usage_policy_id y absent UPDATE_IGNORED usage_policy_id +user_api_scopes len1 absent UPDATE_IGNORED user_api_scopes +user_api_scopes len1 len0 UPDATE_IGNORED user_api_scopes user_api_scopes len2 absent UPDATE_IGNORED user_api_scopes === summary -OK 114 +OK 116 OK_RECREATE 2 -SUPPRESSED 23 -NOT_OBSERVABLE 56 -UPDATE_IGNORED 22 +SUPPRESSED 24 +NOT_OBSERVABLE 57 +UPDATE_IGNORED 24 UNSETTABLE 2 SKIPPED 82 START_NOT_REACHED 23 diff --git a/bundle/direct/tests/output/cluster_policies.txt b/bundle/direct/tests/output/cluster_policies.txt index ebe3c0fd689..4d28e9c1d92 100644 --- a/bundle/direct/tests/output/cluster_policies.txt +++ b/bundle/direct/tests/output/cluster_policies.txt @@ -1,6 +1,6 @@ === summary -OK 154 -SUPPRESSED 1 -NOT_OBSERVABLE 1 +OK 158 +SUPPRESSED 2 +NOT_OBSERVABLE 2 SKIPPED 1 diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index 4eeed220057..5c0dbfcb3d9 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -4,9 +4,9 @@ deployment.version_id x y START_NOT_REA deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) === summary -OK 2607 +OK 2858 OK_INERT 3 -SUPPRESSED 12 -NOT_OBSERVABLE 129 +SUPPRESSED 18 +NOT_OBSERVABLE 232 SKIPPED 3 START_NOT_REACHED 3 diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index 00968b54f9a..2c1cd8fdf04 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -1,16 +1,16 @@ === model_serving_endpoint.yml.tmpl budget_policy_id x absent START_NOT_REACHED budget_policy_id (declared inert: no_update_api) budget_policy_id x y START_NOT_REACHED budget_policy_id (declared inert: no_update_api) -config.auto_capture_config.catalog_name absent y COLLATERAL_DRIFT config -config.auto_capture_config.catalog_name y absent COLLATERAL_DRIFT config -config.auto_capture_config.enabled absent false COLLATERAL_DRIFT config -config.auto_capture_config.enabled absent true COLLATERAL_DRIFT config -config.auto_capture_config.enabled false absent COLLATERAL_DRIFT config -config.auto_capture_config.enabled false true COLLATERAL_DRIFT config -config.auto_capture_config.enabled true absent COLLATERAL_DRIFT config -config.auto_capture_config.enabled true false COLLATERAL_DRIFT config -config.auto_capture_config.schema_name x absent COLLATERAL_DRIFT config -config.auto_capture_config.table_name_prefix y absent COLLATERAL_DRIFT config +config.auto_capture_config.catalog_name absent y POST_DEPLOY_DRIFT config +config.auto_capture_config.catalog_name y absent POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled absent false POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled absent true POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled false absent POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled false true POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled true absent POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled true false POST_DEPLOY_DRIFT config +config.auto_capture_config.schema_name x absent POST_DEPLOY_DRIFT config +config.auto_capture_config.table_name_prefix y absent POST_DEPLOY_DRIFT config rate_limits len0 absent START_NOT_REACHED rate_limits (declared inert: not_implemented) rate_limits len1 absent START_NOT_REACHED rate_limits (declared inert: not_implemented) rate_limits len1 len0 START_NOT_REACHED rate_limits (declared inert: not_implemented) @@ -20,11 +20,11 @@ rate_limits[0].key user absent START_NOT_REA rate_limits[0].key user endpoint START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) === summary -OK 134 +OK 150 OK_RECREATE 26 OK_INERT 11 -SUPPRESSED 10 -NOT_OBSERVABLE 6 -COLLATERAL_DRIFT 10 +SUPPRESSED 14 +NOT_OBSERVABLE 10 +POST_DEPLOY_DRIFT 10 SKIPPED 2 START_NOT_REACHED 9 diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 20db49dbc0b..3f46744f300 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -8,11 +8,11 @@ dry_run true absent START_NOT_REA dry_run true false START_NOT_REACHED dry_run === summary -OK 768 +OK 806 OK_RECREATE 33 OK_INERT 9 -SUPPRESSED 39 -NOT_OBSERVABLE 20 +SUPPRESSED 46 +NOT_OBSERVABLE 29 UPDATE_IGNORED 2 SKIPPED 2 START_NOT_REACHED 5 diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index fe040fb4b8c..a7463308a00 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -25,7 +25,7 @@ default_endpoint_settings.no_suspension absent true BACKEND_ERROR default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension true false BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings absent keys0 COLLATERAL_DRIFT default_endpoint_settings +default_endpoint_settings.pg_settings absent keys0 POST_DEPLOY_DRIFT default_endpoint_settings default_endpoint_settings.pg_settings keys0 keys1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings keys1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... default_endpoint_settings.pg_settings keys1 keys0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... @@ -48,7 +48,7 @@ OK 30 OK_RECREATE 6 SUPPRESSED 4 NOT_OBSERVABLE 3 -COLLATERAL_DRIFT 1 +POST_DEPLOY_DRIFT 1 BACKEND_ERROR 29 SKIPPED 1 BASE_ERROR 6 diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 20e096c1bff..19dab0083f9 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -221,7 +221,10 @@ func defaultValues(typ reflect.Type) []any { if len(elements) < 2 { return nil } - return []any{[]any{elements[0]}, []any{elements[0], elements[1]}} + // The empty list is its own value, not the same as absent: it is what a user writing + // "on_start: []" produces, and it reaches the API as an explicit empty array through + // ForceSendFields -- the only way to clear a list the backend already holds. + return []any{[]any{}, []any{elements[0]}, []any{elements[0], elements[1]}} } return kindValues(typ.Kind()) } From e3479a555369b01262607e2bdcc9d7ff96ce2b72 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 13:50:05 +0200 Subject: [PATCH 037/118] Field catalog: align alerts with AWS Every remaining divergence was in the value library, and each one is a constraint the fake server does not enforce: - A threshold compares against either a column or a value, and the alert file this config loads already picks the value -- so the column is the other arm of that choice, the backend drops it, and it drifts forever. The value's own bool/double/string members are a oneof in the same way. - A timezone and a quartz cron expression are not free text. - run_as is a oneof whose valid member depends on whether the caller is a user or a service principal; run_as_user_name is the deprecated flat form of the same thing and setting it does nothing for the service principal a cloud run uses. - The backend will not leave a subscription without a recipient. What it keeps instead is not something this suite can predict, so the field is skipped rather than guessed at. I also tried modelling "an update keeps what the body omits" here, as for warehouses, and it was wrong: an alert update carries update_mask="*", so the backend replaces the whole spec and a cleared field really is cleared. The evidence is 30 rows where a real workspace clears a field the merge would have kept. alerts matches AWS row for row. Co-authored-by: Isaac --- bundle/direct/tests/output/alerts.txt | 8 +++---- .../direct/tests/testdata/fields/alerts.yml | 22 +++++++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/bundle/direct/tests/output/alerts.txt b/bundle/direct/tests/output/alerts.txt index 26755f1d511..f69dc102c74 100644 --- a/bundle/direct/tests/output/alerts.txt +++ b/bundle/direct/tests/output/alerts.txt @@ -1,6 +1,6 @@ === summary -OK 122 -SUPPRESSED 4 -NOT_OBSERVABLE 10 -SKIPPED 13 +OK 80 +SUPPRESSED 3 +NOT_OBSERVABLE 9 +SKIPPED 17 diff --git a/bundle/direct/tests/testdata/fields/alerts.yml b/bundle/direct/tests/testdata/fields/alerts.yml index a8de07d703b..38d1f18f324 100644 --- a/bundle/direct/tests/testdata/fields/alerts.yml +++ b/bundle/direct/tests/testdata/fields/alerts.yml @@ -22,8 +22,22 @@ skip: # Must be the id of a notification destination registered with the workspace. evaluation.notification.subscriptions[*].destination_id: needs a notification destination + # A threshold compares against either a column or a value, and the alert file this config + # loads (data/alert.dbalert.json) sets the value -- so the column is the other arm of that + # choice and the backend drops it, leaving it drifting forever. The value's own three + # members (bool, double, string) are a oneof in the same way. + evaluation.threshold: two arms of one choice, and the alert file already picks the value + # Also a oneof (user_name or service_principal_name), and which member is valid depends on + # whether the identity running the suite is a user or a service principal. run_as_user_name + # is the deprecated flat form of the same thing: setting it has no effect when the caller is + # a service principal, which is what a cloud run is. + run_as: a oneof, and which member is valid depends on the run identity + run_as_user_name: only settable when the caller is a user, not a service principal + # An alert has to be able to notify somebody, so the backend will not leave a subscription + # without a recipient -- but what it keeps instead is not something this suite can predict. + evaluation.notification.subscriptions[*].user_email: the backend refuses to leave a subscription without a recipient + fields: - # A subscription, and a run_as, has to name a principal the workspace knows. - evaluation.notification.subscriptions[*].user_email: [$CURRENT_USER_NAME] - run_as.user_name: [$CURRENT_USER_NAME] - run_as_user_name: [$CURRENT_USER_NAME] + # A real zone, and a real cron expression -- neither is free text. + schedule.timezone_id: [UTC, America/New_York] + schedule.quartz_cron_schedule: ["0 0 9 10 1 ?", "0 0 10 10 1 ?"] From 4338924524f861cf3651586d7168ba0960548069 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 13:56:22 +0200 Subject: [PATCH 038/118] structaccess: resolve embedded fields breadth-first, as encoding/json does From the adversarial review. Get and Set searched embedded structs depth-first, so a name declared at two embedding depths could resolve to the deeper field -- while json.Marshal picks the shallower one. Reading and writing the field would then not be the field that gets serialized under that name. The search now goes one level of embedding at a time, and the test fails on the old behaviour. Co-authored-by: Isaac --- libs/structs/structaccess/get.go | 40 +++++++++++++++++++-------- libs/structs/structaccess/set_test.go | 38 +++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/libs/structs/structaccess/get.go b/libs/structs/structaccess/get.go index 834d3f3f532..3d5037a3b4e 100644 --- a/libs/structs/structaccess/get.go +++ b/libs/structs/structaccess/get.go @@ -269,24 +269,43 @@ func findFieldInStruct(v reflect.Value, key string) (reflect.Value, reflect.Stru // the SDK request struct, so its fields sit two levels down. // Returns: fieldValue, structField, owner (the struct value declaring the field), found func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.StructField, reflect.Value, bool) { - t := v.Type() - // First pass: direct fields if fv, sf, found := findFieldInStruct(v, key); found { return fv, sf, v, true } - // Second pass: search embedded anonymous structs (flattening semantics) + // Second pass: search embedded anonymous structs (flattening semantics), one level of + // embedding at a time. Breadth-first, not depth-first: encoding/json resolves a name + // declared at two embedding depths in favour of the shallower one, so a search that + // descended fully into the first embed could pick a deeper field than the one that is + // actually serialized under that name. + for _, fv := range embeddedStructs(v) { + if out, sf, found := findFieldInStruct(fv, key); found { + return out, sf, fv, true + } + } + for _, fv := range embeddedStructs(v) { + if out, sf, owner, found := findStructFieldByKey(fv, key); found { + return out, sf, owner, true + } + } + + return reflect.Value{}, reflect.StructField{}, reflect.Value{}, false +} + +// embeddedStructs returns the anonymous struct fields of v, dereferenced, skipping any that +// cannot be descended into. +func embeddedStructs(v reflect.Value) []reflect.Value { + var out []reflect.Value + t := v.Type() for i := range t.NumField() { - sf := t.Field(i) - if !sf.Anonymous { + if !t.Field(i).Anonymous { continue } fv := v.Field(i) - // Dereference pointer anonymous structs for fv.Kind() == reflect.Pointer { if fv.IsNil() { - // Not initialized; can't descend + // Not initialized; can't descend. break } fv = fv.Elem() @@ -294,12 +313,9 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S if fv.Kind() != reflect.Struct { continue } - if out, osf, owner, found := findStructFieldByKey(fv, key); found { - return out, osf, owner, true - } + out = append(out, fv) } - - return reflect.Value{}, reflect.StructField{}, reflect.Value{}, false + return out } // forceSendFields returns the ForceSendFields slice a struct declares itself. A struct that diff --git a/libs/structs/structaccess/set_test.go b/libs/structs/structaccess/set_test.go index 1b294494eca..da8cf3ff19c 100644 --- a/libs/structs/structaccess/set_test.go +++ b/libs/structs/structaccess/set_test.go @@ -1,6 +1,7 @@ package structaccess_test import ( + "encoding/json" "testing" "github.com/databricks/cli/libs/structs/structaccess" @@ -791,3 +792,40 @@ func TestSet_MixedForceSendFields(t *testing.T) { assert.Equal(t, []string{"SecondFieldOmit"}, obj.Second.ForceSendFields) // no duplicates }) } + +// encoding/json resolves a name declared at two embedding depths in favour of the shallower +// one. Get and Set have to agree with it, so the embedded search goes level by level: a +// depth-first search would find Deep.Value first, since its embed is declared first. +type deepValue struct { + Value string `json:"value"` +} + +type deepEmbed struct { + deepValue +} + +type shallowEmbed struct { + Value string `json:"value"` +} + +type embedDepths struct { + deepEmbed + shallowEmbed +} + +func TestSet_ShallowerEmbedWins(t *testing.T) { + target := &embedDepths{} + + require.NoError(t, structaccess.SetByString(target, "value", "set")) + assert.Equal(t, "set", target.Value) + assert.Empty(t, target.deepEmbed.Value) + + got, err := structaccess.GetByString(target, "value") + require.NoError(t, err) + assert.Equal(t, "set", got) + + // The same field json.Marshal picks, which is the contract being matched. + blob, err := json.Marshal(target) + require.NoError(t, err) + assert.JSONEq(t, `{"value":"set"}`, string(blob)) +} From 05ac39a2e17155143461fd9ae5c3eba421323ff9 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 13:56:22 +0200 Subject: [PATCH 039/118] Field catalog: four fixes from the fifth review round - A resource built for a retry had its cleanup registered on the transition subtest that asked for it, though every transition after that one reuses it: the resource was destroyed as soon as that subtest ended, and the rest of the field's transitions were silently creating a new one each time. Cleanup belongs to the test that owns the resource, which is the one running the config. - The starting-value check and explainSkip both looked up the exact leaf path, but the planner records a change at the granularity it diffed at -- "config" rather than "config.auto_capture_config.catalog_name". So a pending ancestor change let a transition run from a value the remote never reached, and a dropped change was reported as NOT_OBSERVABLE instead of SUPPRESSED with the engine's own reason. Both now go through one helper that matches at any granularity, which fieldWasDropped is expressed in terms of too. - A skip key naming a whole subtree with a trailing wildcard did not match a pattern beneath it, which is the form a field under an absent container takes -- so the field was reported as an uncovered gap despite being deliberately skipped. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 78 ++++++++----- bundle/direct/tests/output/apps.txt | 108 ++++++++++++++---- bundle/direct/tests/output/catalogs.txt | 11 +- bundle/direct/tests/output/clusters.txt | 10 +- .../tests/output/database_instances.txt | 42 +++---- bundle/direct/tests/output/experiments.txt | 9 +- bundle/direct/tests/output/genie_spaces.txt | 5 +- bundle/direct/tests/output/instance_pools.txt | 17 ++- bundle/direct/tests/output/jobs.txt | 5 +- .../tests/output/model_serving_endpoints.txt | 37 ++++-- bundle/direct/tests/output/models.txt | 19 ++- bundle/direct/tests/output/pipelines.txt | 9 +- .../direct/tests/output/postgres_projects.txt | 88 ++++++++------ .../tests/output/postgres_synced_tables.txt | 50 +++++++- .../direct/tests/output/registered_models.txt | 42 ++++++- bundle/direct/tests/output/schemas.txt | 11 +- bundle/direct/tests/output/secret_scopes.txt | 4 +- bundle/direct/tests/output/sql_warehouses.txt | 3 +- bundle/direct/tests/values_test.go | 7 +- bundle/direct/tests/values_unit_test.go | 9 +- 20 files changed, 404 insertions(+), 160 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 5c41561f135..aa2d2287a92 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -118,7 +118,11 @@ func TestFields(t *testing.T) { } } +// runConfig drives one config. Every resource it creates belongs to this test's lifetime, +// which is what `owner` below carries into the subtests: a resource a subtest asks for is +// reused by the transitions after it, so its cleanup cannot be the subtest's. func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, rep *report, runSeed uint64) { + owner := t adapter, err := dresources.NewAdapter(dresources.SupportedResources[cfg.resourceType], cfg.resourceType, client) require.NoError(t, err) @@ -194,7 +198,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // created without the field genuinely starts absent. Rebuild and try the // transition once more, so it is observed rather than written off. if res.verdict == verdictStartNotReached { - rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h) + rebuilt, err := rebuild(owner, ctx, client, user, cfg, fv, h) if err != nil { // Nothing was observed and there is no resource left to observe // it on, which is a different statement from the field refusing @@ -221,7 +225,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl deployedKnown = false // The resource is in an unknown state; carrying it into the next // transition would turn one failure into a run of them. - rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h) + rebuilt, err := rebuild(owner, ctx, client, user, cfg, fv, h) if err != nil { broken = err return @@ -242,7 +246,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // what makes that cheap: reusing the old one waits out an asynchronous delete. h.restore(base) if !h.converged() { - rebuilt, err := rebuild(t, ctx, client, user, cfg, fv, h) + rebuilt, err := rebuild(owner, ctx, client, user, cfg, fv, h) if err != nil { rep.add(result{cfg.name, f.path, "", "", verdictBaseError, oneLine(err.Error()), err.Error()}) return @@ -263,31 +267,44 @@ func sameField(drifting, path string) bool { strings.HasPrefix(drifting, path+".") || strings.HasPrefix(drifting, path+"[") } -// fieldWasDropped reports whether the plan mentions the field and yet will not act on it. -// The planner records a change at whatever granularity it diffed, which is not always the -// leaf -- a whole "tasks" entry rather than "tasks[0].description" -- so every entry naming -// the field, an ancestor of it, or something under it counts. A field the plan does not -// mention at all is not "dropped": there is nothing to conclude from its absence, and the -// node's own action decides. -func fieldWasDropped(plan *deployplan.Plan, node, path string) bool { +// relatedChange returns the plan's change for the field, or for whatever ancestor or +// descendant of it the planner actually recorded -- it diffs at its own granularity, so a +// change to "tasks[0].description" can be reported against "tasks". A pending change is +// preferred over a skipped one, since that is the one that explains what will happen. +func relatedChange(plan *deployplan.Plan, node, path string) (*deployplan.ChangeDesc, bool) { if plan == nil { - return false + return nil, false } entry, ok := plan.Plan[node] if !ok { - return false + return nil, false } - mentioned := false - for key, change := range entry.Changes { + + var found *deployplan.ChangeDesc + for _, key := range slices.Sorted(maps.Keys(entry.Changes)) { + change := entry.Changes[key] if !sameField(key, path) { continue } if change.Action != deployplan.Skip { - return false + return change, true + } + if found == nil { + found = change } - mentioned = true } - return mentioned + return found, found != nil +} + +// fieldWasDropped reports whether the plan mentions the field and yet will not act on it. +// The planner records a change at whatever granularity it diffed, which is not always the +// leaf -- a whole "tasks" entry rather than "tasks[0].description" -- so every entry naming +// the field, an ancestor of it, or something under it counts. A field the plan does not +// mention at all is not "dropped": there is nothing to conclude from its absence, and the +// node's own action decides. +func fieldWasDropped(plan *deployplan.Plan, node, path string) bool { + change, ok := relatedChange(plan, node, path) + return ok && change.Action == deployplan.Skip } // converged reports whether a plan no longer wants to change the field. @@ -328,7 +345,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // under a label that is not what happened -- "absent to 168" while the remote still // holds 720. Cheaper to check than to reason about afterwards. if plan, diags := h.readPlan(); !diags.HasError() { - if change, ok := planChange(plan, h.node, path); ok && !reachedValue(change) && !baseline[path] { + if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change) && !baseline[path] { res.verdict = verdictStartNotReached res.detail = path res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) @@ -445,12 +462,17 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans } // newBaseline builds a harness and deploys the config as written. -func newBaseline(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues) (*bundleHarness, error) { - h, err := newHarness(t, ctx, client, user, cfg.name, uniqueName(), fv.base) +// +// owner is the test whose lifetime the resource belongs to, which is the one running the +// config -- not the subtest that happened to ask for the rebuild. Registering the cleanup on +// the subtest would destroy the resource as soon as that transition ended, and every +// transition after it would silently be creating a new one. +func newBaseline(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues) (*bundleHarness, error) { + h, err := newHarness(owner, ctx, client, user, cfg.name, uniqueName(), fv.base) if err != nil { return nil, err } - t.Cleanup(func() { _ = h.destroy() }) + owner.Cleanup(func() { _ = h.destroy() }) action, diags := h.deploy() if diags.HasError() { @@ -467,9 +489,9 @@ func newBaseline(t *testing.T, ctx context.Context, client *databricks.Workspace // rebuild starts over on a fresh resource, leaving the old one to be destroyed at the // end of the test. A new name is what makes this cheap: reusing the old one can mean // waiting out an asynchronous delete. -func rebuild(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, old *bundleHarness) (*bundleHarness, error) { - t.Cleanup(func() { _ = old.destroy() }) - return newBaseline(t, ctx, client, user, cfg, fv) +func rebuild(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, old *bundleHarness) (*bundleHarness, error) { + owner.Cleanup(func() { _ = old.destroy() }) + return newBaseline(owner, ctx, client, user, cfg, fv) } // explainSkip says why a plan came back with nothing to do for the field under test. @@ -477,11 +499,13 @@ func rebuild(t *testing.T, ctx context.Context, client *databricks.WorkspaceClie // bool and an explicit false are the same on the wire), whereas a field it diffed and // then dropped is, and the engine records its own reason for that. func explainSkip(plan *deployplan.Plan, node, path string) (verdict, string) { - entry, ok := plan.Plan[node] - if !ok { + if _, ok := plan.Plan[node]; !ok { return verdictNoPlan, "no plan entry" } - change, ok := entry.Changes[path] + // Whichever entry names the field, which is not always the leaf: the planner records a + // change at the granularity it diffed at, so the reason for dropping "tasks[0].foo" may + // be attached to "tasks". + change, ok := relatedChange(plan, node, path) if !ok { return verdictNotObservable, "" } diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index c43b49371e5..0fd3c44282a 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -1,50 +1,106 @@ === app.yml.tmpl budget_policy_id x absent UPDATE_IGNORED budget_policy_id budget_policy_id y absent UPDATE_IGNORED budget_policy_id +compute_max_instances 1 absent UPDATE_IGNORED compute_max_instances compute_max_instances 2 absent UPDATE_IGNORED compute_max_instances compute_min_instances 1 absent UPDATE_IGNORED compute_min_instances compute_min_instances 2 absent UPDATE_IGNORED compute_min_instances config.command absent len1 START_NOT_REACHED config.command config.command absent len2 START_NOT_REACHED config.command -config.command len1 absent SUPPRESSED no active deployment +config.command len1 absent START_NOT_REACHED config.command config.command len1 len2 START_NOT_REACHED config.command -config.command len2 absent SUPPRESSED no active deployment -config.command len2 len1 SUPPRESSED no active deployment +config.command len2 absent START_NOT_REACHED config.command +config.command len2 len1 START_NOT_REACHED config.command config.command[0] absent x UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it config.command[0] absent y UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it config.command[0] x absent START_NOT_REACHED config.command[0] -config.command[0] x y SUPPRESSED no active deployment +config.command[0] x y START_NOT_REACHED config.command[0] config.command[0] y absent START_NOT_REACHED config.command[0] -config.command[0] y x SUPPRESSED no active deployment +config.command[0] y x START_NOT_REACHED config.command[0] +config.command[1] absent x START_NOT_REACHED config.command[1] +config.command[1] absent y START_NOT_REACHED config.command[1] +config.command[1] x absent START_NOT_REACHED config.command[1] +config.command[1] x y START_NOT_REACHED config.command[1] config.command[1] y absent START_NOT_REACHED config.command[1] +config.command[1] y x START_NOT_REACHED config.command[1] config.env absent len0 START_NOT_REACHED config.env -config.env absent len1 SUPPRESSED no active deployment +config.env absent len1 START_NOT_REACHED config.env config.env len0 absent START_NOT_REACHED config.env config.env len0 len1 START_NOT_REACHED config.env -config.env len1 absent SUPPRESSED no active deployment -config.env len1 len0 SUPPRESSED no active deployment +config.env len1 absent START_NOT_REACHED config.env +config.env len1 len0 START_NOT_REACHED config.env config.env[0].name x y START_NOT_REACHED config.env[0].name -config.env[0].name y x SUPPRESSED no active deployment -config.env[0].value absent x SUPPRESSED no active deployment -config.env[0].value absent y SUPPRESSED no active deployment +config.env[0].name y x START_NOT_REACHED config.env[0].name +config.env[0].value absent x START_NOT_REACHED config.env[0].value +config.env[0].value absent y START_NOT_REACHED config.env[0].value config.env[0].value x absent START_NOT_REACHED config.env[0].value config.env[0].value x y START_NOT_REACHED config.env[0].value config.env[0].value y absent START_NOT_REACHED config.env[0].value -config.env[0].value y x SUPPRESSED no active deployment -config.env[0].value_from absent x SUPPRESSED no active deployment -config.env[0].value_from absent y SUPPRESSED no active deployment -config.env[0].value_from x absent SUPPRESSED no active deployment +config.env[0].value y x START_NOT_REACHED config.env[0].value +config.env[0].value_from absent x START_NOT_REACHED config.env[0].value_from +config.env[0].value_from absent y START_NOT_REACHED config.env[0].value_from +config.env[0].value_from x absent START_NOT_REACHED config.env[0].value_from config.env[0].value_from x y START_NOT_REACHED config.env[0].value_from -config.env[0].value_from y absent SUPPRESSED no active deployment -config.env[0].value_from y x SUPPRESSED no active deployment +config.env[0].value_from y absent START_NOT_REACHED config.env[0].value_from +config.env[0].value_from y x START_NOT_REACHED config.env[0].value_from description x absent UPDATE_IGNORED description +description y absent UPDATE_IGNORED description git_repository.auto_deploy true absent UPDATE_IGNORED git_repository.auto_deploy git_repository.caller_credential_id 1 absent UPDATE_IGNORED git_repository.caller_credential_id git_repository.caller_credential_id 2 absent UPDATE_IGNORED git_repository.caller_credential_id +git_source.branch absent x SUPPRESSED no active deployment +git_source.branch absent y SUPPRESSED no active deployment +git_source.branch x absent START_NOT_REACHED git_source.branch +git_source.branch x y START_NOT_REACHED git_source.branch +git_source.branch y absent START_NOT_REACHED git_source.branch +git_source.branch y x START_NOT_REACHED git_source.branch +git_source.commit absent x SUPPRESSED no active deployment +git_source.commit absent y SUPPRESSED no active deployment +git_source.commit x absent START_NOT_REACHED git_source.commit +git_source.commit x y START_NOT_REACHED git_source.commit +git_source.commit y absent START_NOT_REACHED git_source.commit +git_source.commit y x START_NOT_REACHED git_source.commit +git_source.git_repository.auto_deploy absent false SUPPRESSED no active deployment +git_source.git_repository.auto_deploy absent true SUPPRESSED no active deployment +git_source.git_repository.auto_deploy false absent START_NOT_REACHED git_source.git_repository.auto_deploy +git_source.git_repository.auto_deploy false true START_NOT_REACHED git_source.git_repository.auto_deploy +git_source.git_repository.auto_deploy true absent START_NOT_REACHED git_source.git_repository.auto_deploy +git_source.git_repository.auto_deploy true false START_NOT_REACHED git_source.git_repository.auto_deploy +git_source.git_repository.caller_credential_id 1 2 START_NOT_REACHED git_source.git_repository.caller_credential_id +git_source.git_repository.caller_credential_id 1 absent START_NOT_REACHED git_source.git_repository.caller_credential_id +git_source.git_repository.caller_credential_id 2 1 START_NOT_REACHED git_source.git_repository.caller_credential_id +git_source.git_repository.caller_credential_id 2 absent START_NOT_REACHED git_source.git_repository.caller_credential_id +git_source.git_repository.caller_credential_id absent 1 SUPPRESSED no active deployment +git_source.git_repository.caller_credential_id absent 2 SUPPRESSED no active deployment +git_source.git_repository.provider x y START_NOT_REACHED git_source.git_repository.provider +git_source.git_repository.provider y x START_NOT_REACHED git_source.git_repository.provider +git_source.git_repository.url x y START_NOT_REACHED git_source.git_repository.url +git_source.git_repository.url y x START_NOT_REACHED git_source.git_repository.url +git_source.resolved_commit absent x SUPPRESSED no active deployment +git_source.resolved_commit absent y SUPPRESSED no active deployment +git_source.resolved_commit x absent START_NOT_REACHED git_source.resolved_commit +git_source.resolved_commit x y START_NOT_REACHED git_source.resolved_commit +git_source.resolved_commit y absent START_NOT_REACHED git_source.resolved_commit +git_source.resolved_commit y x START_NOT_REACHED git_source.resolved_commit +git_source.source_code_path absent x SUPPRESSED no active deployment +git_source.source_code_path absent y SUPPRESSED no active deployment +git_source.source_code_path x absent START_NOT_REACHED git_source.source_code_path +git_source.source_code_path x y START_NOT_REACHED git_source.source_code_path +git_source.source_code_path y absent START_NOT_REACHED git_source.source_code_path +git_source.source_code_path y x START_NOT_REACHED git_source.source_code_path +git_source.tag absent x SUPPRESSED no active deployment +git_source.tag absent y SUPPRESSED no active deployment +git_source.tag x absent START_NOT_REACHED git_source.tag +git_source.tag x y START_NOT_REACHED git_source.tag +git_source.tag y absent START_NOT_REACHED git_source.tag +git_source.tag y x START_NOT_REACHED git_source.tag resources absent len0 START_NOT_REACHED resources +resources absent len1 START_NOT_REACHED resources +resources len0 absent START_NOT_REACHED resources resources len0 len1 START_NOT_REACHED resources resources len1 absent UPDATE_IGNORED resources resources len1 len0 UPDATE_IGNORED resources +resources[0].app.name x absent UPDATE_IGNORED resources[0].app.name resources[0].app.name y absent UPDATE_IGNORED resources[0].app.name resources[0].app.permission CAN_USE absent UPDATE_IGNORED resources[0].app.permission resources[0].description absent x START_NOT_REACHED resources[0].description @@ -56,24 +112,28 @@ resources[0].postgres.branch y absent UPDATE_IGNORE resources[0].postgres.database x absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.database y absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.permission CAN_CONNECT_AND_CREATE absent UPDATE_IGNORED resources[0].postgres.permission -source_code_path absent x-[UNIQUE_NAME] SUPPRESSED no active deployment -source_code_path absent y-[UNIQUE_NAME] SUPPRESSED no active deployment +source_code_path absent x-[UNIQUE_NAME] START_NOT_REACHED source_code_path +source_code_path absent y-[UNIQUE_NAME] START_NOT_REACHED source_code_path source_code_path x-[UNIQUE_NAME] absent START_NOT_REACHED source_code_path source_code_path x-[UNIQUE_NAME] y-[UNIQUE_NAME] START_NOT_REACHED source_code_path source_code_path y-[UNIQUE_NAME] absent START_NOT_REACHED source_code_path source_code_path y-[UNIQUE_NAME] x-[UNIQUE_NAME] START_NOT_REACHED source_code_path +space x y START_NOT_REACHED space +space y absent START_NOT_REACHED space space y x START_NOT_REACHED space +usage_policy_id x absent UPDATE_IGNORED usage_policy_id usage_policy_id y absent UPDATE_IGNORED usage_policy_id user_api_scopes len1 absent UPDATE_IGNORED user_api_scopes user_api_scopes len1 len0 UPDATE_IGNORED user_api_scopes user_api_scopes len2 absent UPDATE_IGNORED user_api_scopes +user_api_scopes len2 len0 UPDATE_IGNORED user_api_scopes === summary -OK 116 +OK 108 OK_RECREATE 2 -SUPPRESSED 24 -NOT_OBSERVABLE 57 -UPDATE_IGNORED 24 +SUPPRESSED 21 +NOT_OBSERVABLE 3 +UPDATE_IGNORED 29 UNSETTABLE 2 SKIPPED 82 -START_NOT_REACHED 23 +START_NOT_REACHED 83 diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index bdda2a61d1a..ed42204f1e5 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -2,17 +2,18 @@ custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours properties absent keys0 START_NOT_REACHED properties +properties absent keys1 START_NOT_REACHED properties properties keys0 absent START_NOT_REACHED properties +properties keys0 keys1 START_NOT_REACHED properties properties keys1 absent UPDATE_IGNORED properties -properties keys1 keys0 POST_DEPLOY_DRIFT properties,properties['team'] +properties keys1 keys0 UPDATE_IGNORED properties properties['team'] absent x START_NOT_REACHED properties['team'] properties['team'] absent y START_NOT_REACHED properties['team'] properties['team'] x absent UPDATE_IGNORED properties['team'] properties['team'] y absent UPDATE_IGNORED properties['team'] === summary -OK 16 -UPDATE_IGNORED 5 -POST_DEPLOY_DRIFT 1 +OK 14 +UPDATE_IGNORED 6 SKIPPED 8 -START_NOT_REACHED 4 +START_NOT_REACHED 6 diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/tests/output/clusters.txt index d0d01becf4a..217504b4bf4 100644 --- a/bundle/direct/tests/output/clusters.txt +++ b/bundle/direct/tests/output/clusters.txt @@ -1,6 +1,10 @@ +=== cluster.yml.tmpl +aws_attributes.ebs_volume_count absent 1 START_NOT_REACHED aws_attributes.ebs_volume_count +aws_attributes.spot_bid_price_percent absent 1 START_NOT_REACHED aws_attributes.spot_bid_price_percent === summary -OK 449 -SUPPRESSED 23 -NOT_OBSERVABLE 12 +OK 447 +SUPPRESSED 24 +NOT_OBSERVABLE 11 SKIPPED 1 +START_NOT_REACHED 2 diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index 68a9942cc9f..6716c487a70 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -4,23 +4,24 @@ capacity absent y UPDATE_IGNORE capacity x absent START_NOT_REACHED capacity capacity x y START_NOT_REACHED capacity capacity y absent START_NOT_REACHED capacity -capacity y x POST_DEPLOY_DRIFT capacity +capacity y x START_NOT_REACHED capacity custom_tags absent len1 COLLATERAL_DRIFT capacity +custom_tags len0 absent COLLATERAL_DRIFT capacity custom_tags len0 len1 COLLATERAL_DRIFT capacity custom_tags len1 absent COLLATERAL_DRIFT capacity custom_tags len1 len0 COLLATERAL_DRIFT capacity -custom_tags[0].key absent x COLLATERAL_DRIFT capacity -custom_tags[0].key absent y COLLATERAL_DRIFT capacity -custom_tags[0].key x absent COLLATERAL_DRIFT capacity -custom_tags[0].key x y COLLATERAL_DRIFT capacity -custom_tags[0].key y absent COLLATERAL_DRIFT capacity -custom_tags[0].key y x COLLATERAL_DRIFT capacity -custom_tags[0].value absent x COLLATERAL_DRIFT capacity -custom_tags[0].value absent y COLLATERAL_DRIFT capacity -custom_tags[0].value x absent COLLATERAL_DRIFT capacity -custom_tags[0].value x y COLLATERAL_DRIFT capacity -custom_tags[0].value y absent COLLATERAL_DRIFT capacity -custom_tags[0].value y x COLLATERAL_DRIFT capacity +custom_tags[0].key absent x START_NOT_REACHED custom_tags[0].key +custom_tags[0].key absent y START_NOT_REACHED custom_tags[0].key +custom_tags[0].key x absent START_NOT_REACHED custom_tags[0].key +custom_tags[0].key x y START_NOT_REACHED custom_tags[0].key +custom_tags[0].key y absent START_NOT_REACHED custom_tags[0].key +custom_tags[0].key y x START_NOT_REACHED custom_tags[0].key +custom_tags[0].value absent x START_NOT_REACHED custom_tags[0].value +custom_tags[0].value absent y START_NOT_REACHED custom_tags[0].value +custom_tags[0].value x absent START_NOT_REACHED custom_tags[0].value +custom_tags[0].value x y START_NOT_REACHED custom_tags[0].value +custom_tags[0].value y absent START_NOT_REACHED custom_tags[0].value +custom_tags[0].value y x START_NOT_REACHED custom_tags[0].value enable_pg_native_login absent true COLLATERAL_DRIFT capacity enable_pg_native_login false true COLLATERAL_DRIFT capacity enable_pg_native_login true absent START_NOT_REACHED enable_pg_native_login @@ -29,14 +30,14 @@ enable_readable_secondaries absent true COLLATERAL_DR enable_readable_secondaries false true COLLATERAL_DRIFT capacity enable_readable_secondaries true absent START_NOT_REACHED enable_readable_secondaries enable_readable_secondaries true false START_NOT_REACHED enable_readable_secondaries -node_count 1 2 COLLATERAL_DRIFT capacity +node_count 1 2 START_NOT_REACHED node_count node_count 1 absent START_NOT_REACHED node_count node_count 2 1 START_NOT_REACHED node_count node_count 2 absent START_NOT_REACHED node_count node_count absent 1 COLLATERAL_DRIFT capacity node_count absent 2 COLLATERAL_DRIFT capacity retention_window_in_days 1 2 START_NOT_REACHED retention_window_in_days -retention_window_in_days 1 absent COLLATERAL_DRIFT capacity +retention_window_in_days 1 absent START_NOT_REACHED retention_window_in_days retention_window_in_days 2 1 START_NOT_REACHED retention_window_in_days retention_window_in_days 2 absent START_NOT_REACHED retention_window_in_days retention_window_in_days absent 1 COLLATERAL_DRIFT capacity @@ -48,16 +49,15 @@ stopped true false START_NOT_REA usage_policy_id absent x COLLATERAL_DRIFT capacity usage_policy_id absent y COLLATERAL_DRIFT capacity usage_policy_id x absent START_NOT_REACHED usage_policy_id -usage_policy_id x y COLLATERAL_DRIFT capacity +usage_policy_id x y START_NOT_REACHED usage_policy_id usage_policy_id y absent START_NOT_REACHED usage_policy_id usage_policy_id y x START_NOT_REACHED usage_policy_id === summary OK_RECREATE 32 -SUPPRESSED 5 -NOT_OBSERVABLE 3 +SUPPRESSED 6 +NOT_OBSERVABLE 1 UPDATE_IGNORED 2 -POST_DEPLOY_DRIFT 1 -COLLATERAL_DRIFT 31 +COLLATERAL_DRIFT 17 SKIPPED 24 -START_NOT_REACHED 18 +START_NOT_REACHED 34 diff --git a/bundle/direct/tests/output/experiments.txt b/bundle/direct/tests/output/experiments.txt index 05418c58a45..cdd8fea66c6 100644 --- a/bundle/direct/tests/output/experiments.txt +++ b/bundle/direct/tests/output/experiments.txt @@ -5,16 +5,21 @@ tags len0 absent START_NOT_REA tags len0 len1 START_NOT_REACHED tags (declared inert: terraform_compat) tags len1 absent START_NOT_REACHED tags (declared inert: terraform_compat) tags len1 len0 START_NOT_REACHED tags (declared inert: terraform_compat) +tags[0].key absent x START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].key absent y START_NOT_REACHED tags[0].key (declared inert: terraform_compat) tags[0].key x absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) tags[0].key x y START_NOT_REACHED tags[0].key (declared inert: terraform_compat) tags[0].key y absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].key y x START_NOT_REACHED tags[0].key (declared inert: terraform_compat) tags[0].value absent x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value absent y START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value x absent START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value x y START_NOT_REACHED tags[0].value (declared inert: terraform_compat) tags[0].value y absent START_NOT_REACHED tags[0].value (declared inert: terraform_compat) tags[0].value y x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) === summary OK 2 OK_RECREATE 12 -OK_INERT 6 SKIPPED 2 -START_NOT_REACHED 12 +START_NOT_REACHED 18 diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index 6d1ef416036..fd425c680f2 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,5 +1,6 @@ === genie_space.yml.tmpl description x absent UPDATE_IGNORED description +description y absent UPDATE_IGNORED description parent_path absent x-[UNIQUE_NAME] START_NOT_REACHED parent_path parent_path absent y-[UNIQUE_NAME] START_NOT_REACHED parent_path parent_path x-[UNIQUE_NAME] absent START_NOT_REACHED parent_path @@ -16,8 +17,8 @@ warehouse_id x absent UPDATE_IGNORE warehouse_id y absent UPDATE_IGNORED warehouse_id === summary -OK 9 +OK 8 NOT_OBSERVABLE 6 -UPDATE_IGNORED 5 +UPDATE_IGNORED 6 SKIPPED 2 START_NOT_REACHED 10 diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt index 35405d4397a..1544d5505e0 100644 --- a/bundle/direct/tests/output/instance_pools.txt +++ b/bundle/direct/tests/output/instance_pools.txt @@ -1,14 +1,27 @@ === instance_pool.yml.tmpl +aws_attributes.availability ON_DEMAND absent START_NOT_REACHED aws_attributes.availability +aws_attributes.availability SPOT ON_DEMAND START_NOT_REACHED aws_attributes.availability +aws_attributes.instance_profile_arn x y START_NOT_REACHED aws_attributes.instance_profile_arn +aws_attributes.instance_profile_arn y absent START_NOT_REACHED aws_attributes.instance_profile_arn +aws_attributes.zone_id x absent START_NOT_REACHED aws_attributes.zone_id +azure_attributes.availability SPOT_AZURE absent START_NOT_REACHED azure_attributes.availability +azure_attributes.capacity_reservation_group x y START_NOT_REACHED azure_attributes.capacity_reservation_group +azure_attributes.capacity_reservation_group y absent START_NOT_REACHED azure_attributes.capacity_reservation_group +azure_attributes.spot_bid_max_price 1 2 START_NOT_REACHED azure_attributes.spot_bid_max_price +azure_attributes.spot_bid_max_price 2 1 START_NOT_REACHED azure_attributes.spot_bid_max_price +azure_attributes.spot_bid_max_price 2 absent START_NOT_REACHED azure_attributes.spot_bid_max_price enable_elastic_disk absent true UPDATE_IGNORED enable_elastic_disk enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk enable_elastic_disk true absent START_NOT_REACHED enable_elastic_disk enable_elastic_disk true false START_NOT_REACHED enable_elastic_disk +gcp_attributes.gcp_availability PREEMPTIBLE_GCP absent START_NOT_REACHED gcp_attributes.gcp_availability +gcp_attributes.zone_id y absent START_NOT_REACHED gcp_attributes.zone_id === summary -OK 104 +OK 91 OK_RECREATE 78 SUPPRESSED 6 NOT_OBSERVABLE 4 UPDATE_IGNORED 2 SKIPPED 1 -START_NOT_REACHED 2 +START_NOT_REACHED 15 diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index 5c0dbfcb3d9..f6ba6d1ffae 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -2,11 +2,12 @@ deployment.version_id x absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id x y START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) +deployment.version_id y x START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) === summary OK 2858 -OK_INERT 3 +OK_INERT 2 SUPPRESSED 18 NOT_OBSERVABLE 232 SKIPPED 3 -START_NOT_REACHED 3 +START_NOT_REACHED 4 diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index 2c1cd8fdf04..0dbcb9ecfc6 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -1,30 +1,45 @@ === model_serving_endpoint.yml.tmpl budget_policy_id x absent START_NOT_REACHED budget_policy_id (declared inert: no_update_api) budget_policy_id x y START_NOT_REACHED budget_policy_id (declared inert: no_update_api) +budget_policy_id y absent START_NOT_REACHED budget_policy_id (declared inert: no_update_api) +budget_policy_id y x START_NOT_REACHED budget_policy_id (declared inert: no_update_api) +config.auto_capture_config.catalog_name absent x POST_DEPLOY_DRIFT config config.auto_capture_config.catalog_name absent y POST_DEPLOY_DRIFT config -config.auto_capture_config.catalog_name y absent POST_DEPLOY_DRIFT config +config.auto_capture_config.catalog_name x y START_NOT_REACHED config.auto_capture_config.catalog_name +config.auto_capture_config.catalog_name y absent START_NOT_REACHED config.auto_capture_config.catalog_name config.auto_capture_config.enabled absent false POST_DEPLOY_DRIFT config config.auto_capture_config.enabled absent true POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled false absent POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled false true POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled true absent POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled true false POST_DEPLOY_DRIFT config -config.auto_capture_config.schema_name x absent POST_DEPLOY_DRIFT config -config.auto_capture_config.table_name_prefix y absent POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled false absent START_NOT_REACHED config.auto_capture_config.enabled +config.auto_capture_config.enabled false true START_NOT_REACHED config.auto_capture_config.enabled +config.auto_capture_config.enabled true absent START_NOT_REACHED config.auto_capture_config.enabled +config.auto_capture_config.enabled true false START_NOT_REACHED config.auto_capture_config.enabled +config.auto_capture_config.schema_name absent x POST_DEPLOY_DRIFT config +config.auto_capture_config.schema_name x absent START_NOT_REACHED config.auto_capture_config.schema_name +config.auto_capture_config.schema_name x y START_NOT_REACHED config.auto_capture_config.schema_name +config.auto_capture_config.table_name_prefix absent x POST_DEPLOY_DRIFT config +config.auto_capture_config.table_name_prefix x y START_NOT_REACHED config.auto_capture_config.table_name_prefix +config.auto_capture_config.table_name_prefix y absent START_NOT_REACHED config.auto_capture_config.table_name_prefix +rate_limits absent len0 START_NOT_REACHED rate_limits (declared inert: not_implemented) +rate_limits absent len1 START_NOT_REACHED rate_limits (declared inert: not_implemented) rate_limits len0 absent START_NOT_REACHED rate_limits (declared inert: not_implemented) +rate_limits len0 len1 START_NOT_REACHED rate_limits (declared inert: not_implemented) rate_limits len1 absent START_NOT_REACHED rate_limits (declared inert: not_implemented) rate_limits len1 len0 START_NOT_REACHED rate_limits (declared inert: not_implemented) rate_limits[0].calls 1 2 START_NOT_REACHED rate_limits[0].calls (declared inert: not_implemented) +rate_limits[0].calls 2 1 START_NOT_REACHED rate_limits[0].calls (declared inert: not_implemented) +rate_limits[0].key absent endpoint START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) +rate_limits[0].key absent user START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) rate_limits[0].key endpoint absent START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) +rate_limits[0].key endpoint user START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) rate_limits[0].key user absent START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) rate_limits[0].key user endpoint START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) === summary OK 150 -OK_RECREATE 26 -OK_INERT 11 +OK_RECREATE 20 +OK_INERT 2 SUPPRESSED 14 NOT_OBSERVABLE 10 -POST_DEPLOY_DRIFT 10 +POST_DEPLOY_DRIFT 6 SKIPPED 2 -START_NOT_REACHED 9 +START_NOT_REACHED 28 diff --git a/bundle/direct/tests/output/models.txt b/bundle/direct/tests/output/models.txt index 793b301c662..f25306d6f88 100644 --- a/bundle/direct/tests/output/models.txt +++ b/bundle/direct/tests/output/models.txt @@ -1,12 +1,27 @@ === model.yml.tmpl description x absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) description y absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) +tags absent len0 START_NOT_REACHED tags (declared inert: terraform_compat) +tags absent len1 START_NOT_REACHED tags (declared inert: terraform_compat) tags len0 absent START_NOT_REACHED tags (declared inert: terraform_compat) +tags len0 len1 START_NOT_REACHED tags (declared inert: terraform_compat) +tags[0].key absent x START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].key absent y START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].key x absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].key x y START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].key y absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].key y x START_NOT_REACHED tags[0].key (declared inert: terraform_compat) +tags[0].value absent x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value absent y START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value x absent START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value x y START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value y absent START_NOT_REACHED tags[0].value (declared inert: terraform_compat) +tags[0].value y x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) === summary OK 4 OK_RECREATE 2 -OK_INERT 17 +OK_INERT 2 BACKEND_ERROR 2 SKIPPED 1 -START_NOT_REACHED 1 +START_NOT_REACHED 16 diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 3f46744f300..8af082e3aef 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -2,17 +2,18 @@ deployment.version_id x absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id x y START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) +deployment.version_id y x START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) dry_run absent true UPDATE_IGNORED dry_run dry_run false true UPDATE_IGNORED dry_run dry_run true absent START_NOT_REACHED dry_run dry_run true false START_NOT_REACHED dry_run === summary -OK 806 +OK 807 OK_RECREATE 33 -OK_INERT 9 -SUPPRESSED 46 +OK_INERT 8 +SUPPRESSED 45 NOT_OBSERVABLE 29 UPDATE_IGNORED 2 SKIPPED 2 -START_NOT_REACHED 5 +START_NOT_REACHED 6 diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index a7463308a00..3472bd44a8e 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -1,42 +1,66 @@ === postgres_project.yml.tmpl +budget_policy_id absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +budget_policy_id absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... budget_policy_id x absent START_NOT_REACHED budget_policy_id -budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -custom_tags len1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +budget_policy_id x y START_NOT_REACHED budget_policy_id +budget_policy_id y absent START_NOT_REACHED budget_policy_id +budget_policy_id y x START_NOT_REACHED budget_policy_id +custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +custom_tags absent len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +custom_tags len0 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +custom_tags len0 len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +custom_tags len1 absent START_NOT_REACHED custom_tags custom_tags len1 len0 START_NOT_REACHED custom_tags -custom_tags[0].key absent x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].key absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key x y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].key y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].value absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value absent y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value x y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +custom_tags[0].value y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_branch x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_branch y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... -default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.autoscaling_limit_max_cu 1 2 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu +default_endpoint_settings.autoscaling_limit_max_cu 1 absent START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu +default_endpoint_settings.autoscaling_limit_max_cu 2 1 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu +default_endpoint_settings.autoscaling_limit_max_cu 2 absent START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu +default_endpoint_settings.autoscaling_limit_max_cu absent 1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_max_cu absent 2 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu +default_endpoint_settings.autoscaling_limit_min_cu 1 2 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu +default_endpoint_settings.autoscaling_limit_min_cu 1 absent START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu +default_endpoint_settings.autoscaling_limit_min_cu 2 1 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu +default_endpoint_settings.autoscaling_limit_min_cu 2 absent START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu +default_endpoint_settings.autoscaling_limit_min_cu absent 1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_min_cu absent 2 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu +default_endpoint_settings.no_suspension absent false START_NOT_REACHED default_endpoint_settings.no_suspension +default_endpoint_settings.no_suspension absent true START_NOT_REACHED default_endpoint_settings.no_suspension +default_endpoint_settings.no_suspension false absent START_NOT_REACHED default_endpoint_settings.no_suspension +default_endpoint_settings.no_suspension false true START_NOT_REACHED default_endpoint_settings.no_suspension +default_endpoint_settings.no_suspension true absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension true false BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings absent keys0 POST_DEPLOY_DRIFT default_endpoint_settings +default_endpoint_settings.pg_settings absent keys0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... +default_endpoint_settings.pg_settings absent keys1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... +default_endpoint_settings.pg_settings keys0 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings keys0 keys1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings keys1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... -default_endpoint_settings.pg_settings keys1 keys0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings keys1 absent START_NOT_REACHED default_endpoint_settings.pg_settings +default_endpoint_settings.pg_settings keys1 keys0 START_NOT_REACHED default_endpoint_settings.pg_settings default_endpoint_settings.pg_settings['work_mem'] absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] x y BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] y x BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.pg_settings['work_mem'] y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +display_name x absent START_NOT_REACHED display_name +display_name x y START_NOT_REACHED display_name display_name y absent START_NOT_REACHED display_name +display_name y x START_NOT_REACHED display_name enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... enable_pg_native_login true false START_NOT_REACHED enable_pg_native_login pg_version 16 absent START_NOT_REACHED pg_version @@ -44,12 +68,10 @@ pg_version 17 16 START_NOT_REA purge_on_delete true absent START_NOT_REACHED purge_on_delete === summary -OK 30 +OK 9 OK_RECREATE 6 SUPPRESSED 4 -NOT_OBSERVABLE 3 -POST_DEPLOY_DRIFT 1 -BACKEND_ERROR 29 +BACKEND_ERROR 3 SKIPPED 1 -BASE_ERROR 6 -START_NOT_REACHED 7 +BASE_ERROR 34 +START_NOT_REACHED 30 diff --git a/bundle/direct/tests/output/postgres_synced_tables.txt b/bundle/direct/tests/output/postgres_synced_tables.txt index 32dc99dd90a..6d576d01d52 100644 --- a/bundle/direct/tests/output/postgres_synced_tables.txt +++ b/bundle/direct/tests/output/postgres_synced_tables.txt @@ -1,11 +1,57 @@ === postgres_synced_table.yml.tmpl existing_pipeline_id x absent START_NOT_REACHED existing_pipeline_id +extra_columns[0].column_name x y START_NOT_REACHED extra_columns[0].column_name +extra_columns[0].column_name y x START_NOT_REACHED extra_columns[0].column_name +extra_columns[0].column_type x y START_NOT_REACHED extra_columns[0].column_type +extra_columns[0].column_type y x START_NOT_REACHED extra_columns[0].column_type +extra_columns[0].compute absent x START_NOT_REACHED extra_columns[0].compute +extra_columns[0].compute absent y START_NOT_REACHED extra_columns[0].compute +extra_columns[0].compute x absent START_NOT_REACHED extra_columns[0].compute +extra_columns[0].compute x y START_NOT_REACHED extra_columns[0].compute +extra_columns[0].compute y absent START_NOT_REACHED extra_columns[0].compute +extra_columns[0].compute y x START_NOT_REACHED extra_columns[0].compute +extra_columns[0].maintenance STORED_GENERATED absent START_NOT_REACHED extra_columns[0].maintenance +extra_columns[0].maintenance absent STORED_GENERATED START_NOT_REACHED extra_columns[0].maintenance +new_pipeline_spec.budget_policy_id absent x START_NOT_REACHED new_pipeline_spec.budget_policy_id +new_pipeline_spec.budget_policy_id absent y START_NOT_REACHED new_pipeline_spec.budget_policy_id +new_pipeline_spec.budget_policy_id x absent START_NOT_REACHED new_pipeline_spec.budget_policy_id +new_pipeline_spec.budget_policy_id x y START_NOT_REACHED new_pipeline_spec.budget_policy_id +new_pipeline_spec.budget_policy_id y absent START_NOT_REACHED new_pipeline_spec.budget_policy_id +new_pipeline_spec.budget_policy_id y x START_NOT_REACHED new_pipeline_spec.budget_policy_id +new_pipeline_spec.pipeline_channel CURRENT PREVIEW START_NOT_REACHED new_pipeline_spec.pipeline_channel +new_pipeline_spec.pipeline_channel CURRENT absent START_NOT_REACHED new_pipeline_spec.pipeline_channel +new_pipeline_spec.pipeline_channel PREVIEW CURRENT START_NOT_REACHED new_pipeline_spec.pipeline_channel +new_pipeline_spec.pipeline_channel PREVIEW absent START_NOT_REACHED new_pipeline_spec.pipeline_channel +new_pipeline_spec.pipeline_channel absent CURRENT START_NOT_REACHED new_pipeline_spec.pipeline_channel +new_pipeline_spec.pipeline_channel absent PREVIEW START_NOT_REACHED new_pipeline_spec.pipeline_channel +new_pipeline_spec.storage_catalog absent x START_NOT_REACHED new_pipeline_spec.storage_catalog +new_pipeline_spec.storage_catalog absent y START_NOT_REACHED new_pipeline_spec.storage_catalog +new_pipeline_spec.storage_catalog x absent START_NOT_REACHED new_pipeline_spec.storage_catalog +new_pipeline_spec.storage_catalog x y START_NOT_REACHED new_pipeline_spec.storage_catalog +new_pipeline_spec.storage_catalog y absent START_NOT_REACHED new_pipeline_spec.storage_catalog +new_pipeline_spec.storage_catalog y x START_NOT_REACHED new_pipeline_spec.storage_catalog +new_pipeline_spec.storage_schema absent x START_NOT_REACHED new_pipeline_spec.storage_schema +new_pipeline_spec.storage_schema absent y START_NOT_REACHED new_pipeline_spec.storage_schema +new_pipeline_spec.storage_schema x absent START_NOT_REACHED new_pipeline_spec.storage_schema +new_pipeline_spec.storage_schema x y START_NOT_REACHED new_pipeline_spec.storage_schema +new_pipeline_spec.storage_schema y absent START_NOT_REACHED new_pipeline_spec.storage_schema +new_pipeline_spec.storage_schema y x START_NOT_REACHED new_pipeline_spec.storage_schema primary_key_columns len1 absent START_NOT_REACHED primary_key_columns timeseries_key y absent START_NOT_REACHED timeseries_key +type_overrides[0].column_name x y START_NOT_REACHED type_overrides[0].column_name +type_overrides[0].column_name y x START_NOT_REACHED type_overrides[0].column_name +type_overrides[0].pg_type PG_SPECIFIC_TYPE_HALFVEC PG_SPECIFIC_TYPE_VARCHAR START_NOT_REACHED type_overrides[0].pg_type +type_overrides[0].pg_type PG_SPECIFIC_TYPE_VARCHAR PG_SPECIFIC_TYPE_HALFVEC START_NOT_REACHED type_overrides[0].pg_type +type_overrides[0].size 1 2 START_NOT_REACHED type_overrides[0].size +type_overrides[0].size 1 absent START_NOT_REACHED type_overrides[0].size +type_overrides[0].size 2 1 START_NOT_REACHED type_overrides[0].size +type_overrides[0].size 2 absent START_NOT_REACHED type_overrides[0].size +type_overrides[0].size absent 1 START_NOT_REACHED type_overrides[0].size +type_overrides[0].size absent 2 START_NOT_REACHED type_overrides[0].size === summary -OK_RECREATE 107 +OK_RECREATE 61 SUPPRESSED 6 NOT_OBSERVABLE 4 SKIPPED 1 -START_NOT_REACHED 3 +START_NOT_REACHED 49 diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 0f86ad7a604..a820e25bbc7 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,5 +1,35 @@ === registered_model.yml.tmpl -catalog_name absent main BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) +aliases[0].alias_name absent x START_NOT_REACHED aliases[0].alias_name +aliases[0].alias_name absent y START_NOT_REACHED aliases[0].alias_name +aliases[0].alias_name x absent START_NOT_REACHED aliases[0].alias_name +aliases[0].alias_name x y START_NOT_REACHED aliases[0].alias_name +aliases[0].alias_name y absent START_NOT_REACHED aliases[0].alias_name +aliases[0].alias_name y x START_NOT_REACHED aliases[0].alias_name +aliases[0].catalog_name absent x START_NOT_REACHED aliases[0].catalog_name +aliases[0].catalog_name absent y START_NOT_REACHED aliases[0].catalog_name +aliases[0].catalog_name x absent START_NOT_REACHED aliases[0].catalog_name +aliases[0].catalog_name x y START_NOT_REACHED aliases[0].catalog_name +aliases[0].catalog_name y absent START_NOT_REACHED aliases[0].catalog_name +aliases[0].catalog_name y x START_NOT_REACHED aliases[0].catalog_name +aliases[0].model_name absent x START_NOT_REACHED aliases[0].model_name +aliases[0].model_name absent y START_NOT_REACHED aliases[0].model_name +aliases[0].model_name x absent START_NOT_REACHED aliases[0].model_name +aliases[0].model_name x y START_NOT_REACHED aliases[0].model_name +aliases[0].model_name y absent START_NOT_REACHED aliases[0].model_name +aliases[0].model_name y x START_NOT_REACHED aliases[0].model_name +aliases[0].schema_name absent x START_NOT_REACHED aliases[0].schema_name +aliases[0].schema_name absent y START_NOT_REACHED aliases[0].schema_name +aliases[0].schema_name x absent START_NOT_REACHED aliases[0].schema_name +aliases[0].schema_name x y START_NOT_REACHED aliases[0].schema_name +aliases[0].schema_name y absent START_NOT_REACHED aliases[0].schema_name +aliases[0].schema_name y x START_NOT_REACHED aliases[0].schema_name +aliases[0].version_num 1 2 START_NOT_REACHED aliases[0].version_num +aliases[0].version_num 1 absent START_NOT_REACHED aliases[0].version_num +aliases[0].version_num 2 1 START_NOT_REACHED aliases[0].version_num +aliases[0].version_num 2 absent START_NOT_REACHED aliases[0].version_num +aliases[0].version_num absent 1 START_NOT_REACHED aliases[0].version_num +aliases[0].version_num absent 2 START_NOT_REACHED aliases[0].version_num +catalog_name absent main BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) full_name absent x UPDATE_IGNORED full_name full_name absent y UPDATE_IGNORED full_name @@ -7,8 +37,8 @@ full_name x absent START_NOT_REA full_name x y START_NOT_REACHED full_name full_name y absent START_NOT_REACHED full_name full_name y x START_NOT_REACHED full_name -name absent x-[UNIQUE_NAME] BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name absent y-[UNIQUE_NAME] BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name absent x-[UNIQUE_NAME] BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name absent y-[UNIQUE_NAME] BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name x-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name y-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) owner absent x UPDATE_IGNORED owner @@ -17,11 +47,11 @@ owner x absent START_NOT_REA owner x y START_NOT_REACHED owner owner y absent START_NOT_REACHED owner owner y x START_NOT_REACHED owner -schema_name absent default BASE_ERROR cannot create resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) +schema_name absent default BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) schema_name default absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) === summary -OK 40 +OK 10 OK_RECREATE 2 SUPPRESSED 1 NOT_OBSERVABLE 1 @@ -29,4 +59,4 @@ UPDATE_IGNORED 4 BACKEND_ERROR 4 SKIPPED 9 BASE_ERROR 4 -START_NOT_REACHED 8 +START_NOT_REACHED 38 diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index 7cb413f4e81..49456642c34 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -2,18 +2,19 @@ custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours properties absent keys0 START_NOT_REACHED properties +properties absent keys1 START_NOT_REACHED properties properties keys0 absent START_NOT_REACHED properties +properties keys0 keys1 START_NOT_REACHED properties properties keys1 absent UPDATE_IGNORED properties -properties keys1 keys0 POST_DEPLOY_DRIFT properties,properties['team'] +properties keys1 keys0 UPDATE_IGNORED properties properties['team'] absent x START_NOT_REACHED properties['team'] properties['team'] absent y START_NOT_REACHED properties['team'] properties['team'] x absent UPDATE_IGNORED properties['team'] properties['team'] y absent UPDATE_IGNORED properties['team'] === summary -OK 14 +OK 12 OK_RECREATE 2 -UPDATE_IGNORED 5 -POST_DEPLOY_DRIFT 1 +UPDATE_IGNORED 6 SKIPPED 2 -START_NOT_REACHED 4 +START_NOT_REACHED 6 diff --git a/bundle/direct/tests/output/secret_scopes.txt b/bundle/direct/tests/output/secret_scopes.txt index 6c91b18e642..1742efc3439 100644 --- a/bundle/direct/tests/output/secret_scopes.txt +++ b/bundle/direct/tests/output/secret_scopes.txt @@ -1,6 +1,6 @@ === secret_scope.yml.tmpl -backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot create resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARAME... -backend_type AZURE_KEYVAULT absent BASE_ERROR cannot create resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARAME... +backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... +backend_type AZURE_KEYVAULT absent BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... backend_type DATABRICKS AZURE_KEYVAULT BACKEND_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... backend_type absent AZURE_KEYVAULT BACKEND_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index bafd55aba2a..db1bab1403d 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -24,8 +24,7 @@ spot_instance_policy absent POLICY_UNSPECIFIED START === summary OK 32 -SUPPRESSED 1 -NOT_OBSERVABLE 1 +SUPPRESSED 2 UPDATE_IGNORED 11 SKIPPED 5 START_NOT_REACHED 11 diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 19dab0083f9..57ca0829490 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -407,9 +407,12 @@ func (fv *fieldValues) skipReason(path string) (string, bool) { concrete, err := structpath.ParsePath(path) if err != nil { - // A pattern, which cannot be matched against another pattern: compare textually. + // A pattern, which cannot be matched against another pattern: compare textually. A key + // naming a whole subtree may itself end in a wildcard, so trim that first -- otherwise + // "a.b.*" would not cover the pattern "a.b.c". for key, reason := range fv.skip { - if strings.HasPrefix(path, key+".") || strings.HasPrefix(path, key+"[") { + prefix := strings.TrimSuffix(strings.TrimSuffix(key, "[*]"), ".*") + if strings.HasPrefix(path, prefix+".") || strings.HasPrefix(path, prefix+"[") { return reason, true } } diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go index 65ebd255949..903079b0f1d 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/tests/values_unit_test.go @@ -145,9 +145,9 @@ func TestSetFieldAbsentAndEmpty(t *testing.T) { // element wildcard, subtree -- take different paths through the matcher. func TestSkipReasonMatchesPatterns(t *testing.T) { fv := &fieldValues{skip: map[string]string{ - "storage_root": "needs an external location", - "aliases[*].id": "assigned by the backend", - "telemetry_config": "only accepted as a whole", + "storage_root": "needs an external location", + "aliases[*].id": "assigned by the backend", + "telemetry_config.*": "only accepted as a whole", }} //exhaustruct:ignore for _, tc := range []struct { @@ -162,6 +162,9 @@ func TestSkipReasonMatchesPatterns(t *testing.T) { {"telemetry_config.sinks[*].name", "only accepted as a whole"}, {"aliases[0].alias_name", ""}, {"comment", ""}, + // A key naming a whole subtree, written with the trailing wildcard, still covers a + // pattern beneath it -- which is the form a field under an absent container takes. + {"telemetry_config.sinks[*].endpoint", "only accepted as a whole"}, } { t.Run(tc.path, func(t *testing.T) { reason, skipped := fv.skipReason(tc.path) From 75f5c78f878ee0ed45f101373157efdac22c83c5 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 14:31:44 +0200 Subject: [PATCH 040/118] Field catalog: dedupe field paths, shorten labels, model two policy constraints From working instance_pools and cluster_policies against AWS. A field reachable by two paths was tested and reported twice: WalkType visits a cluster policy's definition once on the bundle struct and once on the embedded SDK struct, both under the same json name, and structaccess resolves it to one field. One row per path now. A value long enough or punctuated enough to be unreadable as a subtest name (a policy definition is a whole JSON document) gets a trimmed, digest-suffixed label, so it stays readable and safe to paste back into a test filter. Generated ids in error messages are redacted the way the unique suffix already was: a UUID and a backend id both differ per run, so the goldens were not stable without it. Two constraints the fake server did not enforce, both taken from cloud error messages: a policy must have a definition or a family to take one from, and a library entry must name exactly one artifact. The generic CRUD test in dresources built a zero-valued policy, so it gets a realistic config. Instance pools skip what a test workspace cannot offer: a preloaded image needs custom containers enabled, node_type_flexibility needs a second node type, and only one of the three cloud attribute blocks applies, with the backend filling in defaults for it so a value set there cannot be cleared. Co-authored-by: Isaac --- bundle/direct/dresources/all_test.go | 9 ++++ bundle/direct/tests/fields_test.go | 53 +++++++++++++++--- bundle/direct/tests/output/apps.txt | 12 ++--- .../direct/tests/output/cluster_policies.txt | 13 ++++- bundle/direct/tests/output/dashboards.txt | 20 +++---- bundle/direct/tests/output/genie_spaces.txt | 20 +++---- bundle/direct/tests/output/instance_pools.txt | 25 +++------ .../direct/tests/output/registered_models.txt | 8 +-- bundle/direct/tests/output/sql_warehouses.txt | 8 +-- .../testdata/fields/cluster_policies.yml | 18 ++++--- .../tests/testdata/fields/instance_pools.yml | 22 ++++---- bundle/direct/tests/values_test.go | 9 ++++ libs/testserver/cluster_policies.go | 31 +++++++++++ libs/testserver/merge.go | 54 +++++++++++++++++++ libs/testserver/merge_test.go | 46 ++++++++++++++++ libs/testserver/sql_warehouses.go | 15 +++++- 16 files changed, 285 insertions(+), 78 deletions(-) create mode 100644 libs/testserver/merge.go create mode 100644 libs/testserver/merge_test.go diff --git a/bundle/direct/dresources/all_test.go b/bundle/direct/dresources/all_test.go index 36eaa3e27df..3a75e8c0676 100644 --- a/bundle/direct/dresources/all_test.go +++ b/bundle/direct/dresources/all_test.go @@ -41,6 +41,15 @@ var testConfig map[string]any = map[string]any{ }, }, + // A policy has to say what it constrains: the API rejects one with no definition and no + // policy family to take one from. + "cluster_policies": &resources.ClusterPolicy{ + CreatePolicy: compute.CreatePolicy{ + Name: "mypolicy", + Definition: `{"spark_version":{"type":"fixed","value":"13.3.x-scala2.12"}}`, + }, + }, + "catalogs": &resources.Catalog{ CreateCatalog: catalog.CreateCatalog{ Name: "mycatalog", diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index aa2d2287a92..1df9ac80843 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -35,6 +35,7 @@ import ( "encoding/json" "errors" "fmt" + "hash/fnv" "maps" "reflect" "regexp" @@ -534,17 +535,38 @@ func valueLabel(v any) string { case "": return "empty" default: - return strings.ReplaceAll(s, " ", "_") + return shortLabel(s) } } +// labelSafe matches the characters a label keeps verbatim: everything else would need +// quoting to pass a label back to `go test -run`. +var labelSafe = regexp.MustCompile(`[^A-Za-z0-9._@/:=-]+`) + +// shortLabel renders a value as a token that is readable, stable, and safe to paste into a +// -run filter. A value that survives as-is is left alone; anything long or punctuated -- a +// cluster policy definition is a whole JSON document -- is trimmed and given a short digest, +// so two values that share a prefix still get different labels. +func shortLabel(s string) string { + const maxLabel = 24 + + clean := labelSafe.ReplaceAllString(s, "_") + if clean == s && len(clean) <= maxLabel { + return clean + } + + digest := fnv.New32a() + _, _ = digest.Write([]byte(s)) + return strings.Trim(clean[:min(len(clean), maxLabel-5)], "_") + "~" + strconv.FormatUint(uint64(digest.Sum32())%0x10000, 16) +} + // allErrors returns every error diagnostic in full -- status, code, message, endpoint -- // the way the CLI would print it. firstError's one-line form is for the report column. func allErrors(diags diag.Diagnostics) string { var out []string for _, d := range diags { if d.Severity == diag.Error { - out = append(out, idPattern.ReplaceAllString(d.Summary, "[UNIQUE_NAME]")) + out = append(out, redactIDs(d.Summary)) } } return strings.Join(out, "\n") @@ -715,10 +737,27 @@ func driftDetail(plan *deployplan.Plan, node string, baseline map[string]bool) ( return strings.Join(ownPaths, ","), strings.Join(childNodes, " ") } -// idPattern matches the unique suffix this suite gives every resource it creates. The -// suffix is random per run, and error messages quote resource names and ids, so it has -// to be redacted or the goldens differ on every run. -var idPattern = regexp.MustCompile(`f[0-9a-f]{20}`) +// generatedIDs are the shapes of value that differ between runs, all of which turn up inside +// quoted resource names and ids in error messages -- so they have to be redacted or no golden +// would ever be stable. Ordered: the unique suffix first, since it is the most specific. +var generatedIDs = []struct { + pattern *regexp.Regexp + replacement string +}{ + // The suffix this suite gives every resource it creates. + {regexp.MustCompile(`f[0-9a-f]{20}`), "[UNIQUE_NAME]"}, + {regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`), "[UUID]"}, + // A backend-assigned id, which is a bare number or hex string only ever seen after "id=". + {regexp.MustCompile(`id=[0-9A-Fa-f]{6,}`), "id=[ID]"}, +} + +// redactIDs replaces every run-specific id in a message with a stable placeholder. +func redactIDs(s string) string { + for _, id := range generatedIDs { + s = id.pattern.ReplaceAllString(s, id.replacement) + } + return s +} // simplerConfig reports whether a is the simpler of two config names for one resource type. // Shorter wins, so "job.yml.tmpl" beats "job_with_depends_on.yml.tmpl"; length ties break @@ -757,7 +796,7 @@ func planJSON(plan *deployplan.Plan) string { } func oneLine(s string) string { - s = idPattern.ReplaceAllString(s, "[UNIQUE_NAME]") + s = redactIDs(s) s = strings.ReplaceAll(s, "\n", " ") s = strings.Join(strings.Fields(s), " ") if len(s) > 140 { diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 0fd3c44282a..1208dcc947f 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -112,12 +112,12 @@ resources[0].postgres.branch y absent UPDATE_IGNORE resources[0].postgres.database x absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.database y absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.permission CAN_CONNECT_AND_CREATE absent UPDATE_IGNORED resources[0].postgres.permission -source_code_path absent x-[UNIQUE_NAME] START_NOT_REACHED source_code_path -source_code_path absent y-[UNIQUE_NAME] START_NOT_REACHED source_code_path -source_code_path x-[UNIQUE_NAME] absent START_NOT_REACHED source_code_path -source_code_path x-[UNIQUE_NAME] y-[UNIQUE_NAME] START_NOT_REACHED source_code_path -source_code_path y-[UNIQUE_NAME] absent START_NOT_REACHED source_code_path -source_code_path y-[UNIQUE_NAME] x-[UNIQUE_NAME] START_NOT_REACHED source_code_path +source_code_path absent x-_UNIQUE_NAME~89d START_NOT_REACHED source_code_path +source_code_path absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED source_code_path +source_code_path x-_UNIQUE_NAME~89d absent START_NOT_REACHED source_code_path +source_code_path x-_UNIQUE_NAME~89d y-_UNIQUE_NAME~57d2 START_NOT_REACHED source_code_path +source_code_path y-_UNIQUE_NAME~57d2 absent START_NOT_REACHED source_code_path +source_code_path y-_UNIQUE_NAME~57d2 x-_UNIQUE_NAME~89d START_NOT_REACHED source_code_path space x y START_NOT_REACHED space space y absent START_NOT_REACHED space space y x START_NOT_REACHED space diff --git a/bundle/direct/tests/output/cluster_policies.txt b/bundle/direct/tests/output/cluster_policies.txt index 4d28e9c1d92..d7ec765ccaa 100644 --- a/bundle/direct/tests/output/cluster_policies.txt +++ b/bundle/direct/tests/output/cluster_policies.txt @@ -1,6 +1,17 @@ +=== cluster_policy.yml.tmpl +definition absent spark_version_:_ty~69b3 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition absent spark_version_:_ty~cbc0 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition spark_version_:_ty~69b3 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition spark_version_:_ty~cbc0 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +libraries[0].jar absent x BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: Library must specify exactly one of jar, egg, whl, pypi, maven or cran. (4... +libraries[0].jar absent y BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: Library must specify exactly one of jar, egg, whl, pypi, maven or cran. (4... +libraries[0].jar x absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: Library must specify exactly one of jar, egg, whl, pypi, maven or cran. (4... +libraries[0].jar y absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: Library must specify exactly one of jar, egg, whl, pypi, maven or cran. (4... === summary -OK 158 +OK 90 SUPPRESSED 2 NOT_OBSERVABLE 2 +BACKEND_ERROR 4 SKIPPED 1 +BASE_ERROR 4 diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index 6a5494e53ba..cd2e4813c3d 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -3,22 +3,22 @@ dataset_catalog y absent START_NOT_REA dataset_schema x y START_NOT_REACHED dataset_schema dataset_schema y absent START_NOT_REACHED dataset_schema dataset_schema y x START_NOT_REACHED dataset_schema -display_name absent x-[UNIQUE_NAME] START_NOT_REACHED display_name -display_name absent y-[UNIQUE_NAME] START_NOT_REACHED display_name -display_name x-[UNIQUE_NAME] absent UPDATE_IGNORED display_name -display_name y-[UNIQUE_NAME] absent UPDATE_IGNORED display_name +display_name absent x-_UNIQUE_NAME~89d START_NOT_REACHED display_name +display_name absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED display_name +display_name x-_UNIQUE_NAME~89d absent UPDATE_IGNORED display_name +display_name y-_UNIQUE_NAME~57d2 absent UPDATE_IGNORED display_name etag absent x START_NOT_REACHED etag etag absent y START_NOT_REACHED etag etag x absent START_NOT_REACHED etag etag x y START_NOT_REACHED etag etag y absent START_NOT_REACHED etag etag y x START_NOT_REACHED etag -parent_path absent x-[UNIQUE_NAME] START_NOT_REACHED parent_path -parent_path absent y-[UNIQUE_NAME] START_NOT_REACHED parent_path -parent_path x-[UNIQUE_NAME] absent START_NOT_REACHED parent_path -parent_path x-[UNIQUE_NAME] y-[UNIQUE_NAME] START_NOT_REACHED parent_path -parent_path y-[UNIQUE_NAME] absent START_NOT_REACHED parent_path -parent_path y-[UNIQUE_NAME] x-[UNIQUE_NAME] START_NOT_REACHED parent_path +parent_path absent x-_UNIQUE_NAME~89d START_NOT_REACHED parent_path +parent_path absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED parent_path +parent_path x-_UNIQUE_NAME~89d absent START_NOT_REACHED parent_path +parent_path x-_UNIQUE_NAME~89d y-_UNIQUE_NAME~57d2 START_NOT_REACHED parent_path +parent_path y-_UNIQUE_NAME~57d2 absent START_NOT_REACHED parent_path +parent_path y-_UNIQUE_NAME~57d2 x-_UNIQUE_NAME~89d START_NOT_REACHED parent_path === summary OK 20 diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index fd425c680f2..1f588c82f4b 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,16 +1,16 @@ === genie_space.yml.tmpl description x absent UPDATE_IGNORED description description y absent UPDATE_IGNORED description -parent_path absent x-[UNIQUE_NAME] START_NOT_REACHED parent_path -parent_path absent y-[UNIQUE_NAME] START_NOT_REACHED parent_path -parent_path x-[UNIQUE_NAME] absent START_NOT_REACHED parent_path -parent_path x-[UNIQUE_NAME] y-[UNIQUE_NAME] START_NOT_REACHED parent_path -parent_path y-[UNIQUE_NAME] absent START_NOT_REACHED parent_path -parent_path y-[UNIQUE_NAME] x-[UNIQUE_NAME] START_NOT_REACHED parent_path -title absent x-[UNIQUE_NAME] START_NOT_REACHED title -title absent y-[UNIQUE_NAME] START_NOT_REACHED title -title x-[UNIQUE_NAME] absent UPDATE_IGNORED title -title y-[UNIQUE_NAME] absent UPDATE_IGNORED title +parent_path absent x-_UNIQUE_NAME~89d START_NOT_REACHED parent_path +parent_path absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED parent_path +parent_path x-_UNIQUE_NAME~89d absent START_NOT_REACHED parent_path +parent_path x-_UNIQUE_NAME~89d y-_UNIQUE_NAME~57d2 START_NOT_REACHED parent_path +parent_path y-_UNIQUE_NAME~57d2 absent START_NOT_REACHED parent_path +parent_path y-_UNIQUE_NAME~57d2 x-_UNIQUE_NAME~89d START_NOT_REACHED parent_path +title absent x-_UNIQUE_NAME~89d START_NOT_REACHED title +title absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED title +title x-_UNIQUE_NAME~89d absent UPDATE_IGNORED title +title y-_UNIQUE_NAME~57d2 absent UPDATE_IGNORED title warehouse_id absent x START_NOT_REACHED warehouse_id warehouse_id absent y START_NOT_REACHED warehouse_id warehouse_id x absent UPDATE_IGNORED warehouse_id diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt index 1544d5505e0..9a400dad3ac 100644 --- a/bundle/direct/tests/output/instance_pools.txt +++ b/bundle/direct/tests/output/instance_pools.txt @@ -1,27 +1,14 @@ === instance_pool.yml.tmpl -aws_attributes.availability ON_DEMAND absent START_NOT_REACHED aws_attributes.availability -aws_attributes.availability SPOT ON_DEMAND START_NOT_REACHED aws_attributes.availability -aws_attributes.instance_profile_arn x y START_NOT_REACHED aws_attributes.instance_profile_arn -aws_attributes.instance_profile_arn y absent START_NOT_REACHED aws_attributes.instance_profile_arn -aws_attributes.zone_id x absent START_NOT_REACHED aws_attributes.zone_id -azure_attributes.availability SPOT_AZURE absent START_NOT_REACHED azure_attributes.availability -azure_attributes.capacity_reservation_group x y START_NOT_REACHED azure_attributes.capacity_reservation_group -azure_attributes.capacity_reservation_group y absent START_NOT_REACHED azure_attributes.capacity_reservation_group -azure_attributes.spot_bid_max_price 1 2 START_NOT_REACHED azure_attributes.spot_bid_max_price -azure_attributes.spot_bid_max_price 2 1 START_NOT_REACHED azure_attributes.spot_bid_max_price -azure_attributes.spot_bid_max_price 2 absent START_NOT_REACHED azure_attributes.spot_bid_max_price enable_elastic_disk absent true UPDATE_IGNORED enable_elastic_disk enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk enable_elastic_disk true absent START_NOT_REACHED enable_elastic_disk enable_elastic_disk true false START_NOT_REACHED enable_elastic_disk -gcp_attributes.gcp_availability PREEMPTIBLE_GCP absent START_NOT_REACHED gcp_attributes.gcp_availability -gcp_attributes.zone_id y absent START_NOT_REACHED gcp_attributes.zone_id === summary -OK 91 -OK_RECREATE 78 -SUPPRESSED 6 -NOT_OBSERVABLE 4 +OK 44 +OK_RECREATE 46 +SUPPRESSED 4 +NOT_OBSERVABLE 2 UPDATE_IGNORED 2 -SKIPPED 1 -START_NOT_REACHED 15 +SKIPPED 6 +START_NOT_REACHED 2 diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index a820e25bbc7..4dcffa601ed 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -37,10 +37,10 @@ full_name x absent START_NOT_REA full_name x y START_NOT_REACHED full_name full_name y absent START_NOT_REACHED full_name full_name y x START_NOT_REACHED full_name -name absent x-[UNIQUE_NAME] BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name absent y-[UNIQUE_NAME] BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name x-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name y-[UNIQUE_NAME] absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name absent x-_UNIQUE_NAME~89d BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name absent y-_UNIQUE_NAME~57d2 BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name x-_UNIQUE_NAME~89d absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name y-_UNIQUE_NAME~57d2 absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) owner absent x UPDATE_IGNORED owner owner absent y UPDATE_IGNORED owner owner x absent START_NOT_REACHED owner diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index db1bab1403d..0572b0ed6bb 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -13,10 +13,10 @@ max_num_clusters 2 absent UPDATE_IGNORE max_num_clusters 3 absent UPDATE_IGNORED max_num_clusters max_num_clusters absent 2 START_NOT_REACHED max_num_clusters max_num_clusters absent 3 START_NOT_REACHED max_num_clusters -name absent x-[UNIQUE_NAME] START_NOT_REACHED name -name absent y-[UNIQUE_NAME] START_NOT_REACHED name -name x-[UNIQUE_NAME] absent UPDATE_IGNORED name -name y-[UNIQUE_NAME] absent UPDATE_IGNORED name +name absent x-_UNIQUE_NAME~89d START_NOT_REACHED name +name absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED name +name x-_UNIQUE_NAME~89d absent UPDATE_IGNORED name +name y-_UNIQUE_NAME~57d2 absent UPDATE_IGNORED name spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy spot_instance_policy POLICY_UNSPECIFIED absent UPDATE_IGNORED spot_instance_policy spot_instance_policy absent COST_OPTIMIZED START_NOT_REACHED spot_instance_policy diff --git a/bundle/direct/tests/testdata/fields/cluster_policies.yml b/bundle/direct/tests/testdata/fields/cluster_policies.yml index 3dddfc36ca9..a7ae6ab2540 100644 --- a/bundle/direct/tests/testdata/fields/cluster_policies.yml +++ b/bundle/direct/tests/testdata/fields/cluster_policies.yml @@ -1,13 +1,17 @@ # Value library for cluster policies. See ../../README.md. # libraries is a union: an entry carries exactly one of jar, egg, whl, pypi, maven or cran. -# Seeding one entry makes the container and that entry's fields testable; the other members -# of the union are then reachable from absent, and the ones the API rejects alongside jar are -# recorded as such rather than left uncovered. +# One entry, so the container and that member are testable. The other members stay uncovered +# for this config, which the report says: they can only be reached by putting a second member +# on this entry, which is not something a user can write -- the backend keeps whichever it +# prefers and the whole block then reads as drifting. base: libraries: - jar: dbfs:/FileStore/test.jar - - maven: - coordinates: com.example:lib:1.0 - exclusions: - - com.other:unwanted + +fields: + # A policy definition is a JSON document the backend parses, and it is required: the API + # rejects an update that does not carry one. + definition: + - '{"spark_version":{"type":"fixed","value":"13.3.x-scala2.12"}}' + - '{"spark_version":{"type":"fixed","value":"14.3.x-scala2.12"}}' diff --git a/bundle/direct/tests/testdata/fields/instance_pools.yml b/bundle/direct/tests/testdata/fields/instance_pools.yml index 87259f31ca5..abc75f36269 100644 --- a/bundle/direct/tests/testdata/fields/instance_pools.yml +++ b/bundle/direct/tests/testdata/fields/instance_pools.yml @@ -3,12 +3,16 @@ base: custom_tags: team: eng preloaded_spark_versions: - - 13.3.x-scala2.12 - preloaded_docker_images: - - url: databricksruntime/standard:latest - basic_auth: - username: user - password: pass - node_type_flexibility: - alternate_node_type_ids: - - i3.2xlarge + - $DEFAULT_SPARK_VERSION + +skip: + # A preloaded image needs custom containers enabled for the workspace, which a test + # workspace does not have ("Custom containers is turned off for your deployment"). + preloaded_docker_images: needs custom containers enabled for the workspace + # Naming a second node type, which differs per cloud and the suite has no variable for. + node_type_flexibility: needs a second node type, which differs per cloud + # Only one of aws_/azure_/gcp_attributes applies, decided by the workspace's cloud, and the + # backend fills in defaults for the one that does -- so a value set here cannot be cleared. + aws_attributes: cloud-specific; only one of the three attribute blocks applies + azure_attributes: cloud-specific; only one of the three attribute blocks applies + gcp_attributes: cloud-specific; only one of the three attribute blocks applies diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 57ca0829490..ea75fe1d5f8 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -451,7 +451,16 @@ func isRequired(resourceType, path string) bool { // config is reported as not covered rather than silently tested against nothing. func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValues, resource any, runSeed uint64, unsettable []dresources.FieldRule, unique string) (fields []field, uncovered []string, inertFields map[string]string) { inertFields = map[string]string{} + // One row per field path. A path can be reached twice -- a cluster policy's definition is + // declared both on the bundle struct and on the embedded SDK struct under the same json + // name -- and structaccess resolves it to one field, so testing it twice would just print + // every result twice. + added := map[string]bool{} add := func(path string, kind reflect.Kind, values []any) { + if added[path] { + return + } + added[path] = true if _, skipped := fv.skipReason(path); skipped { return } diff --git a/libs/testserver/cluster_policies.go b/libs/testserver/cluster_policies.go index 13c48f0770e..e6194268210 100644 --- a/libs/testserver/cluster_policies.go +++ b/libs/testserver/cluster_policies.go @@ -102,11 +102,42 @@ func (s *FakeWorkspace) ClusterPoliciesEdit(req Request) any { if policy.Definition == "" && policy.PolicyFamilyId != "" { policy.Definition = policyFamilyDefinition(policy.PolicyFamilyId) } + + // A policy has to say what it constrains, so the API refuses an edit that leaves it with + // no definition and no family to take one from. + if policy.Definition == "" { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "'definition' must be supplied.", + }, + } + } + + // A library entry names exactly one artifact, so an entry with none is rejected rather + // than stored as an empty object. + if slices.ContainsFunc(policy.Libraries, isEmptyLibrary) { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "Library must specify exactly one of jar, egg, whl, pypi, maven or cran.", + }, + } + } + s.ClusterPolicies[request.PolicyId] = policy return Response{} } +// isEmptyLibrary reports whether a library entry names no artifact at all. +func isEmptyLibrary(library compute.Library) bool { + return library.Jar == "" && library.Egg == "" && library.Whl == "" && + library.Requirements == "" && library.Pypi == nil && library.Maven == nil && library.Cran == nil +} + func (s *FakeWorkspace) ClusterPoliciesDelete(req Request) any { var request compute.DeletePolicy if err := json.Unmarshal(req.Body, &request); err != nil { diff --git a/libs/testserver/merge.go b/libs/testserver/merge.go new file mode 100644 index 00000000000..4c48aa33974 --- /dev/null +++ b/libs/testserver/merge.go @@ -0,0 +1,54 @@ +package testserver + +import "encoding/json" + +// mergeInto applies the fields a request body carries over an existing resource, leaving the +// rest as they were. +// +// It models what an update does to a field the body omits: nothing. A client sends its whole +// desired state, but the SDK request types are omitempty, so a field the config cleared is +// absent from the body and the backend keeps the value it had -- which is why clearing such a +// field never converges. A handler that unmarshals the body into a fresh struct instead +// applies the zero value, so the fake accepts a clear the real API ignores. +// +// Arrays are replaced whole, as the backend does: an element is not separately addressable, +// so a list is only ever sent entire. +func mergeInto[T any](existing T, body []byte) (T, error) { + var out T + stored, err := json.Marshal(existing) + if err != nil { + return out, err + } + + var base, patch any + if err := json.Unmarshal(stored, &base); err != nil { + return out, err + } + if err := json.Unmarshal(body, &patch); err != nil { + return out, err + } + + merged, err := json.Marshal(mergeValue(base, patch)) + if err != nil { + return out, err + } + err = json.Unmarshal(merged, &out) + return out, err +} + +// mergeValue applies patch over base, recursing into objects and replacing anything else. +func mergeValue(base, patch any) any { + baseMap, baseIsMap := base.(map[string]any) + patchMap, patchIsMap := patch.(map[string]any) + if !baseIsMap || !patchIsMap { + return patch + } + for key, value := range patchMap { + if existing, ok := baseMap[key]; ok { + baseMap[key] = mergeValue(existing, value) + continue + } + baseMap[key] = value + } + return baseMap +} diff --git a/libs/testserver/merge_test.go b/libs/testserver/merge_test.go new file mode 100644 index 00000000000..fa362af556f --- /dev/null +++ b/libs/testserver/merge_test.go @@ -0,0 +1,46 @@ +package testserver + +import ( + "testing" + + "github.com/databricks/databricks-sdk-go/service/sql" + "github.com/stretchr/testify/require" +) + +func TestMergeInto(t *testing.T) { + existing := sql.GetWarehouseResponse{ //exhaustruct:ignore + Name: "wh", + ClusterSize: "2X-Small", + AutoStopMins: 10, + Channel: &sql.Channel{Name: sql.ChannelNameChannelNameCurrent}, //exhaustruct:ignore + Tags: &sql.EndpointTags{CustomTags: []sql.EndpointTagPair{ //exhaustruct:ignore + {Key: "team", Value: "eng"}, + }}, + } + + // A field the body omits keeps its value; that is what makes clearing it never converge. + got, err := mergeInto(existing, []byte(`{"name":"wh2"}`)) + require.NoError(t, err) + require.Equal(t, "wh2", got.Name) + require.Equal(t, "2X-Small", got.ClusterSize) + require.Equal(t, 10, got.AutoStopMins) + + // An explicit empty value does clear it, which is what ForceSendFields produces. + got, err = mergeInto(existing, []byte(`{"cluster_size":""}`)) + require.NoError(t, err) + require.Empty(t, got.ClusterSize) + require.Equal(t, "wh", got.Name) + + // A nested object merges key by key rather than being replaced. + got, err = mergeInto(existing, []byte(`{"channel":{"dbsql_version":"2024.15"}}`)) + require.NoError(t, err) + require.Equal(t, sql.ChannelNameChannelNameCurrent, got.Channel.Name) + require.Equal(t, "2024.15", got.Channel.DbsqlVersion) + + // A list is replaced whole: an element is not separately addressable. + got, err = mergeInto(existing, []byte(`{"tags":{"custom_tags":[{"key":"owner","value":"me"}]}}`)) + require.NoError(t, err) + require.Len(t, got.Tags.CustomTags, 1) + require.Equal(t, "owner", got.Tags.CustomTags[0].Key) + require.Equal(t, "me", got.Tags.CustomTags[0].Value) +} diff --git a/libs/testserver/sql_warehouses.go b/libs/testserver/sql_warehouses.go index 7f36ace72fc..e13d3baffe7 100644 --- a/libs/testserver/sql_warehouses.go +++ b/libs/testserver/sql_warehouses.go @@ -36,12 +36,25 @@ func (s *FakeWorkspace) SqlWarehousesUpsert(req Request, warehouseId string) Res defer s.LockUnlock()() if warehouseId != "" { - _, ok := s.SqlWarehouses[warehouseId] + existing, ok := s.SqlWarehouses[warehouseId] if !ok { return Response{ StatusCode: 404, } } + // An edit applies only the fields the request body carries. The client sends its whole + // desired state, but every field is omitempty, so a field the config cleared is absent + // from the body and the backend keeps the value it had -- which is why clearing one + // never converges. Unmarshalling into a fresh struct instead would apply the zero + // value, and the suite would report the field as freely clearable. + merged, err := mergeInto(existing, req.Body) + if err != nil { + return Response{ + Body: fmt.Sprintf("internal error: %s", err), + StatusCode: http.StatusInternalServerError, + } + } + warehouse = merged } else { warehouseId = nextUUID() } From 6d79bd93cd635f7e61f8c09a68c086722a4150d1 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 14:34:48 +0200 Subject: [PATCH 041/118] structaccess: search embedded structs breadth-first across the whole tree Follow-up to the earlier fix, which only separated the first level of embedding from the rest: a field three levels down in the first anonymous member still won over the same name two levels down in a later member, while encoding/json picks the shallower one. ValidatePattern had the same depth-first walk, so a path could validate against one field and then be read and written on another. Both now walk level by level and share the direct-field scan. Co-authored-by: Isaac --- libs/structs/structaccess/get.go | 28 ++++++------ libs/structs/structaccess/set_test.go | 27 +++++++++++ libs/structs/structaccess/typecheck.go | 63 +++++++++++++++++--------- 3 files changed, 83 insertions(+), 35 deletions(-) diff --git a/libs/structs/structaccess/get.go b/libs/structs/structaccess/get.go index 3d5037a3b4e..3d37f20c2b1 100644 --- a/libs/structs/structaccess/get.go +++ b/libs/structs/structaccess/get.go @@ -274,20 +274,22 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S return fv, sf, v, true } - // Second pass: search embedded anonymous structs (flattening semantics), one level of - // embedding at a time. Breadth-first, not depth-first: encoding/json resolves a name - // declared at two embedding depths in favour of the shallower one, so a search that - // descended fully into the first embed could pick a deeper field than the one that is - // actually serialized under that name. - for _, fv := range embeddedStructs(v) { - if out, sf, found := findFieldInStruct(fv, key); found { - return out, sf, fv, true - } - } - for _, fv := range embeddedStructs(v) { - if out, sf, owner, found := findStructFieldByKey(fv, key); found { - return out, sf, owner, true + // Second pass: search embedded anonymous structs (flattening semantics) breadth-first, one + // level of embedding at a time. Not depth-first: encoding/json resolves a name declared at + // two embedding depths in favour of the shallower one, so descending fully into the first + // embed could pick a field three levels down over the same name two levels down in a + // later one -- and then reading or writing the field would not be the field serialized + // under that name. + level := embeddedStructs(v) + for len(level) > 0 { + var next []reflect.Value + for _, fv := range level { + if out, sf, found := findFieldInStruct(fv, key); found { + return out, sf, fv, true + } + next = append(next, embeddedStructs(fv)...) } + level = next } return reflect.Value{}, reflect.StructField{}, reflect.Value{}, false diff --git a/libs/structs/structaccess/set_test.go b/libs/structs/structaccess/set_test.go index da8cf3ff19c..86b8646093d 100644 --- a/libs/structs/structaccess/set_test.go +++ b/libs/structs/structaccess/set_test.go @@ -2,6 +2,7 @@ package structaccess_test import ( "encoding/json" + "reflect" "testing" "github.com/databricks/cli/libs/structs/structaccess" @@ -808,11 +809,37 @@ type shallowEmbed struct { Value string `json:"value"` } +type deeperEmbed struct { + deepEmbed +} + type embedDepths struct { deepEmbed shallowEmbed } +// The same name three levels down in the first member, against two levels down in a later +// one. json picks the shallower, so the search has to be breadth-first across the whole tree +// rather than depth-first per member. +type embedDepthsAcrossMembers struct { + deeperEmbed + deepEmbed +} + +func TestSet_ShallowerEmbedWinsAcrossMembers(t *testing.T) { + target := &embedDepthsAcrossMembers{} + + require.NoError(t, structaccess.SetByString(target, "value", "set")) + assert.Equal(t, "set", target.Value) + assert.Empty(t, target.deeperEmbed.Value) + + require.NoError(t, structaccess.ValidateByString(reflect.TypeOf(target), "value")) + + blob, err := json.Marshal(target) + require.NoError(t, err) + assert.JSONEq(t, `{"value":"set"}`, string(blob)) +} + func TestSet_ShallowerEmbedWins(t *testing.T) { target := &embedDepths{} diff --git a/libs/structs/structaccess/typecheck.go b/libs/structs/structaccess/typecheck.go index 7147fa0f435..968e23bf94f 100644 --- a/libs/structs/structaccess/typecheck.go +++ b/libs/structs/structaccess/typecheck.go @@ -147,25 +147,54 @@ func FindStructFieldByKeyType(t reflect.Type, key string) (reflect.StructField, } // First pass: direct fields + if sf, ok := findDirectFieldByKeyType(t, key); ok { + return sf, t, true + } + + // Second pass: search embedded anonymous structs breadth-first, mirroring findStructFieldByKey + // (get.go) so a path validates against the same field Get and Set resolve it to, which is + // the one encoding/json serializes: the shallower of two same-named fields. + level := embeddedStructTypes(t) + for len(level) > 0 { + var next []reflect.Type + for _, ft := range level { + if sf, ok := findDirectFieldByKeyType(ft, key); ok { + return sf, ft, true + } + next = append(next, embeddedStructTypes(ft)...) + } + level = next + } + + return reflect.StructField{}, reflect.TypeOf(nil), false +} + +// findDirectFieldByKeyType matches key against the struct's own fields, by json tag name. +func findDirectFieldByKeyType(t reflect.Type, key string) (reflect.StructField, bool) { for sf := range t.Fields() { if sf.PkgPath != "" { // unexported continue } name := structtag.JSONTag(sf.Tag.Get("json")).Name() if name == "-" || sf.Name == EmbeddedSliceFieldName { - name = "" + continue } - if name != "" && name == key { - // Skip fields marked as internal/readonly - btag := structtag.BundleTag(sf.Tag.Get("bundle")) - if btag.Internal() || btag.ReadOnly() { - continue - } - return sf, t, true + if name != key { + continue } + // Skip fields marked as internal/readonly + btag := structtag.BundleTag(sf.Tag.Get("bundle")) + if btag.Internal() || btag.ReadOnly() { + continue + } + return sf, true } + return reflect.StructField{}, false +} - // Second pass: search embedded anonymous structs recursively (flattening semantics) +// embeddedStructTypes returns the anonymous struct fields of t, dereferenced. +func embeddedStructTypes(t reflect.Type) []reflect.Type { + var out []reflect.Type for sf := range t.Fields() { if !sf.Anonymous { continue @@ -174,19 +203,9 @@ func FindStructFieldByKeyType(t reflect.Type, key string) (reflect.StructField, for ft.Kind() == reflect.Pointer { ft = ft.Elem() } - if ft.Kind() != reflect.Struct { - continue - } - if osf, owner, ok := FindStructFieldByKeyType(ft, key); ok { - // Skip fields marked as internal/readonly - btag := structtag.BundleTag(osf.Tag.Get("bundle")) - if btag.Internal() || btag.ReadOnly() { - // Treat as not found and continue - continue - } - return osf, owner, true + if ft.Kind() == reflect.Struct { + out = append(out, ft) } } - - return reflect.StructField{}, reflect.TypeOf(nil), false + return out } From cefafa7be5b9e7bb636acc4103e6283a4dbf085f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 14:41:23 +0200 Subject: [PATCH 042/118] Field catalog: resolve the unique suffix at write time, match ignored writes by relation Two fixes from the sixth review round. An identity field's values were built with the harness's suffix baked in at enumeration time, but a rebuild gives the replacement resource a new suffix while the old one is still alive under the old name. A later transition then asked for the old name and got "already exists", which is a finding about this suite rather than the engine. Values now carry a placeholder that is resolved against the current harness when the value is written; the label shows the placeholder, so the report is unchanged and stable. wasIgnored compared the exact leaf path, so a write the planner recorded against an ancestor found nothing to compare and an ignored write was reported as drift. It goes through the same relatedChange helper as the rest, which converged now uses too, and the exact-lookup helper is gone. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 29 +++++++++---------- bundle/direct/tests/harness_test.go | 13 ++++++++- bundle/direct/tests/output/apps.txt | 12 ++++---- bundle/direct/tests/output/catalogs.txt | 7 +++-- bundle/direct/tests/output/dashboards.txt | 20 ++++++------- bundle/direct/tests/output/genie_spaces.txt | 20 ++++++------- .../tests/output/model_serving_endpoints.txt | 14 ++++----- .../direct/tests/output/registered_models.txt | 8 ++--- bundle/direct/tests/output/schemas.txt | 7 +++-- bundle/direct/tests/output/sql_warehouses.txt | 8 ++--- bundle/direct/tests/values_test.go | 10 ++++++- 11 files changed, 83 insertions(+), 65 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 1df9ac80843..a7bf76b7f04 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -308,9 +308,10 @@ func fieldWasDropped(plan *deployplan.Plan, node, path string) bool { return ok && change.Action == deployplan.Skip } -// converged reports whether a plan no longer wants to change the field. +// converged reports whether a plan no longer wants to change the field, at whatever +// granularity the planner recorded it. func converged(plan *deployplan.Plan, node, path string) bool { - change, ok := planChange(plan, node, path) + change, ok := relatedChange(plan, node, path) return !ok || change.Action == deployplan.Skip } @@ -672,29 +673,21 @@ func baselineDrift(h *bundleHarness) (map[string]bool, error) { // the field is still pending in the post-deploy plan, and its remote value is identical to // what the pre-deploy plan saw. func wasIgnored(before, after *deployplan.Plan, node, path string) bool { - beforeChange, ok := planChange(before, node, path) + // relatedChange, not an exact lookup: the planner records a change at the granularity it + // diffed at, so a write to "libraries[1].pypi.repo" can be reported against + // "libraries[1].pypi" -- and comparing the wrong path finds nothing, which turns an + // ignored write into drift. + beforeChange, ok := relatedChange(before, node, path) if !ok { return false } - afterChange, ok := planChange(after, node, path) + afterChange, ok := relatedChange(after, node, path) if !ok || afterChange.Action == deployplan.Skip { return false } return jsonEqual(beforeChange.Remote, afterChange.Remote) } -func planChange(plan *deployplan.Plan, node, path string) (*deployplan.ChangeDesc, bool) { - if plan == nil { - return nil, false - } - entry, ok := plan.Plan[node] - if !ok { - return nil, false - } - change, ok := entry.Changes[path] - return change, ok -} - // jsonEqual compares two plan values, which are decoded as any and so cannot be compared // directly. func jsonEqual(a, b any) bool { @@ -746,6 +739,10 @@ var generatedIDs = []struct { }{ // The suffix this suite gives every resource it creates. {regexp.MustCompile(`f[0-9a-f]{20}`), "[UNIQUE_NAME]"}, + // The placeholder an identity field's values carry. Only ever seen in a label, where it + // reads better without brackets -- an error message carries the substituted suffix, which + // the rule above catches. + {regexp.MustCompile(regexp.QuoteMeta(uniqueMarker)), "UNIQUE"}, {regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`), "[UUID]"}, // A backend-assigned id, which is a bare number or hex string only ever seen after "id=". {regexp.MustCompile(`id=[0-9A-Fa-f]{6,}`), "id=[ID]"}, diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index b9005a14713..b5df8fa8a24 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -458,10 +458,21 @@ func (h *bundleHarness) setField(path string, value any) error { return err } return h.edit(func(resource any) error { - return setNode(resource, node, value) + return setNode(resource, node, substituteUnique(value, h.unique)) }) } +// substituteUnique resolves the placeholder in an identity field's value against the suffix +// this harness's resource actually carries. A rebuild changes that suffix, and the resource it +// replaced is still alive under the old one until the test ends. +func substituteUnique(value any, unique string) any { + text, ok := value.(string) + if !ok || !strings.Contains(text, uniqueMarker) { + return value + } + return strings.ReplaceAll(text, uniqueMarker, unique) +} + // setNode is setField without the bundle: the edit itself, for the unit tests. func setNode(resource any, node *structpath.PathNode, value any) error { if value == nil { diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 1208dcc947f..c740c28547f 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -112,12 +112,12 @@ resources[0].postgres.branch y absent UPDATE_IGNORE resources[0].postgres.database x absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.database y absent UPDATE_IGNORED resources[0].postgres.database resources[0].postgres.permission CAN_CONNECT_AND_CREATE absent UPDATE_IGNORED resources[0].postgres.permission -source_code_path absent x-_UNIQUE_NAME~89d START_NOT_REACHED source_code_path -source_code_path absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED source_code_path -source_code_path x-_UNIQUE_NAME~89d absent START_NOT_REACHED source_code_path -source_code_path x-_UNIQUE_NAME~89d y-_UNIQUE_NAME~57d2 START_NOT_REACHED source_code_path -source_code_path y-_UNIQUE_NAME~57d2 absent START_NOT_REACHED source_code_path -source_code_path y-_UNIQUE_NAME~57d2 x-_UNIQUE_NAME~89d START_NOT_REACHED source_code_path +source_code_path absent x-UNIQUE START_NOT_REACHED source_code_path +source_code_path absent y-UNIQUE START_NOT_REACHED source_code_path +source_code_path x-UNIQUE absent START_NOT_REACHED source_code_path +source_code_path x-UNIQUE y-UNIQUE START_NOT_REACHED source_code_path +source_code_path y-UNIQUE absent START_NOT_REACHED source_code_path +source_code_path y-UNIQUE x-UNIQUE START_NOT_REACHED source_code_path space x y START_NOT_REACHED space space y absent START_NOT_REACHED space space y x START_NOT_REACHED space diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index ed42204f1e5..1faf5961797 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -9,11 +9,12 @@ properties keys1 absent UPDATE_IGNORE properties keys1 keys0 UPDATE_IGNORED properties properties['team'] absent x START_NOT_REACHED properties['team'] properties['team'] absent y START_NOT_REACHED properties['team'] -properties['team'] x absent UPDATE_IGNORED properties['team'] -properties['team'] y absent UPDATE_IGNORED properties['team'] +properties['team'] x absent POST_DEPLOY_DRIFT properties,properties['team'] +properties['team'] y absent POST_DEPLOY_DRIFT properties,properties['team'] === summary OK 14 -UPDATE_IGNORED 6 +UPDATE_IGNORED 4 +POST_DEPLOY_DRIFT 2 SKIPPED 8 START_NOT_REACHED 6 diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index cd2e4813c3d..ed4bd81810d 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -3,22 +3,22 @@ dataset_catalog y absent START_NOT_REA dataset_schema x y START_NOT_REACHED dataset_schema dataset_schema y absent START_NOT_REACHED dataset_schema dataset_schema y x START_NOT_REACHED dataset_schema -display_name absent x-_UNIQUE_NAME~89d START_NOT_REACHED display_name -display_name absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED display_name -display_name x-_UNIQUE_NAME~89d absent UPDATE_IGNORED display_name -display_name y-_UNIQUE_NAME~57d2 absent UPDATE_IGNORED display_name +display_name absent x-UNIQUE START_NOT_REACHED display_name +display_name absent y-UNIQUE START_NOT_REACHED display_name +display_name x-UNIQUE absent UPDATE_IGNORED display_name +display_name y-UNIQUE absent UPDATE_IGNORED display_name etag absent x START_NOT_REACHED etag etag absent y START_NOT_REACHED etag etag x absent START_NOT_REACHED etag etag x y START_NOT_REACHED etag etag y absent START_NOT_REACHED etag etag y x START_NOT_REACHED etag -parent_path absent x-_UNIQUE_NAME~89d START_NOT_REACHED parent_path -parent_path absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED parent_path -parent_path x-_UNIQUE_NAME~89d absent START_NOT_REACHED parent_path -parent_path x-_UNIQUE_NAME~89d y-_UNIQUE_NAME~57d2 START_NOT_REACHED parent_path -parent_path y-_UNIQUE_NAME~57d2 absent START_NOT_REACHED parent_path -parent_path y-_UNIQUE_NAME~57d2 x-_UNIQUE_NAME~89d START_NOT_REACHED parent_path +parent_path absent x-UNIQUE START_NOT_REACHED parent_path +parent_path absent y-UNIQUE START_NOT_REACHED parent_path +parent_path x-UNIQUE absent START_NOT_REACHED parent_path +parent_path x-UNIQUE y-UNIQUE START_NOT_REACHED parent_path +parent_path y-UNIQUE absent START_NOT_REACHED parent_path +parent_path y-UNIQUE x-UNIQUE START_NOT_REACHED parent_path === summary OK 20 diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index 1f588c82f4b..ce14d349f25 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,16 +1,16 @@ === genie_space.yml.tmpl description x absent UPDATE_IGNORED description description y absent UPDATE_IGNORED description -parent_path absent x-_UNIQUE_NAME~89d START_NOT_REACHED parent_path -parent_path absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED parent_path -parent_path x-_UNIQUE_NAME~89d absent START_NOT_REACHED parent_path -parent_path x-_UNIQUE_NAME~89d y-_UNIQUE_NAME~57d2 START_NOT_REACHED parent_path -parent_path y-_UNIQUE_NAME~57d2 absent START_NOT_REACHED parent_path -parent_path y-_UNIQUE_NAME~57d2 x-_UNIQUE_NAME~89d START_NOT_REACHED parent_path -title absent x-_UNIQUE_NAME~89d START_NOT_REACHED title -title absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED title -title x-_UNIQUE_NAME~89d absent UPDATE_IGNORED title -title y-_UNIQUE_NAME~57d2 absent UPDATE_IGNORED title +parent_path absent x-UNIQUE START_NOT_REACHED parent_path +parent_path absent y-UNIQUE START_NOT_REACHED parent_path +parent_path x-UNIQUE absent START_NOT_REACHED parent_path +parent_path x-UNIQUE y-UNIQUE START_NOT_REACHED parent_path +parent_path y-UNIQUE absent START_NOT_REACHED parent_path +parent_path y-UNIQUE x-UNIQUE START_NOT_REACHED parent_path +title absent x-UNIQUE START_NOT_REACHED title +title absent y-UNIQUE START_NOT_REACHED title +title x-UNIQUE absent UPDATE_IGNORED title +title y-UNIQUE absent UPDATE_IGNORED title warehouse_id absent x START_NOT_REACHED warehouse_id warehouse_id absent y START_NOT_REACHED warehouse_id warehouse_id x absent UPDATE_IGNORED warehouse_id diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index 0dbcb9ecfc6..d18bb359da6 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -3,20 +3,20 @@ budget_policy_id x absent START_NOT_REA budget_policy_id x y START_NOT_REACHED budget_policy_id (declared inert: no_update_api) budget_policy_id y absent START_NOT_REACHED budget_policy_id (declared inert: no_update_api) budget_policy_id y x START_NOT_REACHED budget_policy_id (declared inert: no_update_api) -config.auto_capture_config.catalog_name absent x POST_DEPLOY_DRIFT config -config.auto_capture_config.catalog_name absent y POST_DEPLOY_DRIFT config +config.auto_capture_config.catalog_name absent x UPDATE_IGNORED config.auto_capture_config.catalog_name +config.auto_capture_config.catalog_name absent y UPDATE_IGNORED config.auto_capture_config.catalog_name config.auto_capture_config.catalog_name x y START_NOT_REACHED config.auto_capture_config.catalog_name config.auto_capture_config.catalog_name y absent START_NOT_REACHED config.auto_capture_config.catalog_name -config.auto_capture_config.enabled absent false POST_DEPLOY_DRIFT config -config.auto_capture_config.enabled absent true POST_DEPLOY_DRIFT config +config.auto_capture_config.enabled absent false UPDATE_IGNORED config.auto_capture_config.enabled +config.auto_capture_config.enabled absent true UPDATE_IGNORED config.auto_capture_config.enabled config.auto_capture_config.enabled false absent START_NOT_REACHED config.auto_capture_config.enabled config.auto_capture_config.enabled false true START_NOT_REACHED config.auto_capture_config.enabled config.auto_capture_config.enabled true absent START_NOT_REACHED config.auto_capture_config.enabled config.auto_capture_config.enabled true false START_NOT_REACHED config.auto_capture_config.enabled -config.auto_capture_config.schema_name absent x POST_DEPLOY_DRIFT config +config.auto_capture_config.schema_name absent x UPDATE_IGNORED config.auto_capture_config.schema_name config.auto_capture_config.schema_name x absent START_NOT_REACHED config.auto_capture_config.schema_name config.auto_capture_config.schema_name x y START_NOT_REACHED config.auto_capture_config.schema_name -config.auto_capture_config.table_name_prefix absent x POST_DEPLOY_DRIFT config +config.auto_capture_config.table_name_prefix absent x UPDATE_IGNORED config.auto_capture_config.table_name_prefix config.auto_capture_config.table_name_prefix x y START_NOT_REACHED config.auto_capture_config.table_name_prefix config.auto_capture_config.table_name_prefix y absent START_NOT_REACHED config.auto_capture_config.table_name_prefix rate_limits absent len0 START_NOT_REACHED rate_limits (declared inert: not_implemented) @@ -40,6 +40,6 @@ OK_RECREATE 20 OK_INERT 2 SUPPRESSED 14 NOT_OBSERVABLE 10 -POST_DEPLOY_DRIFT 6 +UPDATE_IGNORED 6 SKIPPED 2 START_NOT_REACHED 28 diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 4dcffa601ed..299c1584522 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -37,10 +37,10 @@ full_name x absent START_NOT_REA full_name x y START_NOT_REACHED full_name full_name y absent START_NOT_REACHED full_name full_name y x START_NOT_REACHED full_name -name absent x-_UNIQUE_NAME~89d BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name absent y-_UNIQUE_NAME~57d2 BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name x-_UNIQUE_NAME~89d absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name y-_UNIQUE_NAME~57d2 absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name absent x-UNIQUE BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name absent y-UNIQUE BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name x-UNIQUE absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) +name y-UNIQUE absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) owner absent x UPDATE_IGNORED owner owner absent y UPDATE_IGNORED owner owner x absent START_NOT_REACHED owner diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index 49456642c34..d75538d10de 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -9,12 +9,13 @@ properties keys1 absent UPDATE_IGNORE properties keys1 keys0 UPDATE_IGNORED properties properties['team'] absent x START_NOT_REACHED properties['team'] properties['team'] absent y START_NOT_REACHED properties['team'] -properties['team'] x absent UPDATE_IGNORED properties['team'] -properties['team'] y absent UPDATE_IGNORED properties['team'] +properties['team'] x absent POST_DEPLOY_DRIFT properties,properties['team'] +properties['team'] y absent POST_DEPLOY_DRIFT properties,properties['team'] === summary OK 12 OK_RECREATE 2 -UPDATE_IGNORED 6 +UPDATE_IGNORED 4 +POST_DEPLOY_DRIFT 2 SKIPPED 2 START_NOT_REACHED 6 diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index 0572b0ed6bb..0f459c36e99 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -13,10 +13,10 @@ max_num_clusters 2 absent UPDATE_IGNORE max_num_clusters 3 absent UPDATE_IGNORED max_num_clusters max_num_clusters absent 2 START_NOT_REACHED max_num_clusters max_num_clusters absent 3 START_NOT_REACHED max_num_clusters -name absent x-_UNIQUE_NAME~89d START_NOT_REACHED name -name absent y-_UNIQUE_NAME~57d2 START_NOT_REACHED name -name x-_UNIQUE_NAME~89d absent UPDATE_IGNORED name -name y-_UNIQUE_NAME~57d2 absent UPDATE_IGNORED name +name absent x-UNIQUE START_NOT_REACHED name +name absent y-UNIQUE START_NOT_REACHED name +name x-UNIQUE absent UPDATE_IGNORED name +name y-UNIQUE absent UPDATE_IGNORED name spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy spot_instance_policy POLICY_UNSPECIFIED absent UPDATE_IGNORED spot_instance_policy spot_instance_policy absent COST_OPTIMIZED START_NOT_REACHED spot_instance_policy diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index ea75fe1d5f8..2e665a63cbd 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -592,11 +592,19 @@ func uniqueIdentityValues(values []any, path, unique string, resource any) []any // Only a name can carry a suffix; an id field of another type keeps its value. return values } - out = append(out, text+"-"+unique) + // A placeholder, not the suffix itself: a rebuild gives the resource a new suffix while + // the old one is still alive under its old name, so a value fixed at enumeration time + // would collide with it. Substituted on the way into the config. + out = append(out, text+"-"+uniqueMarker) } return out } +// uniqueMarker stands in for the run's unique suffix inside a value. It is substituted for the +// harness's own suffix when the value is written, and redacted when the value is labelled, so +// the report is stable across runs. +const uniqueMarker = "@UNIQUE@" + func namesThisResource(path, unique string, resource any) bool { current, err := structaccess.GetByString(resource, path) if err != nil { From ebbaa16061cb27d91a62a221d812bff859c3fd70 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 15:15:36 +0200 Subject: [PATCH 043/118] Field catalog: align cluster policies with AWS Every divergence was a rule the fake server did not enforce, or a value the API would not take. Each one comes from a cloud error message: - A policy is named, and it takes its rules from either its own definition or a policy family, never both. The fake accepted an edit with no name, and one carrying both. - A library entry names an artifact; an entry left with none is "Unknown library". - A jar is a path, not free text, so the generic x and y were rejected. - The other members of the libraries union are skipped: reaching one means putting a second artifact on the entry that already holds the jar, which is not a valid write. The backend keeps one of them and the whole block then reads as drifting. Covering them needs a config whose entry holds that member instead, which the report now says. Also fixed a redaction that never fired: the pattern for a backend id was written with a literal backspace instead of a word boundary, so a real workspace's hex ids stayed in the report while the fake server's UUIDs were redacted as UUIDs. Both shapes now redact to the same placeholder, which they have to for the two reports to be comparable at all, and TestRedactIDs pins it. cluster_policies matches AWS row for row. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 6 ++-- .../direct/tests/output/cluster_policies.txt | 32 +++++++++++-------- .../testdata/fields/cluster_policies.yml | 22 +++++++++++++ bundle/direct/tests/values_unit_test.go | 14 ++++++++ libs/testserver/cluster_policies.go | 27 ++++++++++++++-- 5 files changed, 82 insertions(+), 19 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index a7bf76b7f04..645c5b4b32e 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -743,9 +743,11 @@ var generatedIDs = []struct { // reads better without brackets -- an error message carries the substituted suffix, which // the rule above catches. {regexp.MustCompile(regexp.QuoteMeta(uniqueMarker)), "UNIQUE"}, + // A backend-assigned id. Before the UUID rule, and covering both shapes: the fake server + // hands out UUIDs where a real workspace hands out hex, and the two have to redact to the + // same placeholder or every error message naming one would differ between them. + {regexp.MustCompile("id=[0-9A-Fa-f-]{6,}"), "id=[ID]"}, {regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`), "[UUID]"}, - // A backend-assigned id, which is a bare number or hex string only ever seen after "id=". - {regexp.MustCompile(`id=[0-9A-Fa-f]{6,}`), "id=[ID]"}, } // redactIDs replaces every run-specific id in a message with a stable placeholder. diff --git a/bundle/direct/tests/output/cluster_policies.txt b/bundle/direct/tests/output/cluster_policies.txt index d7ec765ccaa..1d6567eec79 100644 --- a/bundle/direct/tests/output/cluster_policies.txt +++ b/bundle/direct/tests/output/cluster_policies.txt @@ -1,17 +1,21 @@ === cluster_policy.yml.tmpl -definition absent spark_version_:_ty~69b3 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -definition absent spark_version_:_ty~cbc0 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -definition spark_version_:_ty~69b3 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -definition spark_version_:_ty~cbc0 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -libraries[0].jar absent x BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: Library must specify exactly one of jar, egg, whl, pypi, maven or cran. (4... -libraries[0].jar absent y BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: Library must specify exactly one of jar, egg, whl, pypi, maven or cran. (4... -libraries[0].jar x absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: Library must specify exactly one of jar, egg, whl, pypi, maven or cran. (4... -libraries[0].jar y absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[UUID]: Library must specify exactly one of jar, egg, whl, pypi, maven or cran. (4... +definition absent spark_version_:_ty~69b3 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition absent spark_version_:_ty~cbc0 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition spark_version_:_ty~69b3 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition spark_version_:_ty~cbc0 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +libraries[0].jar absent dbfs:/FileStore/a.jar BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) +libraries[0].jar absent dbfs:/FileStore/b.jar BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) +libraries[0].jar dbfs:/FileStore/a.jar absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) +libraries[0].jar dbfs:/FileStore/b.jar absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) +name absent x-UNIQUE BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) +name absent y-UNIQUE BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) +name x-UNIQUE absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) +name y-UNIQUE absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) === summary -OK 90 -SUPPRESSED 2 -NOT_OBSERVABLE 2 -BACKEND_ERROR 4 -SKIPPED 1 -BASE_ERROR 4 +OK 22 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +BACKEND_ERROR 6 +SKIPPED 10 +BASE_ERROR 6 diff --git a/bundle/direct/tests/testdata/fields/cluster_policies.yml b/bundle/direct/tests/testdata/fields/cluster_policies.yml index a7ae6ab2540..6ae4355692b 100644 --- a/bundle/direct/tests/testdata/fields/cluster_policies.yml +++ b/bundle/direct/tests/testdata/fields/cluster_policies.yml @@ -9,7 +9,29 @@ base: libraries: - jar: dbfs:/FileStore/test.jar +skip: + # The other members of the union. Reaching one means putting a second artifact on the entry + # that already holds the jar, which is not a valid write: the backend keeps one of them and + # the whole block then reads as drifting. Covering them needs a config whose libraries entry + # holds that member instead. + libraries[*].egg: another member of the same union as the seeded jar + libraries[*].whl: another member of the same union as the seeded jar + libraries[*].requirements: another member of the same union as the seeded jar + libraries[*].pypi: another member of the same union as the seeded jar + libraries[*].maven: another member of the same union as the seeded jar + libraries[*].cran: another member of the same union as the seeded jar + + # A policy takes its rules from either its own definition or a policy family, never both, + # and this config sets a definition. A family also has to be one the workspace offers. + policy_family_id: mutually exclusive with the definition this config sets + policy_family_definition_overrides: only meaningful with a policy family + policy_family_version: only meaningful with a policy family + fields: + # A library path, not free text. Clearing it is still tried, and leaves the entry with no + # artifact at all, which the API rejects. + libraries[*].jar: [dbfs:/FileStore/a.jar, dbfs:/FileStore/b.jar] + # A policy definition is a JSON document the backend parses, and it is required: the API # rejects an update that does not carry one. definition: diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go index 903079b0f1d..79fdcd5c5ee 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/tests/values_unit_test.go @@ -185,3 +185,17 @@ func TestIsRequiredInsideSlice(t *testing.T) { assert.True(t, isRequired("alerts", "display_name")) assert.False(t, isRequired("alerts", "custom_summary")) } + +// Every id in a message has to redact to the same placeholder on both sides, or a report from +// a real workspace could never match one from the fake server: the fake hands out UUIDs where +// a workspace hands out hex. +func TestRedactIDs(t *testing.T) { + for _, tc := range []struct{ in, want string }{ + {"updating id=79f5e1f2-de43-03a0-79f5-e1f2de4303a1: nope", "updating id=[ID]: nope"}, + {"updating id=00145E75FBD6DCBB: nope", "updating id=[ID]: nope"}, + {"Catalog 'test-schema-f0ad2bd52de6e45119d42' missing", "Catalog 'test-schema-[UNIQUE_NAME]' missing"}, + {"principal 79f5e1f2-de43-03a0-79f5-e1f2de4303a1 denied", "principal [UUID] denied"}, + } { + assert.Equal(t, tc.want, redactIDs(tc.in)) + } +} diff --git a/libs/testserver/cluster_policies.go b/libs/testserver/cluster_policies.go index e6194268210..325fbd84b0a 100644 --- a/libs/testserver/cluster_policies.go +++ b/libs/testserver/cluster_policies.go @@ -103,6 +103,27 @@ func (s *FakeWorkspace) ClusterPoliciesEdit(req Request) any { policy.Definition = policyFamilyDefinition(policy.PolicyFamilyId) } + // A policy is named, and it takes its rules from either its own definition or a policy + // family -- never both. + if policy.Name == "" { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "'name' must be supplied.", + }, + } + } + if request.Definition != "" && request.PolicyFamilyId != "" { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "policy_family_id and definition cannot both be set.", + }, + } + } + // A policy has to say what it constrains, so the API refuses an edit that leaves it with // no definition and no family to take one from. if policy.Definition == "" { @@ -115,14 +136,14 @@ func (s *FakeWorkspace) ClusterPoliciesEdit(req Request) any { } } - // A library entry names exactly one artifact, so an entry with none is rejected rather - // than stored as an empty object. + // A library entry names an artifact, so an entry with none is rejected rather than stored + // as an empty object. "Unknown library" is the API's own wording for it. if slices.ContainsFunc(policy.Libraries, isEmptyLibrary) { return Response{ StatusCode: 400, Body: map[string]string{ "error_code": "INVALID_PARAMETER_VALUE", - "message": "Library must specify exactly one of jar, egg, whl, pypi, maven or cran.", + "message": "Unknown library", }, } } From 3d4e79ae7f1b94753a87e5c4bb9466e205da10f3 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 15:18:16 +0200 Subject: [PATCH 044/118] structaccess: treat a same-depth embed conflict as not found, assign before ForceSendFields Two more from the review. When two embedded structs declare one json name at the same depth, encoding/json calls that ambiguous and omits the field entirely. Get, Set and ValidatePattern picked the first match, so a caller could read and write a field that is never serialized. All three now report it as not found, which is what json does with it. Set also updated ForceSendFields before assigning, so a value that could not be converted left the field's send-behaviour changed even though the Set failed. The assignment goes first. Co-authored-by: Isaac --- libs/structs/structaccess/get.go | 23 ++++++++++++++-- libs/structs/structaccess/set.go | 10 ++++--- libs/structs/structaccess/set_test.go | 36 ++++++++++++++++++++++++++ libs/structs/structaccess/typecheck.go | 18 ++++++++++++- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/libs/structs/structaccess/get.go b/libs/structs/structaccess/get.go index 3d37f20c2b1..1fdb49d6533 100644 --- a/libs/structs/structaccess/get.go +++ b/libs/structs/structaccess/get.go @@ -283,12 +283,31 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S level := embeddedStructs(v) for len(level) > 0 { var next []reflect.Value + var found []struct { + value reflect.Value + field reflect.StructField + owner reflect.Value + } for _, fv := range level { - if out, sf, found := findFieldInStruct(fv, key); found { - return out, sf, fv, true + if out, sf, ok := findFieldInStruct(fv, key); ok { + found = append(found, struct { + value reflect.Value + field reflect.StructField + owner reflect.Value + }{out, sf, fv}) + continue } next = append(next, embeddedStructs(fv)...) } + if len(found) == 1 { + return found[0].value, found[0].field, found[0].owner, true + } + if len(found) > 1 { + // Two embedded structs declare the same name at the same depth. encoding/json calls + // that ambiguous and omits the field entirely, so there is no field to read or + // write: picking one would target data that is never serialized. + return reflect.Value{}, reflect.StructField{}, reflect.Value{}, false + } level = next } diff --git a/libs/structs/structaccess/set.go b/libs/structs/structaccess/set.go index 1f72cf96cc9..f46536c2727 100644 --- a/libs/structs/structaccess/set.go +++ b/libs/structs/structaccess/set.go @@ -140,13 +140,15 @@ func setStructField(parentVal reflect.Value, fieldName string, valueVal reflect. return fmt.Errorf("field %q cannot be set", sf.Name) } - // Handle ForceSendFields: remove if setting nil, add if setting empty value - err := updateForceSendFields(owner, sf.Name, valueVal, sf) - if err != nil { + // Assign first: a value that cannot be converted must leave the struct exactly as it was, + // and updating ForceSendFields before the assignment left the field's send-behaviour + // changed after a failed Set. + if err := assignValue(fv, valueVal); err != nil { return err } - return assignValue(fv, valueVal) + // Handle ForceSendFields: remove if setting nil, add if setting empty value + return updateForceSendFields(owner, sf.Name, valueVal, sf) } // setMapValue sets a value in a map diff --git a/libs/structs/structaccess/set_test.go b/libs/structs/structaccess/set_test.go index 86b8646093d..1f450e863e1 100644 --- a/libs/structs/structaccess/set_test.go +++ b/libs/structs/structaccess/set_test.go @@ -856,3 +856,39 @@ func TestSet_ShallowerEmbedWins(t *testing.T) { require.NoError(t, err) assert.JSONEq(t, `{"value":"set"}`, string(blob)) } + +// Two embedded structs declaring one name at the same depth: encoding/json calls that +// ambiguous and omits the field, so there is nothing to read or write either. +type ambiguousA struct { + Value string `json:"value"` +} + +type ambiguousB struct { + Value string `json:"value"` +} + +type ambiguousEmbeds struct { + ambiguousA + ambiguousB +} + +func TestSet_AmbiguousEmbedIsNotFound(t *testing.T) { + target := &ambiguousEmbeds{} + + require.Error(t, structaccess.SetByString(target, "value", "set")) + require.Error(t, structaccess.ValidateByString(reflect.TypeOf(target), "value")) + + // Which is what json does with it: the name resolves to no field at all. + blob, err := json.Marshal(target) + require.NoError(t, err) + assert.JSONEq(t, `{}`, string(blob)) +} + +// A value that cannot be converted must leave the struct untouched, ForceSendFields included. +func TestSet_FailedConversionLeavesForceSendFields(t *testing.T) { + job := &jobs.JobSettings{Name: "n"} //exhaustruct:ignore + + require.Error(t, structaccess.SetByString(job, "max_concurrent_runs", "not-a-number")) + assert.Empty(t, job.ForceSendFields) + assert.Equal(t, "n", job.Name) +} diff --git a/libs/structs/structaccess/typecheck.go b/libs/structs/structaccess/typecheck.go index 968e23bf94f..160925da721 100644 --- a/libs/structs/structaccess/typecheck.go +++ b/libs/structs/structaccess/typecheck.go @@ -157,12 +157,28 @@ func FindStructFieldByKeyType(t reflect.Type, key string) (reflect.StructField, level := embeddedStructTypes(t) for len(level) > 0 { var next []reflect.Type + var found []struct { + field reflect.StructField + owner reflect.Type + } for _, ft := range level { if sf, ok := findDirectFieldByKeyType(ft, key); ok { - return sf, ft, true + found = append(found, struct { + field reflect.StructField + owner reflect.Type + }{sf, ft}) + continue } next = append(next, embeddedStructTypes(ft)...) } + if len(found) == 1 { + return found[0].field, found[0].owner, true + } + if len(found) > 1 { + // Ambiguous at this depth, which encoding/json resolves by omitting the field; see + // findStructFieldByKey in get.go. + return reflect.StructField{}, reflect.TypeOf(nil), false + } level = next } From 430ba964a1cd093975a5bd7ddd756a66387d6c06 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 15:19:45 +0200 Subject: [PATCH 045/118] structaccess: silence vet on the deliberately ambiguous test fixture The repeated json tag is what the fixture exists to exercise. Co-authored-by: Isaac --- libs/structs/structaccess/set_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/structs/structaccess/set_test.go b/libs/structs/structaccess/set_test.go index 1f450e863e1..138fc605d57 100644 --- a/libs/structs/structaccess/set_test.go +++ b/libs/structs/structaccess/set_test.go @@ -869,7 +869,7 @@ type ambiguousB struct { type ambiguousEmbeds struct { ambiguousA - ambiguousB + ambiguousB //nolint:govet // the repeated json tag is the point: both embeds declare "value" } func TestSet_AmbiguousEmbedIsNotFound(t *testing.T) { From c5fc39d64ca5d2e96a6f0db07d2c3175e8898198 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 15:27:08 +0200 Subject: [PATCH 046/118] Field catalog: three more attribution fixes from the review - wasIgnored matched the change by relation in both plans independently, so the two reads could be compared at different granularities: a leaf before the apply against its parent object after it. Comparing a value with the object containing it never matches, which turned an ignored write into drift. It now looks the second plan up at the key the first one used. UPDATE_IGNORED goes from 62 rows to 76. - Baseline drift was matched by exact path while everything else is matched by relation, so drift already recorded against an ancestor counted as this field failing to reach its starting value, and the ancestor's drift was blamed on every field beneath it. - A list whose element type offers a single value (an enum with one member) got no values at all. The empty list plus that one still covers adding, clearing and removing. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 63 +++++++++++++++++++------ bundle/direct/tests/output/catalogs.txt | 7 ++- bundle/direct/tests/output/schemas.txt | 7 ++- bundle/direct/tests/values_test.go | 7 ++- 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 645c5b4b32e..ccf17a5721e 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -273,14 +273,22 @@ func sameField(drifting, path string) bool { // change to "tasks[0].description" can be reported against "tasks". A pending change is // preferred over a skipped one, since that is the one that explains what will happen. func relatedChange(plan *deployplan.Plan, node, path string) (*deployplan.ChangeDesc, bool) { + _, change, ok := relatedChangeKey(plan, node, path) + return change, ok +} + +// relatedChangeKey is relatedChange, also returning the key the change was recorded under so +// a caller comparing two plans can look the same key up in both. +func relatedChangeKey(plan *deployplan.Plan, node, path string) (string, *deployplan.ChangeDesc, bool) { if plan == nil { - return nil, false + return "", nil, false } entry, ok := plan.Plan[node] if !ok { - return nil, false + return "", nil, false } + var foundKey string var found *deployplan.ChangeDesc for _, key := range slices.Sorted(maps.Keys(entry.Changes)) { change := entry.Changes[key] @@ -288,13 +296,26 @@ func relatedChange(plan *deployplan.Plan, node, path string) (*deployplan.Change continue } if change.Action != deployplan.Skip { - return change, true + return key, change, true } if found == nil { - found = change + foundKey, found = key, change } } - return found, found != nil + return foundKey, found, found != nil +} + +// changeAt looks a change up by its exact key. +func changeAt(plan *deployplan.Plan, node, key string) (*deployplan.ChangeDesc, bool) { + if plan == nil { + return nil, false + } + entry, ok := plan.Plan[node] + if !ok { + return nil, false + } + change, ok := entry.Changes[key] + return change, ok } // fieldWasDropped reports whether the plan mentions the field and yet will not act on it. @@ -308,6 +329,19 @@ func fieldWasDropped(plan *deployplan.Plan, node, path string) bool { return ok && change.Action == deployplan.Skip } +// baselineDrifts reports whether the field was already drifting before anything was changed. +// Matched by relation, the same way a plan's changes are: the baseline may be recorded against +// an ancestor -- a whole "config" block -- and treating that as this field's failure to reach +// its starting value would blame the ancestor's drift on every field beneath it. +func baselineDrifts(baseline map[string]bool, path string) bool { + for drifting := range baseline { + if sameField(drifting, path) { + return true + } + } + return false +} + // converged reports whether a plan no longer wants to change the field, at whatever // granularity the planner recorded it. func converged(plan *deployplan.Plan, node, path string) bool { @@ -347,7 +381,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // under a label that is not what happened -- "absent to 168" while the remote still // holds 720. Cheaper to check than to reason about afterwards. if plan, diags := h.readPlan(); !diags.HasError() { - if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change) && !baseline[path] { + if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change) && !baselineDrifts(baseline, path) { res.verdict = verdictStartNotReached res.detail = path res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) @@ -408,7 +442,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // One read cannot tell that apart from a read that was merely stale, so take a second // one. If the value has appeared by then the write did land and the first read was // behind; if it still has not, the field really was ignored. - if !baseline[path] && wasIgnored(plan, after, h.node, path) { + if !baselineDrifts(baseline, path) && wasIgnored(plan, after, h.node, path) { second, diags := h.readPlan() if diags.HasError() { res.verdict = verdictPlanError @@ -673,15 +707,16 @@ func baselineDrift(h *bundleHarness) (map[string]bool, error) { // the field is still pending in the post-deploy plan, and its remote value is identical to // what the pre-deploy plan saw. func wasIgnored(before, after *deployplan.Plan, node, path string) bool { - // relatedChange, not an exact lookup: the planner records a change at the granularity it - // diffed at, so a write to "libraries[1].pypi.repo" can be reported against - // "libraries[1].pypi" -- and comparing the wrong path finds nothing, which turns an - // ignored write into drift. - beforeChange, ok := relatedChange(before, node, path) + // Matched by relation, since the planner records a change at the granularity it diffed at: + // a write to "libraries[1].pypi.repo" can be reported against "libraries[1].pypi". But the + // two reads must be compared at the *same* key -- the granularity can differ between them, + // and comparing a leaf's value against its parent object never matches, which would turn + // every ignored write into drift. + beforeKey, beforeChange, ok := relatedChangeKey(before, node, path) if !ok { return false } - afterChange, ok := relatedChange(after, node, path) + afterChange, ok := changeAt(after, node, beforeKey) if !ok || afterChange.Action == deployplan.Skip { return false } @@ -717,7 +752,7 @@ func driftDetail(plan *deployplan.Plan, node string, baseline map[string]bool) ( continue } for p, ch := range entry.Changes { - if ch.Action != deployplan.Skip && !baseline[p] { + if ch.Action != deployplan.Skip && !baselineDrifts(baseline, p) { ownPaths = append(ownPaths, p) } } diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index 1faf5961797..ed42204f1e5 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -9,12 +9,11 @@ properties keys1 absent UPDATE_IGNORE properties keys1 keys0 UPDATE_IGNORED properties properties['team'] absent x START_NOT_REACHED properties['team'] properties['team'] absent y START_NOT_REACHED properties['team'] -properties['team'] x absent POST_DEPLOY_DRIFT properties,properties['team'] -properties['team'] y absent POST_DEPLOY_DRIFT properties,properties['team'] +properties['team'] x absent UPDATE_IGNORED properties['team'] +properties['team'] y absent UPDATE_IGNORED properties['team'] === summary OK 14 -UPDATE_IGNORED 4 -POST_DEPLOY_DRIFT 2 +UPDATE_IGNORED 6 SKIPPED 8 START_NOT_REACHED 6 diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index d75538d10de..49456642c34 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -9,13 +9,12 @@ properties keys1 absent UPDATE_IGNORE properties keys1 keys0 UPDATE_IGNORED properties properties['team'] absent x START_NOT_REACHED properties['team'] properties['team'] absent y START_NOT_REACHED properties['team'] -properties['team'] x absent POST_DEPLOY_DRIFT properties,properties['team'] -properties['team'] y absent POST_DEPLOY_DRIFT properties,properties['team'] +properties['team'] x absent UPDATE_IGNORED properties['team'] +properties['team'] y absent UPDATE_IGNORED properties['team'] === summary OK 12 OK_RECREATE 2 -UPDATE_IGNORED 4 -POST_DEPLOY_DRIFT 2 +UPDATE_IGNORED 6 SKIPPED 2 START_NOT_REACHED 6 diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 2e665a63cbd..dc6cf835d80 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -218,9 +218,14 @@ func defaultValues(typ reflect.Type) []any { // nothing: with absent in the set, that is add, grow, shrink and remove. if typ.Kind() == reflect.Slice { elements := defaultValues(typ.Elem()) - if len(elements) < 2 { + if len(elements) == 0 { return nil } + if len(elements) == 1 { + // One element is all the type offers -- a list of an enum with a single valid value. + // The empty list and that one still cover adding, clearing and removing. + return []any{[]any{}, []any{elements[0]}} + } // The empty list is its own value, not the same as absent: it is what a user writing // "on_start: []" produces, and it reaches the API as an explicit empty array through // ForceSendFields -- the only way to clear a list the backend already holds. From f539021b3645502703f6de9931961a6285e8276f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 15:30:19 +0200 Subject: [PATCH 047/118] Field catalog: instance pools are local-only A real workspace refuses to delete an instance pool ("Can't delete pool with id: ..."), and most of a pool's spec is immutable so most transitions recreate it -- 20 of them hit the failing delete on AWS. A plain deploy and destroy works, which is what the invariant suite does; a thousand recreates in a row does not. Co-authored-by: Isaac --- bundle/direct/tests/testdata/fields/instance_pools.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bundle/direct/tests/testdata/fields/instance_pools.yml b/bundle/direct/tests/testdata/fields/instance_pools.yml index abc75f36269..1bd08e569f9 100644 --- a/bundle/direct/tests/testdata/fields/instance_pools.yml +++ b/bundle/direct/tests/testdata/fields/instance_pools.yml @@ -1,4 +1,10 @@ # Value library for instance pools. See ../../README.md. + +# Most of a pool's spec is immutable, so most transitions recreate it -- and a real workspace +# refuses the delete half ("Can't delete pool with id: ..."), which this suite hits on every +# one of them. A plain deploy and destroy works, which is what the invariant suite does; a +# thousand recreates in a row does not, so the catalog for this type is the local one. +local_only: a real workspace refuses to delete a pool, and most fields recreate base: custom_tags: team: eng From fbe06732645bf326217965a463fb830ff0858b4c Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 16:09:33 +0200 Subject: [PATCH 048/118] Field catalog: align model serving endpoints and warehouses with AWS An enum's *_UNSPECIFIED member is a protobuf "unset" sentinel, not a choice: the backend normalizes it to whatever it defaults to, so asking for it reads as the write being ignored. Excluded from the values an enum offers, which fixes spot_instance_policy on warehouses and any other field whose enum has one. For model serving endpoints, three constraints on an endpoint the corpus config creates without a config block, each taken from a cloud error message: ai_gateway and rate_limits are rejected outright at create ("Cannot specify ai_gateway when creating endpoints without a config"), and an AI gateway only applies to an endpoint serving an external, provisioned throughput, or custom model. Those blocks are skipped, and the fake server now enforces the create rule so a future seed cannot pass locally and fail on cloud. A notification recipient is validated by format, so the generic "x" was rejected -- and CURRENT_USER_NAME is a service principal's UUID on cloud, not an address. Reserved non-resolving addresses instead, per RFC 2606. sql_warehouses and model_serving_endpoints now differ from AWS in one row each, both the same kind of finding and both left as they are rather than papered over: a warehouse reads back STARTING rather than RUNNING right after create, and an endpoint's email_notifications do not take effect from a create. Modelling either means changing behaviour the acceptance suite asserts today, which belongs in its own change. Co-authored-by: Isaac --- .../tests/output/model_serving_endpoints.txt | 43 +++---------------- bundle/direct/tests/output/sql_warehouses.txt | 4 +- bundle/direct/tests/testdata/fields/jobs.yml | 2 +- .../fields/model_serving_endpoints.yml | 38 ++++++++++------ .../tests/testdata/fields/pipelines.yml | 2 +- bundle/direct/tests/values_test.go | 8 +++- libs/testserver/serving_endpoints.go | 22 ++++++++++ 7 files changed, 63 insertions(+), 56 deletions(-) diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index d18bb359da6..c06a74380fd 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -3,43 +3,12 @@ budget_policy_id x absent START_NOT_REA budget_policy_id x y START_NOT_REACHED budget_policy_id (declared inert: no_update_api) budget_policy_id y absent START_NOT_REACHED budget_policy_id (declared inert: no_update_api) budget_policy_id y x START_NOT_REACHED budget_policy_id (declared inert: no_update_api) -config.auto_capture_config.catalog_name absent x UPDATE_IGNORED config.auto_capture_config.catalog_name -config.auto_capture_config.catalog_name absent y UPDATE_IGNORED config.auto_capture_config.catalog_name -config.auto_capture_config.catalog_name x y START_NOT_REACHED config.auto_capture_config.catalog_name -config.auto_capture_config.catalog_name y absent START_NOT_REACHED config.auto_capture_config.catalog_name -config.auto_capture_config.enabled absent false UPDATE_IGNORED config.auto_capture_config.enabled -config.auto_capture_config.enabled absent true UPDATE_IGNORED config.auto_capture_config.enabled -config.auto_capture_config.enabled false absent START_NOT_REACHED config.auto_capture_config.enabled -config.auto_capture_config.enabled false true START_NOT_REACHED config.auto_capture_config.enabled -config.auto_capture_config.enabled true absent START_NOT_REACHED config.auto_capture_config.enabled -config.auto_capture_config.enabled true false START_NOT_REACHED config.auto_capture_config.enabled -config.auto_capture_config.schema_name absent x UPDATE_IGNORED config.auto_capture_config.schema_name -config.auto_capture_config.schema_name x absent START_NOT_REACHED config.auto_capture_config.schema_name -config.auto_capture_config.schema_name x y START_NOT_REACHED config.auto_capture_config.schema_name -config.auto_capture_config.table_name_prefix absent x UPDATE_IGNORED config.auto_capture_config.table_name_prefix -config.auto_capture_config.table_name_prefix x y START_NOT_REACHED config.auto_capture_config.table_name_prefix -config.auto_capture_config.table_name_prefix y absent START_NOT_REACHED config.auto_capture_config.table_name_prefix -rate_limits absent len0 START_NOT_REACHED rate_limits (declared inert: not_implemented) -rate_limits absent len1 START_NOT_REACHED rate_limits (declared inert: not_implemented) -rate_limits len0 absent START_NOT_REACHED rate_limits (declared inert: not_implemented) -rate_limits len0 len1 START_NOT_REACHED rate_limits (declared inert: not_implemented) -rate_limits len1 absent START_NOT_REACHED rate_limits (declared inert: not_implemented) -rate_limits len1 len0 START_NOT_REACHED rate_limits (declared inert: not_implemented) -rate_limits[0].calls 1 2 START_NOT_REACHED rate_limits[0].calls (declared inert: not_implemented) -rate_limits[0].calls 2 1 START_NOT_REACHED rate_limits[0].calls (declared inert: not_implemented) -rate_limits[0].key absent endpoint START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) -rate_limits[0].key absent user START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) -rate_limits[0].key endpoint absent START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) -rate_limits[0].key endpoint user START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) -rate_limits[0].key user absent START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) -rate_limits[0].key user endpoint START_NOT_REACHED rate_limits[0].key (declared inert: not_implemented) === summary -OK 150 -OK_RECREATE 20 +OK 32 +OK_RECREATE 12 OK_INERT 2 -SUPPRESSED 14 -NOT_OBSERVABLE 10 -UPDATE_IGNORED 6 -SKIPPED 2 -START_NOT_REACHED 28 +SUPPRESSED 5 +NOT_OBSERVABLE 3 +SKIPPED 4 +START_NOT_REACHED 4 diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index 0f459c36e99..cf773893e2f 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -18,9 +18,9 @@ name absent y-UNIQUE START_NOT_REA name x-UNIQUE absent UPDATE_IGNORED name name y-UNIQUE absent UPDATE_IGNORED name spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy -spot_instance_policy POLICY_UNSPECIFIED absent UPDATE_IGNORED spot_instance_policy +spot_instance_policy RELIABILITY_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy spot_instance_policy absent COST_OPTIMIZED START_NOT_REACHED spot_instance_policy -spot_instance_policy absent POLICY_UNSPECIFIED START_NOT_REACHED spot_instance_policy +spot_instance_policy absent RELIABILITY_OPTIMIZED START_NOT_REACHED spot_instance_policy === summary OK 32 diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml index 1044d26a8e1..16bd37a6fba 100644 --- a/bundle/direct/tests/testdata/fields/jobs.yml +++ b/bundle/direct/tests/testdata/fields/jobs.yml @@ -47,7 +47,7 @@ base: value: 3600 email_notifications: on_failure: - - $CURRENT_USER_NAME + - notify@databricks.invalid webhook_notifications: on_failure: - id: 00000000-0000-0000-0000-000000000000 diff --git a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml index d5e7be1b685..78fe9ed630b 100644 --- a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml +++ b/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml @@ -4,30 +4,42 @@ # already declares a coherent block -- a catalog, a schema and table names -- so its fields # expand and are tested there. # -# ai_gateway, rate_limits and the notification lists are declared by no config, so they are -# seeded here. served_entities comes from the corpus config. +# The notification lists and tags are declared by no config, so they are seeded here. +# +# ai_gateway and the top-level rate_limits are deliberately not seeded: the API rejects both on +# an endpoint created without a config block ("Cannot specify ai_gateway when creating endpoints +# without a config"), and the corpus config declares no config block. Covering them needs a +# config that declares a served entity, which needs a model in the workspace -- so the report +# lists their fields as uncovered. base: - ai_gateway: - rate_limits: - - calls: 10 - renewal_period: minute - usage_tracking_config: - enabled: true - rate_limits: - - calls: 10 - renewal_period: minute email_notifications: on_update_failure: - - $CURRENT_USER_NAME + - notify@databricks.invalid on_update_success: - - $CURRENT_USER_NAME + - notify@databricks.invalid tags: - key: team value: eng skip: + # An AI gateway only applies to an endpoint serving an external model, provisioned + # throughput, or a custom or agent model -- "AI Gateway is currently only supported for + # External Models, Provisioned Throughput and Custom or Agent Model". The corpus config + # serves none of those, and the API also refuses the whole block on an endpoint created + # without a config. Covering it needs a config that declares such a served entity. + ai_gateway: needs an endpoint serving an external, provisioned-throughput or custom model + # Same shape: "Config needs to be set" -- there is no config block on this endpoint to + # attach an auto-capture destination to. + config: needs a config block, which the corpus config for this type does not declare + # The corpus config for this type declares no telemetry, and seeding a coherent block here # is rejected on every later update ("failed to update telemetry config"). The fields are # covered by model_serving_endpoint_telemetry.yml.tmpl, which declares the whole block -- # but that config is not the simplest one, so it is not the one this suite picks. telemetry_config: needs the telemetry corpus config, which is not the simplest one + +fields: + # A recipient is validated by format, so the generic "x" is rejected. Reserved + # non-resolving addresses, per RFC 2606. + email_notifications.on_update_failure[*]: [notify@databricks.invalid, alerts@databricks.invalid] + email_notifications.on_update_success[*]: [notify@databricks.invalid, alerts@databricks.invalid] diff --git a/bundle/direct/tests/testdata/fields/pipelines.yml b/bundle/direct/tests/testdata/fields/pipelines.yml index 4b2db2deb9c..fa1f9f5727e 100644 --- a/bundle/direct/tests/testdata/fields/pipelines.yml +++ b/bundle/direct/tests/testdata/fields/pipelines.yml @@ -36,7 +36,7 @@ base: - alerts: - on-update-failure email_recipients: - - $CURRENT_USER_NAME + - notify@databricks.invalid restart_window: start_hour: 1 time_zone_id: UTC diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index dc6cf835d80..a35d6637f85 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -255,9 +255,13 @@ func enumValues(typ reflect.Type) []any { all := make([]string, 0, result.Len()) for i := range result.Len() { - if value := result.Index(i).String(); value != "" { - all = append(all, value) + value := result.Index(i).String() + // A protobuf enum's zero member means "unset", not a choice: the backend normalizes it + // to whichever value it defaults to, so asking for it reads as the write being ignored. + if value == "" || strings.HasSuffix(value, "_UNSPECIFIED") { + continue } + all = append(all, value) } slices.Sort(all) if len(all) == 0 { diff --git a/libs/testserver/serving_endpoints.go b/libs/testserver/serving_endpoints.go index 72c2dfce313..12899f53531 100644 --- a/libs/testserver/serving_endpoints.go +++ b/libs/testserver/serving_endpoints.go @@ -224,6 +224,28 @@ func (s *FakeWorkspace) ServingEndpointCreate(req Request) Response { } } + // An endpoint created without a config block has nothing for these to apply to, so the API + // rejects them outright rather than storing them. + if createReq.Config == nil { + for _, rejected := range []struct { + field string + set bool + }{ + {"ai_gateway", createReq.AiGateway != nil}, + {"rate_limits", len(createReq.RateLimits) > 0}, + } { + if rejected.set { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "Cannot specify " + rejected.field + " when creating endpoints without a config.", + }, + } + } + } + } + // Convert config to output format var config *serving.EndpointCoreConfigOutput if createReq.Config != nil { From 9697bd3fa764b883339c7531c0259cae6d997ca1 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 16:15:18 +0200 Subject: [PATCH 049/118] Field catalog: align dashboards and Genie spaces with AWS Both name two workspace objects this suite provisions only one of: a SQL warehouse, which they require, and a workspace folder. A dashboard also cannot be moved between folders without a recreate, and the recreate then fails against a folder that is not there. Both fields are skipped with those reasons. dashboards matches AWS row for row. genie_spaces differs in one row, where a title the backend refuses to clear leaves the chain at a different value than it does locally; the underlying behaviour (clearing the title is ignored) is reported the same on both sides. Co-authored-by: Isaac --- bundle/direct/tests/output/dashboards.txt | 12 +++--------- bundle/direct/tests/output/genie_spaces.txt | 18 ++++-------------- .../tests/testdata/fields/dashboards.yml | 10 ++++++++++ .../tests/testdata/fields/genie_spaces.yml | 7 +++++++ 4 files changed, 24 insertions(+), 23 deletions(-) create mode 100644 bundle/direct/tests/testdata/fields/dashboards.yml create mode 100644 bundle/direct/tests/testdata/fields/genie_spaces.yml diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index ed4bd81810d..d4488ba0915 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -13,17 +13,11 @@ etag x absent START_NOT_REA etag x y START_NOT_REACHED etag etag y absent START_NOT_REACHED etag etag y x START_NOT_REACHED etag -parent_path absent x-UNIQUE START_NOT_REACHED parent_path -parent_path absent y-UNIQUE START_NOT_REACHED parent_path -parent_path x-UNIQUE absent START_NOT_REACHED parent_path -parent_path x-UNIQUE y-UNIQUE START_NOT_REACHED parent_path -parent_path y-UNIQUE absent START_NOT_REACHED parent_path -parent_path y-UNIQUE x-UNIQUE START_NOT_REACHED parent_path === summary -OK 20 +OK 14 SUPPRESSED 2 NOT_OBSERVABLE 6 UPDATE_IGNORED 2 -SKIPPED 6 -START_NOT_REACHED 18 +SKIPPED 8 +START_NOT_REACHED 12 diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index ce14d349f25..c6bc5a92ff2 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,24 +1,14 @@ === genie_space.yml.tmpl description x absent UPDATE_IGNORED description description y absent UPDATE_IGNORED description -parent_path absent x-UNIQUE START_NOT_REACHED parent_path -parent_path absent y-UNIQUE START_NOT_REACHED parent_path -parent_path x-UNIQUE absent START_NOT_REACHED parent_path -parent_path x-UNIQUE y-UNIQUE START_NOT_REACHED parent_path -parent_path y-UNIQUE absent START_NOT_REACHED parent_path -parent_path y-UNIQUE x-UNIQUE START_NOT_REACHED parent_path title absent x-UNIQUE START_NOT_REACHED title title absent y-UNIQUE START_NOT_REACHED title title x-UNIQUE absent UPDATE_IGNORED title title y-UNIQUE absent UPDATE_IGNORED title -warehouse_id absent x START_NOT_REACHED warehouse_id -warehouse_id absent y START_NOT_REACHED warehouse_id -warehouse_id x absent UPDATE_IGNORED warehouse_id -warehouse_id y absent UPDATE_IGNORED warehouse_id === summary -OK 8 +OK 6 NOT_OBSERVABLE 6 -UPDATE_IGNORED 6 -SKIPPED 2 -START_NOT_REACHED 10 +UPDATE_IGNORED 4 +SKIPPED 4 +START_NOT_REACHED 2 diff --git a/bundle/direct/tests/testdata/fields/dashboards.yml b/bundle/direct/tests/testdata/fields/dashboards.yml new file mode 100644 index 00000000000..f07320ae158 --- /dev/null +++ b/bundle/direct/tests/testdata/fields/dashboards.yml @@ -0,0 +1,10 @@ +# Value library for dashboards. See ../../README.md. + +skip: + # Must name a workspace folder that exists, and this suite provisions only the bundle's own. + # A dashboard also cannot be moved between folders without being recreated, and the recreate + # then fails against a folder that is not there. + parent_path: needs a second workspace folder to exist + # Must name an existing SQL warehouse, and dashboards need one, so a value-to-value move + # would need a second warehouse. + warehouse_id: needs a second SQL warehouse to exist diff --git a/bundle/direct/tests/testdata/fields/genie_spaces.yml b/bundle/direct/tests/testdata/fields/genie_spaces.yml new file mode 100644 index 00000000000..e71940ca8cc --- /dev/null +++ b/bundle/direct/tests/testdata/fields/genie_spaces.yml @@ -0,0 +1,7 @@ +# Value library for Genie spaces. See ../../README.md. + +skip: + # Same two as dashboards: both name workspace objects this suite does not provision a second + # of. See dashboards.yml. + parent_path: needs a second workspace folder to exist + warehouse_id: needs a second SQL warehouse to exist From cbfce2787a74f5c34d49d7a955c861f13b10558c Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 16:31:06 +0200 Subject: [PATCH 050/118] structaccess: do not walk a cyclic embedding twice From the review: a struct embedding a pointer to itself sent the embedded-field search round forever whenever the key was not found at all. Both the value and the type walk now skip a type they have already visited. Co-authored-by: Isaac --- libs/structs/structaccess/get.go | 11 ++++++++++- libs/structs/structaccess/set_test.go | 20 ++++++++++++++++++++ libs/structs/structaccess/typecheck.go | 10 +++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/libs/structs/structaccess/get.go b/libs/structs/structaccess/get.go index 1fdb49d6533..24311a7afa2 100644 --- a/libs/structs/structaccess/get.go +++ b/libs/structs/structaccess/get.go @@ -280,6 +280,9 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S // embed could pick a field three levels down over the same name two levels down in a // later one -- and then reading or writing the field would not be the field serialized // under that name. + // A cyclic embedding (a struct embedding a pointer to itself) would otherwise enqueue the + // same type forever when the key is not found at all. + seen := map[reflect.Type]bool{v.Type(): true} level := embeddedStructs(v) for len(level) > 0 { var next []reflect.Value @@ -297,7 +300,13 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S }{out, sf, fv}) continue } - next = append(next, embeddedStructs(fv)...) + for _, deeper := range embeddedStructs(fv) { + if seen[deeper.Type()] { + continue + } + seen[deeper.Type()] = true + next = append(next, deeper) + } } if len(found) == 1 { return found[0].value, found[0].field, found[0].owner, true diff --git a/libs/structs/structaccess/set_test.go b/libs/structs/structaccess/set_test.go index 138fc605d57..ae7bb6c0235 100644 --- a/libs/structs/structaccess/set_test.go +++ b/libs/structs/structaccess/set_test.go @@ -892,3 +892,23 @@ func TestSet_FailedConversionLeavesForceSendFields(t *testing.T) { assert.Empty(t, job.ForceSendFields) assert.Equal(t, "n", job.Name) } + +// A struct embedding a pointer to itself: the search must not walk the same type twice, or a +// key it never finds sends it round forever. +type cyclicEmbed struct { + *cyclicEmbed + Name string `json:"name"` +} + +func TestGet_CyclicEmbedTerminates(t *testing.T) { + target := &cyclicEmbed{Name: "n"} //exhaustruct:ignore + target.cyclicEmbed = target + + got, err := structaccess.GetByString(target, "name") + require.NoError(t, err) + assert.Equal(t, "n", got) + + _, err = structaccess.GetByString(target, "nope") + require.Error(t, err) + require.Error(t, structaccess.ValidateByString(reflect.TypeOf(target), "nope")) +} diff --git a/libs/structs/structaccess/typecheck.go b/libs/structs/structaccess/typecheck.go index 160925da721..f4797f88480 100644 --- a/libs/structs/structaccess/typecheck.go +++ b/libs/structs/structaccess/typecheck.go @@ -154,6 +154,8 @@ func FindStructFieldByKeyType(t reflect.Type, key string) (reflect.StructField, // Second pass: search embedded anonymous structs breadth-first, mirroring findStructFieldByKey // (get.go) so a path validates against the same field Get and Set resolve it to, which is // the one encoding/json serializes: the shallower of two same-named fields. + // See findStructFieldByKey: a cyclic embedding must not be walked twice. + seen := map[reflect.Type]bool{t: true} level := embeddedStructTypes(t) for len(level) > 0 { var next []reflect.Type @@ -169,7 +171,13 @@ func FindStructFieldByKeyType(t reflect.Type, key string) (reflect.StructField, }{sf, ft}) continue } - next = append(next, embeddedStructTypes(ft)...) + for _, deeper := range embeddedStructTypes(ft) { + if seen[deeper] { + continue + } + seen[deeper] = true + next = append(next, deeper) + } } if len(found) == 1 { return found[0].field, found[0].owner, true From 5646c21a469c1942175e015c1849e3620b6bd91e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 16:37:20 +0200 Subject: [PATCH 051/118] Field catalog: fix four base configs and three attribution issues The four remaining types did not deploy at all on AWS, each for a reason in my own seeds: a job's notebook path must be relative once a git_source is seeded; an app resource is a union whose every member names a workspace object this suite does not provision; node-type flexibility cannot be supplied alongside the instance pool the cluster config sets; and a pipeline rejects a .invalid notification address. A pipeline's channel and edition are constrained by the backend but typed as plain strings, so nothing derived their values and the generic "x" was rejected. From the review, three fixes to how a verdict is attributed: - A failed plan after deploying the starting value was ignored, and the transition went ahead anyway -- labelling whatever happened next as a move that may never have started. - The post-deploy drift filter went back to comparing exact entries. Matching by relation there let a baseline recorded against an ancestor hide new drift reported under it, and over-reporting is the safer error for a catalog: a spurious row can be investigated, a hidden one cannot. The starting-value check still matches by relation, where the question is the opposite one. - A rebuild takes the new resource's own baseline drift. Carrying the replaced resource's measurement over would either hide drift the new one has or blame it on the field under test. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 57 +++++++++++++------ bundle/direct/tests/output/clusters.txt | 8 +-- bundle/direct/tests/testdata/fields/apps.yml | 8 +-- .../direct/tests/testdata/fields/clusters.yml | 14 +++-- bundle/direct/tests/testdata/fields/jobs.yml | 4 +- .../tests/testdata/fields/pipelines.yml | 8 ++- 6 files changed, 65 insertions(+), 34 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index ccf17a5721e..c92044080cb 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -209,7 +209,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl res.evidence = err.Error() broken = err } else { - h, base = rebuilt, rebuilt.snapshot() + h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline) res = runTransition(t, h, cfg.name, f.path, tr, false, baseline) } } @@ -231,8 +231,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl broken = err return } - h = rebuilt - base = h.snapshot() + h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline) }) } }) @@ -252,8 +251,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl rep.add(result{cfg.name, f.path, "", "", verdictBaseError, oneLine(err.Error()), err.Error()}) return } - h = rebuilt - base = h.snapshot() + h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline) } } } @@ -329,10 +327,24 @@ func fieldWasDropped(plan *deployplan.Plan, node, path string) bool { return ok && change.Action == deployplan.Skip } -// baselineDrifts reports whether the field was already drifting before anything was changed. -// Matched by relation, the same way a plan's changes are: the baseline may be recorded against -// an ancestor -- a whole "config" block -- and treating that as this field's failure to reach -// its starting value would blame the ancestor's drift on every field beneath it. +// remeasure takes the new resource's own baseline drift. A replacement is a different +// resource: carrying the old measurement over would either hide drift the new one has or blame +// it on whichever field is under test. Falls back to the previous measurement if the plan +// fails, since that is closer than nothing. +func remeasure(h *bundleHarness, previous map[string]bool) map[string]bool { + if measured, err := baselineDrift(h); err == nil { + return measured + } + return previous +} + +// baselineDrifts reports whether the field was already drifting before anything was changed, +// which is what makes it unable to reach a starting value through no fault of the transition. +// Matched by relation: the baseline may be recorded against an ancestor -- a whole "config" +// block -- and treating that as this field's failure would blame the ancestor's drift on every +// field beneath it. +// +// The post-deploy drift filter uses an exact comparison instead; see driftDetail. func baselineDrifts(baseline map[string]bool, path string) bool { for drifting := range baseline { if sameField(drifting, path) { @@ -380,13 +392,19 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // ignores leaves the old value there, and the transition below would then be reported // under a label that is not what happened -- "absent to 168" while the remote still // holds 720. Cheaper to check than to reason about afterwards. - if plan, diags := h.readPlan(); !diags.HasError() { - if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change) && !baselineDrifts(baseline, path) { - res.verdict = verdictStartNotReached - res.detail = path - res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) - return res - } + plan, diags := h.readPlan() + if diags.HasError() { + // Without this plan there is no way to know the field reached "from", and going ahead + // would label whatever happens next as a move that may never have started. + res.verdict = verdictPlanError + res.detail = firstError(diags) + return res + } + if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change) && !baselineDrifts(baseline, path) { + res.verdict = verdictStartNotReached + res.detail = path + res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) + return res } } @@ -442,7 +460,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // One read cannot tell that apart from a read that was merely stale, so take a second // one. If the value has appeared by then the write did land and the first read was // behind; if it still has not, the field really was ignored. - if !baselineDrifts(baseline, path) && wasIgnored(plan, after, h.node, path) { + if !baseline[path] && wasIgnored(plan, after, h.node, path) { second, diags := h.readPlan() if diags.HasError() { res.verdict = verdictPlanError @@ -752,7 +770,10 @@ func driftDetail(plan *deployplan.Plan, node string, baseline map[string]bool) ( continue } for p, ch := range entry.Changes { - if ch.Action != deployplan.Skip && !baselineDrifts(baseline, p) { + // Exact, not by relation: a baseline recorded against an ancestor must not hide new + // drift reported against something under it. Over-reporting is the safer error for a + // catalog -- a spurious row is visible and can be investigated, a hidden one cannot. + if ch.Action != deployplan.Skip && !baseline[p] { ownPaths = append(ownPaths, p) } } diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/tests/output/clusters.txt index 217504b4bf4..0e802c4e365 100644 --- a/bundle/direct/tests/output/clusters.txt +++ b/bundle/direct/tests/output/clusters.txt @@ -3,8 +3,8 @@ aws_attributes.ebs_volume_count absent 1 START_NOT_REA aws_attributes.spot_bid_price_percent absent 1 START_NOT_REACHED aws_attributes.spot_bid_price_percent === summary -OK 447 -SUPPRESSED 24 -NOT_OBSERVABLE 11 -SKIPPED 1 +OK 427 +SUPPRESSED 22 +NOT_OBSERVABLE 9 +SKIPPED 4 START_NOT_REACHED 2 diff --git a/bundle/direct/tests/testdata/fields/apps.yml b/bundle/direct/tests/testdata/fields/apps.yml index c5589d5a954..ac424bd2063 100644 --- a/bundle/direct/tests/testdata/fields/apps.yml +++ b/bundle/direct/tests/testdata/fields/apps.yml @@ -10,13 +10,13 @@ base: env: - name: MY_VAR value: value + # An app resource is a union (secret, job, sql_warehouse, ...), and every member names a + # workspace object this suite does not provision -- a secret scope, a job, a warehouse. Only + # the entry's own fields are seeded, so name, description and the union members stay + # uncovered, which the report says. resources: - name: seeded description: seeded resource - secret: - scope: my-scope - key: my-key - permission: READ fields: compute_size: [MEDIUM, LARGE] diff --git a/bundle/direct/tests/testdata/fields/clusters.yml b/bundle/direct/tests/testdata/fields/clusters.yml index 1182d5b9209..409e3e98810 100644 --- a/bundle/direct/tests/testdata/fields/clusters.yml +++ b/bundle/direct/tests/testdata/fields/clusters.yml @@ -16,9 +16,11 @@ base: init_scripts: - workspace: destination: /Users/$CURRENT_USER_NAME/init.sh - worker_node_type_flexibility: - alternate_node_type_ids: - - i3.2xlarge - driver_node_type_flexibility: - alternate_node_type_ids: - - i3.2xlarge + +skip: + # "The field 'worker_node_type_flexibility' cannot be supplied when an instance pool ID is + # provided", and the corpus config provides one. The alternates would also have to name a + # second node type, which differs per cloud. + worker_node_type_flexibility: incompatible with the instance pool the config sets + driver_node_type_flexibility: incompatible with the instance pool the config sets + node_type_flexibility: incompatible with the instance pool the config sets diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml index 16bd37a6fba..7172db98e58 100644 --- a/bundle/direct/tests/testdata/fields/jobs.yml +++ b/bundle/direct/tests/testdata/fields/jobs.yml @@ -14,7 +14,9 @@ base: tasks: - task_key: seeded notebook_task: - notebook_path: /Shared/notebook + # Relative, because the seeded git_source makes the task read from the repository: + # "Only relative paths are currently supported for remote repositories." + notebook_path: notebook job_cluster_key: seeded job_clusters: - job_cluster_key: seeded diff --git a/bundle/direct/tests/testdata/fields/pipelines.yml b/bundle/direct/tests/testdata/fields/pipelines.yml index fa1f9f5727e..68983ee6785 100644 --- a/bundle/direct/tests/testdata/fields/pipelines.yml +++ b/bundle/direct/tests/testdata/fields/pipelines.yml @@ -36,7 +36,7 @@ base: - alerts: - on-update-failure email_recipients: - - notify@databricks.invalid + - notify@example.com restart_window: start_hour: 1 time_zone_id: UTC @@ -44,3 +44,9 @@ base: team: eng parameters: table: users + +fields: + # Constrained by the backend but typed as a plain string in the SDK, so nothing derives their + # values: the generic "x" is rejected on a real workspace. + channel: [CURRENT, PREVIEW] + edition: [CORE, PRO] From 764bcc32908be23227a41c82043c39909c694717 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 16:50:25 +0200 Subject: [PATCH 052/118] Field catalog: bring the README in line with what the suite does Three claims had gone stale. It does read resources.yml, for two specific things -- skipping a declared backend output, and checking a declared-inert field really is inert. The verdict table was missing seven verdicts the suite emits, including the two that dominate the findings. And the largest class it finds is not drift but a field the backend will not clear, seen from both sides as START_NOT_REACHED and UPDATE_IGNORED. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 46 ++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index dc18941e62e..362f2f2d01c 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -64,9 +64,19 @@ API sees. | `UNSETTABLE` | the value could not be written into the config at all | | `BASE_ERROR` | the transition's starting point would not deploy, so nothing was observed | | `START_NOT_REACHED` | the starting value deployed without error but the field did not end up holding it, so the move under test could not be set up — a field the API refuses to clear cannot start from `absent`. Retried once on a fresh resource before being recorded | - -`POST_DEPLOY_DRIFT` and `SUPPRESSED` are where the interesting gaps are. `NOT_OBSERVABLE` -is not a gap; it is the wire format telling the truth. +| `UPDATE_IGNORED` | the apply succeeded and the engine sent the write, but the field's remote value is unchanged on two consecutive reads: the backend accepted the request and ignored this field | +| `STALE_READ` | the write did land, but the read straight after the apply did not show it and the next one did — not a support gap, yet a user planning right after a deploy is shown a change that does not exist | +| `BASELINE_DRIFT` | the field drifts with no config change at all, measured once per resource so it is not blamed on every field tested afterwards | +| `COLLATERAL_DRIFT` | the field under test converged, but updating it left some *other* field drifting; the detail names that field, which is where the fix belongs | +| `OK_INERT` | the resource declares that it ignores local changes to this field, and it does — the claim verified rather than assumed | +| `INERT_NOT_HONOURED` | the resource declares the field inert and the change was applied anyway | +| `SKIPPED` | left out by the value library, with a reason | + +`START_NOT_REACHED` and `UPDATE_IGNORED` are two views of the largest class this suite finds: +a field the backend will not clear. The engine sends the write, the backend keeps what it had, +and nothing can then start from `absent`. `SUPPRESSED` with a non-benign reason and +`COLLATERAL_DRIFT` are the next most interesting. `NOT_OBSERVABLE` is not a gap; it is the wire +format telling the truth. Two files per resource type. `output/.txt` is committed: one line per finding, then the count of every verdict — including the passing ones, which no line names. Those counts @@ -77,9 +87,15 @@ recreated instead of updated moves one `OK` to `OK_RECREATE` and nothing else wo under each finding — the post-deploy plan for drift, the whole API error for a rejection. It is gitignored, because it is full of generated ids and moves whenever any row moves. -The suite deliberately does **not** read `resources.yml`. That file is the engine's -answer to many of these cases, so consulting it would just restate the implementation. -The `SUPPRESSED` reason string is the engine explaining itself, which is different. +The suite reads `resources.yml` for two things only. A field the resource declares an +`output_only` backend output is skipped rather than tested, since a user cannot meaningfully +set one at all. And a field the resource declares it ignores local changes to is tested +anyway, so the claim is checked: it should come back suppressed with exactly that reason, +which is `OK_INERT`, and anything else is `INERT_NOT_HONOURED`. + +Nothing else is consulted. `resources.yml` is the engine's answer to most of these cases, so +reading it further would just restate the implementation; the `SUPPRESSED` reason string is +the engine explaining itself, which is different. ## Value library @@ -106,9 +122,14 @@ fields: git_source.git_commit: [abc123, def456] ``` -Every resource type runs everywhere — local and cloud alike — so most types need no file -at all. A field with no entry gets two values for its Go kind, which is enough to see a -value-to-value move on top of add and remove. +A field with no entry gets two values derived from its type: the first two members of an SDK +enum (excluding the `*_UNSPECIFIED` sentinel, which means "unset" and is normalized away), or +two of its Go kind. That is enough to see a value-to-value move on top of add and remove. + +Values that a real workspace constrains have to be written down even when the Go type does not +say so — a pipeline's `channel` and `edition` are plain strings the backend validates, and a +cluster size is a t-shirt size. The AWS run is what finds these: locally the fake server takes +anything. A type that cannot run against a real workspace at all declares `local_only: ` and is skipped on cloud — an external location needs a storage credential with cloud IAM behind @@ -120,9 +141,10 @@ field rules, and naming a block skips everything beneath it. A field that names the resource gets the run's own unique suffix appended to its values, so two runs against one workspace never ask for the same name — the second would get "already -exists", which says nothing about the engine. Such a field is either declared as part of the -resource's id, or the corpus config's own value for it carries the run's suffix. The suffix is -redacted in the report, so the golden is the same on every run. +exists", which says nothing about the engine. Such a field is recognised by the corpus config +templating its value with `$UNIQUE_NAME`. The suffix is a placeholder until the value is +written, since a rebuilt resource has a new one while the old is still alive, and it is +redacted in the report so the golden is the same on every run. `base` is expanded with the same `$VARS` the corpus configs use — `$CURRENT_USER_NAME`, `$UNIQUE_NAME`, `$NODE_TYPE_ID` and the rest — so a seeded value can name the workspace's From 5d7a1d2561bec384be3b24e55711cab3185b5aef Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 16:55:48 +0200 Subject: [PATCH 053/118] structaccess: notice a diamond embedding as ambiguous From the review: the cycle guard deduplicated types globally, so a diamond -- two embeds reaching one type, putting the name at the same depth twice -- was only visited once and resolved to a field encoding/json omits. Types are now excluded only from earlier levels, so the two paths within one level produce the two matches that make it ambiguous. Co-authored-by: Isaac --- libs/structs/structaccess/get.go | 10 ++++++--- libs/structs/structaccess/set_test.go | 30 ++++++++++++++++++++++++++ libs/structs/structaccess/typecheck.go | 7 ++++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/libs/structs/structaccess/get.go b/libs/structs/structaccess/get.go index 24311a7afa2..30513a8f153 100644 --- a/libs/structs/structaccess/get.go +++ b/libs/structs/structaccess/get.go @@ -280,8 +280,10 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S // embed could pick a field three levels down over the same name two levels down in a // later one -- and then reading or writing the field would not be the field serialized // under that name. - // A cyclic embedding (a struct embedding a pointer to itself) would otherwise enqueue the - // same type forever when the key is not found at all. + // Guards against a cyclic embedding (a struct embedding a pointer to itself), which would + // otherwise enqueue the same type forever when the key is not found at all. Only types from + // *earlier* levels are excluded: a type reachable twice within one level is a diamond, and + // the two matches it produces are exactly the ambiguity json resolves by omitting the field. seen := map[reflect.Type]bool{v.Type(): true} level := embeddedStructs(v) for len(level) > 0 { @@ -304,10 +306,12 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S if seen[deeper.Type()] { continue } - seen[deeper.Type()] = true next = append(next, deeper) } } + for _, fv := range next { + seen[fv.Type()] = true + } if len(found) == 1 { return found[0].value, found[0].field, found[0].owner, true } diff --git a/libs/structs/structaccess/set_test.go b/libs/structs/structaccess/set_test.go index ae7bb6c0235..b8ca1c3d448 100644 --- a/libs/structs/structaccess/set_test.go +++ b/libs/structs/structaccess/set_test.go @@ -912,3 +912,33 @@ func TestGet_CyclicEmbedTerminates(t *testing.T) { require.Error(t, err) require.Error(t, structaccess.ValidateByString(reflect.TypeOf(target), "nope")) } + +// A diamond: two embeds reaching one type, so the name sits at the same depth twice. +// encoding/json omits it, and the search has to see both paths to notice. +type diamondLeaf struct { + Value string `json:"value"` +} + +type diamondLeft struct { + diamondLeaf +} + +type diamondRight struct { + diamondLeaf +} + +type diamondEmbeds struct { + diamondLeft + diamondRight +} + +func TestSet_DiamondEmbedIsAmbiguous(t *testing.T) { + target := &diamondEmbeds{} + + require.Error(t, structaccess.SetByString(target, "value", "set")) + require.Error(t, structaccess.ValidateByString(reflect.TypeOf(target), "value")) + + blob, err := json.Marshal(target) + require.NoError(t, err) + assert.JSONEq(t, `{}`, string(blob)) +} diff --git a/libs/structs/structaccess/typecheck.go b/libs/structs/structaccess/typecheck.go index f4797f88480..547b12d42a7 100644 --- a/libs/structs/structaccess/typecheck.go +++ b/libs/structs/structaccess/typecheck.go @@ -154,7 +154,8 @@ func FindStructFieldByKeyType(t reflect.Type, key string) (reflect.StructField, // Second pass: search embedded anonymous structs breadth-first, mirroring findStructFieldByKey // (get.go) so a path validates against the same field Get and Set resolve it to, which is // the one encoding/json serializes: the shallower of two same-named fields. - // See findStructFieldByKey: a cyclic embedding must not be walked twice. + // See findStructFieldByKey: a cycle must not be walked twice, but a type reachable twice + // within one level is a diamond and its two matches are the ambiguity json omits. seen := map[reflect.Type]bool{t: true} level := embeddedStructTypes(t) for len(level) > 0 { @@ -175,10 +176,12 @@ func FindStructFieldByKeyType(t reflect.Type, key string) (reflect.StructField, if seen[deeper] { continue } - seen[deeper] = true next = append(next, deeper) } } + for _, ft := range next { + seen[ft] = true + } if len(found) == 1 { return found[0].field, found[0].owner, true } From 66fc2867bfc1eea61145da3f7bec03e150ee4853 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 17:02:48 +0200 Subject: [PATCH 054/118] Field catalog: recognise the planner's key-value selectors, four attribution fixes The planner names a slice element by a key-value selector -- "tasks[task_key='seeded']" -- where this suite names it by index. Nothing matched the two forms, so for jobs, the largest resource, every per-field entry the planner recorded was invisible: 132 transitions were reported as having nothing to observe when in fact the planner had diffed the field and dropped it, with a reason. Both forms now reduce to one before comparison. Four fixes from the review, all about attributing a verdict to the right cause: - A field the plan does not act on is now reported as such whatever the node's action says. Another field already drifting keeps the node at "update", and going ahead on that applied nothing for the field under test and then reported OK once the other field was filtered out as known baseline drift. - The ignored-write guard compares the baseline against the key the plan resolved the field to, not the leaf under test, so existing drift at "config" is no longer reported as an ignored write to "config.foo". - A recreate replaces the resource without going through rebuild, so its baseline drift is re-measured too. - A baseline measurement that fails now says so, since every verdict after it is measured against a baseline that may belong to a resource that no longer exists. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 71 ++++++++++++++++--- .../tests/output/database_instances.txt | 5 +- bundle/direct/tests/output/jobs.txt | 9 +-- bundle/direct/tests/output/secret_scopes.txt | 5 +- bundle/direct/tests/values_unit_test.go | 21 ++++++ 5 files changed, 91 insertions(+), 20 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index c92044080cb..2ee1739b77d 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -220,6 +220,12 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // Only a clean apply leaves the field provably at "to". deployed, deployedKnown = tr.to, res.verdict == verdictOK || res.verdict == verdictRecreate + if res.verdict == verdictRecreate { + // A recreate replaced the resource without going through rebuild, so the + // baseline belongs to one that no longer exists. + baseline = remeasure(h, baseline) + } + if broken != nil || res.verdict.leavesResourceUsable() { return } @@ -261,11 +267,24 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // "config.auto_capture_config.catalog_name" -- and that is still the field's own drift, not // some other field's. func sameField(drifting, path string) bool { + drifting, path = normalizeSelectors(drifting), normalizeSelectors(path) return drifting == path || strings.HasPrefix(path, drifting+".") || strings.HasPrefix(path, drifting+"[") || strings.HasPrefix(drifting, path+".") || strings.HasPrefix(drifting, path+"[") } +// selectorPattern matches the key-value selector the planner uses to name a slice element it +// can identify -- "tasks[task_key='seeded']" for what this suite calls "tasks[0]". +var selectorPattern = regexp.MustCompile(`\[(?:[a-z_]+='[^']*'|[0-9]+)\]`) + +// normalizeSelectors reduces both ways of naming a slice element to one form, so a change the +// planner recorded against "tasks[task_key='seeded'].run_if" is recognised as the same field as +// "tasks[0].run_if". Which element it is does not need to be resolved: this suite drives one +// element at a time, so at most one is in play. +func normalizeSelectors(path string) string { + return selectorPattern.ReplaceAllString(path, "[]") +} + // relatedChange returns the plan's change for the field, or for whatever ancestor or // descendant of it the planner actually recorded -- it diffs at its own granularity, so a // change to "tasks[0].description" can be reported against "tasks". A pending change is @@ -322,22 +341,53 @@ func changeAt(plan *deployplan.Plan, node, key string) (*deployplan.ChangeDesc, // the field, an ancestor of it, or something under it counts. A field the plan does not // mention at all is not "dropped": there is nothing to conclude from its absence, and the // node's own action decides. -func fieldWasDropped(plan *deployplan.Plan, node, path string) bool { +func fieldWillChange(plan *deployplan.Plan, node, path string) bool { + // A create or a recreate replaces the resource and records no per-field detail, so there is + // nothing to consult and the node's own action is the answer. + if !hasFieldChanges(plan, node) { + return true + } change, ok := relatedChange(plan, node, path) - return ok && change.Action == deployplan.Skip + return ok && change.Action != deployplan.Skip +} + +// hasFieldChanges reports whether the plan records per-field detail for the node. +func hasFieldChanges(plan *deployplan.Plan, node string) bool { + if plan == nil { + return false + } + entry, ok := plan.Plan[node] + return ok && len(entry.Changes) > 0 } -// remeasure takes the new resource's own baseline drift. A replacement is a different -// resource: carrying the old measurement over would either hide drift the new one has or blame -// it on whichever field is under test. Falls back to the previous measurement if the plan -// fails, since that is closer than nothing. +// remeasure takes the resource's own baseline drift, for a resource that has just replaced the +// one measured before: carrying the old measurement over would either hide drift the new one +// has or blame it on whichever field is under test. +// +// A plan that fails here leaves the previous measurement in place, which is closer than +// nothing, and says so: every verdict after it is measured against a baseline that may not be +// this resource's. func remeasure(h *bundleHarness, previous map[string]bool) map[string]bool { - if measured, err := baselineDrift(h); err == nil { + measured, err := baselineDrift(h) + if err == nil { return measured } + h.t.Logf("could not measure the new resource's baseline drift, keeping the previous one: %s", err) return previous } +// baselineCovers reports whether the drift this field would be blamed for is drift that was +// already there. The comparison is against the key the plan resolved the field to, not the leaf +// under test: existing drift at "config" would otherwise be reported as an ignored write to +// "config.foo". +func baselineCovers(baseline map[string]bool, plan *deployplan.Plan, node, path string) bool { + if baseline[path] { + return true + } + key, _, ok := relatedChangeKey(plan, node, path) + return ok && baseline[key] +} + // baselineDrifts reports whether the field was already drifting before anything was changed, // which is what makes it unable to reach a starting value through no fault of the transition. // Matched by relation: the baseline may be recorded against an ancestor -- a whole "config" @@ -427,7 +477,10 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // the node at "update", and reading only that would let a change to *this* field be // dropped and still come out as OK once the other field is filtered from the post-deploy // plan. - if action == deployplan.Skip || fieldWasDropped(plan, h.node, path) { + // A field the plan does not act on, however the node's action reads. Another field already + // drifting keeps the node at "update", and going ahead on that would apply nothing for this + // field and then report OK once the other field is filtered out as known baseline drift. + if action == deployplan.Skip || !fieldWillChange(plan, h.node, path) { pending.cancel() res.verdict, res.detail = explainSkip(plan, h.node, path) return res @@ -460,7 +513,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // One read cannot tell that apart from a read that was merely stale, so take a second // one. If the value has appeared by then the write did land and the first read was // behind; if it still has not, the field really was ignored. - if !baseline[path] && wasIgnored(plan, after, h.node, path) { + if !baselineCovers(baseline, plan, h.node, path) && wasIgnored(plan, after, h.node, path) { second, diags := h.readPlan() if diags.HasError() { res.verdict = verdictPlanError diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index 6716c487a70..f0cedfc7ae4 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -6,7 +6,6 @@ capacity x y START_NOT_REA capacity y absent START_NOT_REACHED capacity capacity y x START_NOT_REACHED capacity custom_tags absent len1 COLLATERAL_DRIFT capacity -custom_tags len0 absent COLLATERAL_DRIFT capacity custom_tags len0 len1 COLLATERAL_DRIFT capacity custom_tags len1 absent COLLATERAL_DRIFT capacity custom_tags len1 len0 COLLATERAL_DRIFT capacity @@ -56,8 +55,8 @@ usage_policy_id y x START_NOT_REA === summary OK_RECREATE 32 SUPPRESSED 6 -NOT_OBSERVABLE 1 +NOT_OBSERVABLE 2 UPDATE_IGNORED 2 -COLLATERAL_DRIFT 17 +COLLATERAL_DRIFT 16 SKIPPED 24 START_NOT_REACHED 34 diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index f6ba6d1ffae..7ecac86a01c 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -3,11 +3,12 @@ deployment.version_id x absent START_NOT_REA deployment.version_id x y START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id y x START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) +tasks[0].for_each_task.task.new_cluster.apply_policy_default_values true absent START_NOT_REACHED tasks[0].for_each_task.task.new_cluster.apply_policy_default_values === summary -OK 2858 +OK 2857 OK_INERT 2 -SUPPRESSED 18 -NOT_OBSERVABLE 232 +SUPPRESSED 150 +NOT_OBSERVABLE 100 SKIPPED 3 -START_NOT_REACHED 4 +START_NOT_REACHED 5 diff --git a/bundle/direct/tests/output/secret_scopes.txt b/bundle/direct/tests/output/secret_scopes.txt index 1742efc3439..60b3f54311f 100644 --- a/bundle/direct/tests/output/secret_scopes.txt +++ b/bundle/direct/tests/output/secret_scopes.txt @@ -1,11 +1,8 @@ === secret_scope.yml.tmpl backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... backend_type AZURE_KEYVAULT absent BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... -backend_type DATABRICKS AZURE_KEYVAULT BACKEND_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... -backend_type absent AZURE_KEYVAULT BACKEND_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... === summary -OK_RECREATE 8 -BACKEND_ERROR 2 +NOT_OBSERVABLE 10 SKIPPED 1 BASE_ERROR 2 diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go index 79fdcd5c5ee..5ab66c8f1a5 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/tests/values_unit_test.go @@ -199,3 +199,24 @@ func TestRedactIDs(t *testing.T) { assert.Equal(t, tc.want, redactIDs(tc.in)) } } + +// The planner names a slice element by a key-value selector where this suite names it by index, +// so the two forms have to be recognised as the same field -- otherwise a change recorded +// against "tasks[task_key='seeded'].run_if" looks unrelated to "tasks[0].run_if". +func TestSameField(t *testing.T) { + for _, tc := range []struct { + drifting, path string + want bool + }{ + {"comment", "comment", true}, + {"tasks", "tasks[0].run_if", true}, + {"tasks[task_key='seeded'].run_if", "tasks[0].run_if", true}, + {"tasks[task_key='seeded']", "tasks[0].run_if", true}, + {"tasks[task_key='seeded'].notebook_task.source", "tasks[0].notebook_task", true}, + {"config", "config.auto_capture_config.enabled", true}, + {"comment", "name", false}, + {"tasks[task_key='seeded'].run_if", "tasks[0].timeout_seconds", false}, + } { + assert.Equal(t, tc.want, sameField(tc.drifting, tc.path), "%s vs %s", tc.drifting, tc.path) + } +} From 09f1f54dabe63d096f78cdb6915b42ea7f19e40a Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 17:14:28 +0200 Subject: [PATCH 055/118] Field catalog: skip the app policy ids, document targeted cloud runs An app's budget_policy_id and usage_policy_id both name an account-level policy, and a test workspace has none: "Invalid resource: accounts//budgetPolicies/x". The fake server takes any string, so both read OK locally, which is worth nothing until a workspace has agreed. Found by driving just those two fields against AWS rather than the whole type, which the README now describes: a field is a subtest, so a filter can name a few and the rows land in the per-cloud full report. That is the cheap way to check a field the local report calls OK when its value looks like it must name something real. The seeded app resources entry is gone too: the API rejects an entry carrying no union member at all, and every member names a workspace object this suite does not provision. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 16 +++++++++++ bundle/direct/tests/output/apps.txt | 30 +++----------------- bundle/direct/tests/testdata/fields/apps.yml | 18 +++++++----- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index 362f2f2d01c..e1e959b9298 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -171,6 +171,22 @@ problem verdict: go test ./bundle/direct/tests -run TestFields/pipelines/.*/dry_run/absent_to_true -v ``` +### Checking one field against a real workspace + +A whole resource type on cloud is slow (jobs is half an hour) and the interesting question is +usually about a handful of fields. Every field is a subtest, so a few can be driven on their +own: + + CLOUD_ENV=aws go test ./bundle/direct/tests \ + -run 'TestFields/^apps$/[^/]*/^(budget_policy_id|usage_policy_id)$' + +The golden comparison fails on a partial run, which is expected: read the rows in +`output/..full.txt` instead. This is how to check a field the local report calls +`OK` when its value looks like it must name something real, a `*_id` or an ARN or a path. The +fake server takes any string, so `OK` there means nothing until a workspace has agreed: both +`budget_policy_id` and `usage_policy_id` on apps read `OK` locally and are rejected outright on +a real workspace, which names an account-level policy neither has. + On cloud (`CLOUD_ENV` set) the same reports are compared against the same committed goldens: a divergence means the fake server in `libs/testserver` does not model the API faithfully, which is worth failing over. The full report goes to diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index c740c28547f..062230f5f13 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -1,6 +1,4 @@ === app.yml.tmpl -budget_policy_id x absent UPDATE_IGNORED budget_policy_id -budget_policy_id y absent UPDATE_IGNORED budget_policy_id compute_max_instances 1 absent UPDATE_IGNORED compute_max_instances compute_max_instances 2 absent UPDATE_IGNORED compute_max_instances compute_min_instances 1 absent UPDATE_IGNORED compute_min_instances @@ -94,24 +92,6 @@ git_source.tag x absent START_NOT_REA git_source.tag x y START_NOT_REACHED git_source.tag git_source.tag y absent START_NOT_REACHED git_source.tag git_source.tag y x START_NOT_REACHED git_source.tag -resources absent len0 START_NOT_REACHED resources -resources absent len1 START_NOT_REACHED resources -resources len0 absent START_NOT_REACHED resources -resources len0 len1 START_NOT_REACHED resources -resources len1 absent UPDATE_IGNORED resources -resources len1 len0 UPDATE_IGNORED resources -resources[0].app.name x absent UPDATE_IGNORED resources[0].app.name -resources[0].app.name y absent UPDATE_IGNORED resources[0].app.name -resources[0].app.permission CAN_USE absent UPDATE_IGNORED resources[0].app.permission -resources[0].description absent x START_NOT_REACHED resources[0].description -resources[0].description absent y START_NOT_REACHED resources[0].description -resources[0].description x absent UPDATE_IGNORED resources[0].description -resources[0].description y absent UPDATE_IGNORED resources[0].description -resources[0].postgres.branch x absent UPDATE_IGNORED resources[0].postgres.branch -resources[0].postgres.branch y absent UPDATE_IGNORED resources[0].postgres.branch -resources[0].postgres.database x absent UPDATE_IGNORED resources[0].postgres.database -resources[0].postgres.database y absent UPDATE_IGNORED resources[0].postgres.database -resources[0].postgres.permission CAN_CONNECT_AND_CREATE absent UPDATE_IGNORED resources[0].postgres.permission source_code_path absent x-UNIQUE START_NOT_REACHED source_code_path source_code_path absent y-UNIQUE START_NOT_REACHED source_code_path source_code_path x-UNIQUE absent START_NOT_REACHED source_code_path @@ -121,19 +101,17 @@ source_code_path y-UNIQUE x-UNIQUE START_NOT_REA space x y START_NOT_REACHED space space y absent START_NOT_REACHED space space y x START_NOT_REACHED space -usage_policy_id x absent UPDATE_IGNORED usage_policy_id -usage_policy_id y absent UPDATE_IGNORED usage_policy_id user_api_scopes len1 absent UPDATE_IGNORED user_api_scopes user_api_scopes len1 len0 UPDATE_IGNORED user_api_scopes user_api_scopes len2 absent UPDATE_IGNORED user_api_scopes user_api_scopes len2 len0 UPDATE_IGNORED user_api_scopes === summary -OK 108 +OK 44 OK_RECREATE 2 SUPPRESSED 21 NOT_OBSERVABLE 3 -UPDATE_IGNORED 29 +UPDATE_IGNORED 13 UNSETTABLE 2 -SKIPPED 82 -START_NOT_REACHED 83 +SKIPPED 84 +START_NOT_REACHED 77 diff --git a/bundle/direct/tests/testdata/fields/apps.yml b/bundle/direct/tests/testdata/fields/apps.yml index ac424bd2063..826984874f1 100644 --- a/bundle/direct/tests/testdata/fields/apps.yml +++ b/bundle/direct/tests/testdata/fields/apps.yml @@ -1,4 +1,9 @@ # Value library for apps. See ../../README.md. +# +# resources is deliberately not seeded. An entry is a union (secret, job, sql_warehouse, ...) +# and the API rejects one carrying no member at all -- "Invalid resource provided" -- while +# every member names a workspace object this suite does not provision: a secret scope, a job, a +# warehouse. So the block's fields stay uncovered, which the report says. # The corpus config declares neither block. config carries the app's command and env; the # resources list is a union where an entry names one referenced object. @@ -10,13 +15,12 @@ base: env: - name: MY_VAR value: value - # An app resource is a union (secret, job, sql_warehouse, ...), and every member names a - # workspace object this suite does not provision -- a secret scope, a job, a warehouse. Only - # the entry's own fields are seeded, so name, description and the union members stay - # uncovered, which the report says. - resources: - - name: seeded - description: seeded resource fields: compute_size: [MEDIUM, LARGE] + +skip: + # Both name an account-level policy: "Invalid resource: accounts//budgetPolicies/x". A + # test workspace has none to reference, and creating one is an account-level operation. + budget_policy_id: needs an account-level budget policy to exist + usage_policy_id: needs an account-level usage policy to exist From ef2c5191b18575d9294aa2be3c46dcc16b4f834e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 17:32:17 +0200 Subject: [PATCH 056/118] structaccess: decide ForceSendFields from the value actually stored From a cross-model review. Set converted the caller's value on the way in but judged emptiness from the value it was handed, so an omitempty numeric or bool field set from a string that converts to zero was stored as zero and then left out of ForceSendFields -- and marshalled as absent. Setting max_concurrent_runs to "0" silently meant unset. Latent today, since no value library writes a number as a quoted string, but it contradicts the contract the field catalog's coerce relies on: a scalar is left to structaccess to convert between the numeric and string kinds. Co-authored-by: Isaac --- libs/structs/structaccess/set.go | 17 ++++++++++++++--- libs/structs/structaccess/set_test.go | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/libs/structs/structaccess/set.go b/libs/structs/structaccess/set.go index f46536c2727..89561ceca21 100644 --- a/libs/structs/structaccess/set.go +++ b/libs/structs/structaccess/set.go @@ -143,12 +143,23 @@ func setStructField(parentVal reflect.Value, fieldName string, valueVal reflect. // Assign first: a value that cannot be converted must leave the struct exactly as it was, // and updating ForceSendFields before the assignment left the field's send-behaviour // changed after a failed Set. - if err := assignValue(fv, valueVal); err != nil { + converted, err := convertValue(valueVal, fv.Type()) + if err != nil { + return err + } + if err := assignValue(fv, converted); err != nil { return err } - // Handle ForceSendFields: remove if setting nil, add if setting empty value - return updateForceSendFields(owner, sf.Name, valueVal, sf) + // ForceSendFields is decided from the converted value, not the caller's: setting an + // omitempty int64 from the string "0" stores 0, which is empty and must be forced, while + // the string "0" is not empty and would have left the field to be omitted. + if !valueVal.IsValid() { + // Setting nil: the field is being made absent, which convertValue renders as the zero + // value. Pass the invalid value through so it is removed from ForceSendFields. + return updateForceSendFields(owner, sf.Name, valueVal, sf) + } + return updateForceSendFields(owner, sf.Name, converted, sf) } // setMapValue sets a value in a map diff --git a/libs/structs/structaccess/set_test.go b/libs/structs/structaccess/set_test.go index b8ca1c3d448..652b3ba0641 100644 --- a/libs/structs/structaccess/set_test.go +++ b/libs/structs/structaccess/set_test.go @@ -942,3 +942,18 @@ func TestSet_DiamondEmbedIsAmbiguous(t *testing.T) { require.NoError(t, err) assert.JSONEq(t, `{}`, string(blob)) } + +// ForceSendFields is decided from the value actually stored, not the one the caller passed: +// setting an omitempty numeric field from the string "0" stores zero, which has to be forced +// or the field marshals as absent. +func TestSet_StringZeroIntoOmitemptyNumberIsForced(t *testing.T) { + job := &jobs.JobSettings{Name: "n"} //exhaustruct:ignore + + require.NoError(t, structaccess.SetByString(job, "max_concurrent_runs", "0")) + assert.Equal(t, 0, job.MaxConcurrentRuns) + assert.Contains(t, job.ForceSendFields, "MaxConcurrentRuns") + + blob, err := json.Marshal(job) + require.NoError(t, err) + assert.Contains(t, string(blob), `"max_concurrent_runs":0`) +} From c0938f87a3e863b26395c278f42a1ecfa70e5ea1 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 17:34:55 +0200 Subject: [PATCH 057/118] Field catalog: three latent fixes from a cross-model review - driftDetail put the node's action verb into the list of drifting field paths when a plan carried no per-field detail, and the caller then matched that verb against the field under test, failed, and reported COLLATERAL_DRIFT with detail "recreate" -- blaming a sibling that does not exist. A resource being replaced again is its own non-convergence, and is reported as POST_DEPLOY_DRIFT now. - The key class in the selector pattern only accepted lowercase and underscores, so a keyed slice whose key contains a digit or a capital would stop matching the suite's index form -- reintroducing exactly the mis-attribution the previous commit fixed. It accepts anything but the quote and bracket now. - transitions() shuffled the field's own values in place, and it is called twice per field: once to decide whether the field has anything to test, once to run it. The two calls walked different chains. Both now work on a copy. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 16 +++++++++++----- bundle/direct/tests/values_test.go | 10 +++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 2ee1739b77d..c51e5f74a36 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -275,7 +275,7 @@ func sameField(drifting, path string) bool { // selectorPattern matches the key-value selector the planner uses to name a slice element it // can identify -- "tasks[task_key='seeded']" for what this suite calls "tasks[0]". -var selectorPattern = regexp.MustCompile(`\[(?:[a-z_]+='[^']*'|[0-9]+)\]`) +var selectorPattern = regexp.MustCompile(`\[(?:[^'=\]]+='[^']*'|[0-9]+)\]`) // normalizeSelectors reduces both ways of naming a slice element to one form, so a change the // planner recorded against "tasks[task_key='seeded'].run_if" is recognised as the same field as @@ -542,7 +542,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans } } - if own, child := driftDetail(after, h.node, baseline); own != "" || child != "" { + if own, child, bare := driftDetail(after, h.node, baseline); own != "" || child != "" || bare != "" { // Whether the field under test is among the drifting ones decides who is to blame. drifted := strings.Split(own, ",") // The plan that still wants a change is the whole evidence for this verdict, so @@ -550,6 +550,9 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.evidence = withContext("plan taken right after the deploy:", planJSON(after)) t.Logf("post-deploy plan still proposes a change:\n%s", res.evidence) switch { + case bare != "": + // The whole resource is being replaced again, with no field named. + res.verdict, res.detail = verdictDrift, bare case own == "": // A child node left behind by a recreate: a different problem, different fix. res.verdict, res.detail = verdictDriftChild, child @@ -812,7 +815,7 @@ func jsonEqual(a, b any) bool { // on the node under test (own: the field paths that did not stick) and drift on any // other node (child: how a recreate orphaning a permissions or grants node shows up). // Both are "" when the deploy converged. -func driftDetail(plan *deployplan.Plan, node string, baseline map[string]bool) (own, child string) { +func driftDetail(plan *deployplan.Plan, node string, baseline map[string]bool) (own, child, bare string) { var ownPaths, childNodes []string for key, entry := range plan.Plan { if entry.Action == deployplan.Skip { @@ -831,12 +834,15 @@ func driftDetail(plan *deployplan.Plan, node string, baseline map[string]bool) ( } } if len(entry.Changes) == 0 { - ownPaths = append(ownPaths, string(entry.Action)) + // A recreate carries no per-field detail, so there is no path to name: the whole + // resource is being replaced again. Reported as the field's own non-convergence, + // since attributing it to a sibling would name an action where a field belongs. + bare = string(entry.Action) } } slices.Sort(ownPaths) slices.Sort(childNodes) - return strings.Join(ownPaths, ","), strings.Join(childNodes, " ") + return strings.Join(ownPaths, ","), strings.Join(childNodes, " "), bare } // generatedIDs are the shapes of value that differ between runs, all of which turn up inside diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index a35d6637f85..1b65daeb8de 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -339,9 +339,13 @@ func (t transition) label() string { // fixed for a given field, so the report stays byte-stable, but different between fields, // so the suite is not always walking the same shape of path. func (f field) transitions() []transition { - values := f.values - if !f.required { - values = append([]any{absent}, values...) + // A fresh slice either way, since the shuffle below is in place: transitions() is called + // twice per field -- once to decide whether the field has anything to test, once to run it + // -- and shuffling the field's own values would make the second call walk a different chain + // than the first agreed to. + values := append([]any{absent}, f.values...) + if f.required { + values = slices.Clone(f.values) } if len(values) < 2 { return nil From e110a90d398b256ea6b1810d2163f00a0bede60a Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 17:54:36 +0200 Subject: [PATCH 058/118] Field catalog: give the pipeline reference fields real reasons All 31 pipeline fields whose value looks like it must name something real were driven against AWS on their own, and every one was a fake-server artifact: a budget policy, an instance pool, a cluster policy, a KMS key, a UC connection and an ingestion gateway are all objects a test workspace does not have, so the fake server accepting any string made them read OK. A root path is a workspace path and a time zone is a TZ database id, neither free text. run_as depends on the identity the suite runs as, and a cron trigger is validated as a pair with its expression, which this config does not declare. None of those 31 rows diverge from AWS now. Co-authored-by: Isaac --- bundle/direct/tests/output/pipelines.txt | 6 ++--- .../tests/testdata/fields/pipelines.yml | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 8af082e3aef..c9627cddd14 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -9,11 +9,11 @@ dry_run true absent START_NOT_REA dry_run true false START_NOT_REACHED dry_run === summary -OK 807 -OK_RECREATE 33 +OK 741 +OK_RECREATE 9 OK_INERT 8 SUPPRESSED 45 NOT_OBSERVABLE 29 UPDATE_IGNORED 2 -SKIPPED 2 +SKIPPED 12 START_NOT_REACHED 6 diff --git a/bundle/direct/tests/testdata/fields/pipelines.yml b/bundle/direct/tests/testdata/fields/pipelines.yml index 68983ee6785..2a66b507fda 100644 --- a/bundle/direct/tests/testdata/fields/pipelines.yml +++ b/bundle/direct/tests/testdata/fields/pipelines.yml @@ -45,8 +45,32 @@ base: parameters: table: users +skip: + # Each names a workspace or account object this suite does not provision: an account-level + # budget policy, an instance pool, a cluster policy, a KMS key, a UC connection, an ingestion + # gateway pipeline. + budget_policy_id: needs an account-level budget policy to exist + clusters[*].instance_pool_id: needs a second instance pool to exist + clusters[*].driver_instance_pool_id: needs a second instance pool to exist + clusters[*].policy_id: needs a cluster policy to exist + clusters[*].init_scripts[*].s3.kms_key: needs a KMS key registered for the workspace + gateway_definition: needs a UC connection to a source database + ingestion_definition.connection_name: needs a UC connection to a source database + ingestion_definition.ingestion_gateway_id: needs an ingestion gateway pipeline to exist + # run_as must name a principal allowed to run the pipeline, which depends on the identity the + # suite runs as -- a service principal on cloud. + run_as: depends on the identity the suite runs as + # A cron trigger is validated as a pair: "Invalid cron expression '' or timezone ...", so the + # timezone cannot move without an expression beside it. The config declares no trigger, so + # there is none to move it with. + trigger.cron: validated as a pair with the cron expression, which the config does not declare + fields: # Constrained by the backend but typed as a plain string in the SDK, so nothing derives their # values: the generic "x" is rejected on a real workspace. channel: [CURRENT, PREVIEW] + + # A root path is a workspace path, and a time zone is a TZ database id, not free text. + root_path: [/Shared/pipeline-a, /Shared/pipeline-b] + restart_window.time_zone_id: [UTC, America/New_York] edition: [CORE, PRO] From 196364eafe66b672c6b6df9f16a3c5d04c130048 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 17:58:14 +0200 Subject: [PATCH 059/118] Field catalog: unit-test the value helpers Three helpers with real logic had no direct test, only whatever the goldens happened to exercise: the SDK enum reflection (including that a *_UNSPECIFIED member is never offered), the deep copy behind a container's values, and the label shortening. The container test is the one worth having: it edits the live resource through a pointer inside an element and asserts neither recorded value moved, which is the bug an earlier round shipped. Co-authored-by: Isaac --- bundle/direct/tests/values_unit_test.go | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/tests/values_unit_test.go index 5ab66c8f1a5..491dcf45fec 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/tests/values_unit_test.go @@ -1,12 +1,15 @@ package tests import ( + "fmt" + "reflect" "testing" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/databricks/databricks-sdk-go/service/sql" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -220,3 +223,67 @@ func TestSameField(t *testing.T) { assert.Equal(t, tc.want, sameField(tc.drifting, tc.path), "%s vs %s", tc.drifting, tc.path) } } + +// An SDK enum declares its own values, and the *_UNSPECIFIED member is a protobuf sentinel for +// "unset" rather than a choice: asking for it reads as the write being ignored, since the +// backend normalizes it away. +func TestEnumValues(t *testing.T) { + // AlertOperator has no UNSPECIFIED member, so the two alphabetically-first values come back. + got := enumValues(reflect.TypeFor[sql.AlertOperator]()) + require.Len(t, got, 2) + for _, value := range got { + assert.IsType(t, sql.AlertOperator(""), value) + assert.NotContains(t, value, "UNSPECIFIED") + } + + // SpotInstancePolicy does have one, and it must not be offered. + got = enumValues(reflect.TypeFor[sql.SpotInstancePolicy]()) + require.NotEmpty(t, got) + for _, value := range got { + assert.NotContains(t, fmt.Sprint(value), "UNSPECIFIED") + } + + // A plain string is not an enum, whatever the backend constrains it to. + assert.Nil(t, enumValues(reflect.TypeFor[string]())) +} + +// A container's values are the resource's own and that value with one entry dropped, both deep +// copies -- a shallow one would share the pointers inside an element with the live resource, so +// a later edit would reach into a value recorded here. +func TestContainerValuesAreIndependent(t *testing.T) { + //exhaustruct:ignore + job := &resources.Job{JobSettings: jobs.JobSettings{ + Tasks: []jobs.Task{ + {TaskKey: "a", NotebookTask: &jobs.NotebookTask{NotebookPath: "one"}}, //exhaustruct:ignore + {TaskKey: "b"}, //exhaustruct:ignore + }, + }} + + values := containerValues(job, "tasks") + require.Len(t, values, 2) + full := values[0].([]jobs.Task) + trimmed := values[1].([]jobs.Task) + require.Len(t, full, 2) + require.Len(t, trimmed, 1) + + // Editing the live resource through the pointer inside an element must not be visible in + // either recorded value. + require.NoError(t, setValue(job, "tasks[0].notebook_task.notebook_path", "two")) + assert.Equal(t, "one", full[0].NotebookTask.NotebookPath) + assert.Equal(t, "one", trimmed[0].NotebookTask.NotebookPath) +} + +// A value long enough or punctuated enough to be unreadable as a subtest name gets a trimmed, +// digest-suffixed label -- stable, and safe to paste back into a test filter. +func TestShortLabel(t *testing.T) { + assert.Equal(t, "x", shortLabel("x")) + assert.Equal(t, "dbfs:/FileStore/a.jar", shortLabel("dbfs:/FileStore/a.jar")) + + long := shortLabel(`{"spark_version":{"type":"fixed","value":"13.3"}}`) + assert.NotContains(t, long, `"`) + assert.LessOrEqual(t, len(long), 24) + // Two values sharing a prefix still get different labels. + assert.NotEqual(t, long, shortLabel(`{"spark_version":{"type":"fixed","value":"14.3"}}`)) + // And the same value always gets the same one. + assert.Equal(t, long, shortLabel(`{"spark_version":{"type":"fixed","value":"13.3"}}`)) +} From 6cb21c9e8947e42b9d7e6cfddad732438ba022b3 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 18:08:55 +0200 Subject: [PATCH 060/118] Field catalog: say what an OK from the fake server alone is worth The reference-field check the README describes cannot be run for a local_only type, whose report is a record of what the fake server does rather than what a workspace does. external_locations has 62 rows of exactly the shape that check exists to catch, all in blocks naming cloud storage this suite does not provision. Worth stating plainly next to the reports rather than leaving a reader to assume every OK carries the same weight. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index e1e959b9298..c2f4b0cd76d 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -131,6 +131,13 @@ say so — a pipeline's `channel` and `edition` are plain strings the backend va cluster size is a t-shirt size. The AWS run is what finds these: locally the fake server takes anything. +An `OK` that has only ever been seen against the fake server is worth less than one a +workspace has agreed to, and most of all for a field whose value must name something real. The +fake server takes any string, so for such a field `OK` says nothing until it has been checked +against a workspace — see above. That check is impossible for a `local_only` type, whose report +is a record of what the fake server does: `external_locations` has 62 such rows, all in blocks +naming cloud storage the suite does not provision. + A type that cannot run against a real workspace at all declares `local_only: ` and is skipped on cloud — an external location needs a storage credential with cloud IAM behind it, and Lakebase and Postgres resources are not available in every cloud. This mirrors the From 166bdce7a281408f1e75ff73d01d96b3d4d96be2 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 18:12:50 +0200 Subject: [PATCH 061/118] Field catalog: record the known cloud divergences and the workspace limits Three rows differ from a real workspace and are left as findings, since modelling each needs a behavioural change the acceptance suite asserts otherwise today. And two types cannot be driven against the workspace this was verified on for reasons that are its capacity, not the engine's behaviour -- worth writing down so the next person does not re-derive them, and so the choice not to mark clusters local_only is visible. Co-authored-by: Isaac --- bundle/direct/tests/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/bundle/direct/tests/README.md b/bundle/direct/tests/README.md index c2f4b0cd76d..5fd84e5e7f0 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/tests/README.md @@ -208,3 +208,21 @@ faithfully, which is worth failing over. The full report goes to only in those blocks therefore collapse to one, recorded in `output/configs.txt`. - configs with more than one resource, or with an `-init.sh` - fields under a slice or map, listed at the end of each report + +Three divergences from a real workspace are known and left as findings rather than modelled, +because each needs a behavioural change the acceptance suite currently asserts otherwise: + +- A SQL warehouse reads back `STARTING`, not `RUNNING`, right after create, so a config asking + for `started: false` is already satisfied there. Modelling it means the fake server also has + to move the warehouse to `RUNNING` on a later read, which the engine's waiter depends on. +- A model serving endpoint's `email_notifications` do not take effect from a create; they are + configured through their own endpoint. +- A Genie space's title cannot be cleared, and the chain then reaches a different value than it + does locally in one transition. + +Two types cannot be driven against the workspace this was verified on, for reasons that are its +capacity rather than the engine's behaviour: a cluster never reaches `RUNNING` because the +instance pool `$TEST_INSTANCE_POOL_ID` names cannot provision instances, and an instance pool +cannot be deleted (which is why that type is `local_only` — every field that recreates hits it). +`clusters` is deliberately *not* marked `local_only`: one workspace's capacity is not a property +of the suite. From 1aad2a7ccebe976596e0a57c62c89de195772ff6 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 18:24:33 +0200 Subject: [PATCH 062/118] structaccess: ambiguity is a property of the type, not of the value From the review: two embedded pointers declaring one json name at the same depth make it a name encoding/json omits, but the value walk skips a nil embed, so whether the name resolved depended on which pointers happened to be set. Get and Set could reach a field ValidatePattern rejects. The value walk now asks the type walk first, so all three agree on which names exist. Co-authored-by: Isaac --- libs/structs/structaccess/get.go | 10 ++++++++++ libs/structs/structaccess/set_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/libs/structs/structaccess/get.go b/libs/structs/structaccess/get.go index 30513a8f153..b62a1dcb490 100644 --- a/libs/structs/structaccess/get.go +++ b/libs/structs/structaccess/get.go @@ -269,11 +269,21 @@ func findFieldInStruct(v reflect.Value, key string) (reflect.Value, reflect.Stru // the SDK request struct, so its fields sit two levels down. // Returns: fieldValue, structField, owner (the struct value declaring the field), found func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.StructField, reflect.Value, bool) { + t := v.Type() + // First pass: direct fields if fv, sf, found := findFieldInStruct(v, key); found { return fv, sf, v, true } + // Ambiguity is a property of the type, not of the value: a name declared at the same + // embedding depth by two members is one encoding/json omits, whether or not one of those + // members happens to be a nil pointer right now. Asking the type walk first keeps Get, Set + // and ValidatePattern agreeing on which names exist. + if _, _, ok := FindStructFieldByKeyType(t, key); !ok { + return reflect.Value{}, reflect.StructField{}, reflect.Value{}, false + } + // Second pass: search embedded anonymous structs (flattening semantics) breadth-first, one // level of embedding at a time. Not depth-first: encoding/json resolves a name declared at // two embedding depths in favour of the shallower one, so descending fully into the first diff --git a/libs/structs/structaccess/set_test.go b/libs/structs/structaccess/set_test.go index 652b3ba0641..05a852b0f77 100644 --- a/libs/structs/structaccess/set_test.go +++ b/libs/structs/structaccess/set_test.go @@ -957,3 +957,28 @@ func TestSet_StringZeroIntoOmitemptyNumberIsForced(t *testing.T) { require.NoError(t, err) assert.Contains(t, string(blob), `"max_concurrent_runs":0`) } + +// Whether a name is ambiguous is a property of the type: two embedded pointers declaring it at +// the same depth make it one encoding/json omits, and that must not change with whether one of +// them happens to be nil right now. +type ambiguousPtrEmbeds struct { + *ambiguousA + *ambiguousB //nolint:govet // the repeated json tag is the point: both embeds declare "value" +} + +func TestSet_AmbiguousPointerEmbedsIgnoreNilness(t *testing.T) { + // One embed present, the other nil: the name is still ambiguous. + target := &ambiguousPtrEmbeds{ambiguousA: &ambiguousA{}} //exhaustruct:ignore + require.Error(t, structaccess.SetByString(target, "value", "set")) + _, err := structaccess.GetByString(target, "value") + require.Error(t, err) + + // And with both present, unchanged. + target = &ambiguousPtrEmbeds{ambiguousA: &ambiguousA{}, ambiguousB: &ambiguousB{}} + require.Error(t, structaccess.SetByString(target, "value", "set")) + require.Error(t, structaccess.ValidateByString(reflect.TypeOf(target), "value")) + + blob, err := json.Marshal(target) + require.NoError(t, err) + assert.JSONEq(t, `{}`, string(blob)) +} From f71e715e7ece4eeee58981d2d03a4fe91d9742b5 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 18:26:01 +0200 Subject: [PATCH 063/118] Field catalog: three fixes from the tenth review round - The starting-value check treated a parent's baseline drift as an excuse for the child, so a setup write to "config.foo" that the backend ignored went unnoticed while "config" was already drifting. It compares against the key the plan resolved the field to, the same way the ignored-write guard does, and the relation-matching helper is gone. - A baseline measurement that fails after a rebuild or a recreate is recorded as a PLAN_ERROR rather than only logged: every verdict after it is measured against a baseline belonging to a resource that no longer exists, and a reader of the report has to know that. - A container the config declares empty was reported as having nothing to test. The empty value is a value in its own right, so with absent in the set it still covers adding the container and taking it away. Also: an UpgradeToWrite failure left the write-ahead log behind, and every later operation then failed with an unexpected-WAL error instead of the real one. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 37 ++++++++++------------------- bundle/direct/tests/harness_test.go | 3 +++ bundle/direct/tests/values_test.go | 6 +++-- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index c51e5f74a36..54e8547e04b 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -209,7 +209,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl res.evidence = err.Error() broken = err } else { - h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline) + h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) res = runTransition(t, h, cfg.name, f.path, tr, false, baseline) } } @@ -223,7 +223,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl if res.verdict == verdictRecreate { // A recreate replaced the resource without going through rebuild, so the // baseline belongs to one that no longer exists. - baseline = remeasure(h, baseline) + baseline = remeasure(h, baseline, cfg.name, rep) } if broken != nil || res.verdict.leavesResourceUsable() { @@ -237,7 +237,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl broken = err return } - h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline) + h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) }) } }) @@ -257,7 +257,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl rep.add(result{cfg.name, f.path, "", "", verdictBaseError, oneLine(err.Error()), err.Error()}) return } - h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline) + h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) } } } @@ -367,19 +367,22 @@ func hasFieldChanges(plan *deployplan.Plan, node string) bool { // A plan that fails here leaves the previous measurement in place, which is closer than // nothing, and says so: every verdict after it is measured against a baseline that may not be // this resource's. -func remeasure(h *bundleHarness, previous map[string]bool) map[string]bool { +func remeasure(h *bundleHarness, previous map[string]bool, config string, rep *report) map[string]bool { measured, err := baselineDrift(h) if err == nil { return measured } - h.t.Logf("could not measure the new resource's baseline drift, keeping the previous one: %s", err) + // Recorded, not just logged: every verdict after this is measured against a baseline that + // belongs to a resource which no longer exists, and a reader of the report has to know. + rep.add(result{config, "(baseline)", "", "", verdictPlanError, oneLine(err.Error()), err.Error()}) return previous } // baselineCovers reports whether the drift this field would be blamed for is drift that was // already there. The comparison is against the key the plan resolved the field to, not the leaf -// under test: existing drift at "config" would otherwise be reported as an ignored write to -// "config.foo". +// under test and not any relation of it: existing drift at "config" explains a change recorded +// against "config", but says nothing about whether a write to "config.foo" landed, and treating +// it as an excuse would let an ignored write pass unnoticed. func baselineCovers(baseline map[string]bool, plan *deployplan.Plan, node, path string) bool { if baseline[path] { return true @@ -388,22 +391,6 @@ func baselineCovers(baseline map[string]bool, plan *deployplan.Plan, node, path return ok && baseline[key] } -// baselineDrifts reports whether the field was already drifting before anything was changed, -// which is what makes it unable to reach a starting value through no fault of the transition. -// Matched by relation: the baseline may be recorded against an ancestor -- a whole "config" -// block -- and treating that as this field's failure would blame the ancestor's drift on every -// field beneath it. -// -// The post-deploy drift filter uses an exact comparison instead; see driftDetail. -func baselineDrifts(baseline map[string]bool, path string) bool { - for drifting := range baseline { - if sameField(drifting, path) { - return true - } - } - return false -} - // converged reports whether a plan no longer wants to change the field, at whatever // granularity the planner recorded it. func converged(plan *deployplan.Plan, node, path string) bool { @@ -450,7 +437,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.detail = firstError(diags) return res } - if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change) && !baselineDrifts(baseline, path) { + if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change) && !baselineCovers(baseline, plan, h.node, path) { res.verdict = verdictStartNotReached res.detail = path res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index b5df8fa8a24..c708fd2d209 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -344,6 +344,9 @@ func (h *bundleHarness) apply(p *pendingApply, plan *deployplan.Plan) diag.Diagn defer p.cancel() ctx, db := p.ctx, p.db if err := db.StateDB.UpgradeToWrite(); err != nil { + // Finalize even here: the upgrade can fail with a WAL already created, and leaving it + // behind makes every later operation fail with an unexpected-WAL error instead. + _, _ = db.StateDB.Finalize(ctx) return diag.FromErr(err) } if err := db.InitForApply(ctx, h.client, plan); err != nil { diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index 1b65daeb8de..bcd0a6f4839 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -666,14 +666,16 @@ func containerValues(resource any, path string) []any { switch value.Kind() { case reflect.Slice: if value.Len() == 0 { - return nil + // The config declares it empty, which is a value in its own right: with absent in the + // set that still covers adding the empty container and taking it away. + return []any{full} } trimmed = value.Slice(0, value.Len()-1).Interface() case reflect.Map: keys := sortedMapKeys(value) if len(keys) == 0 { - return nil + return []any{full} } value.SetMapIndex(keys[len(keys)-1], reflect.Value{}) trimmed = value.Interface() From 41c89a86563d8d79461ca7f33e1fd692d8b8d259 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 18:44:30 +0200 Subject: [PATCH 064/118] Field catalog: stop testing jobs configurations no user can write Driving the 126 jobs fields whose value looks like it must name something real against AWS: 119 of them diverged, and the dominant cause was not the values but the shape. A task's type is a union and the base seeds notebook_task, so setting any other member gave the task two types -- which the API rejects, usually by complaining about the cluster the seeded task carries ("For each does not need a cluster"). The fake server accepted it, so about 2200 transitions were exercising configurations no user can write, and reporting OK for them. Those members are skipped now, along with the other two unions a task has (its compute, and its environment) and the fields naming a workspace object the suite does not provision: a serverless compute, a saved query, a warehouse, a registered model, a job whose config lives in a repository. A repository URL and a time zone get real values. The cost is real and stated in the report: jobs drops from 3117 observations to 909, and the skipped members are listed as uncovered. Covering one needs a corpus config whose task is of that type -- some exist, but this suite drives the simplest config per resource type, so they are not the one picked. Trading 2200 rubber-stamped rows for 83 honest gaps. Co-authored-by: Isaac --- bundle/direct/tests/output/jobs.txt | 11 ++--- bundle/direct/tests/testdata/fields/jobs.yml | 50 ++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index 7ecac86a01c..296416abb34 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -3,12 +3,11 @@ deployment.version_id x absent START_NOT_REA deployment.version_id x y START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id y x START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) -tasks[0].for_each_task.task.new_cluster.apply_policy_default_values true absent START_NOT_REACHED tasks[0].for_each_task.task.new_cluster.apply_policy_default_values === summary -OK 2857 +OK 777 OK_INERT 2 -SUPPRESSED 150 -NOT_OBSERVABLE 100 -SKIPPED 3 -START_NOT_REACHED 5 +SUPPRESSED 53 +NOT_OBSERVABLE 40 +SKIPPED 33 +START_NOT_REACHED 4 diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml index 7172db98e58..7e626f27833 100644 --- a/bundle/direct/tests/testdata/fields/jobs.yml +++ b/bundle/direct/tests/testdata/fields/jobs.yml @@ -66,10 +66,60 @@ skip: git_source.git_branch: mutually exclusive with the seeded git_commit git_source.git_tag: mutually exclusive with the seeded git_commit + # A task's type is a union and base seeds notebook_task, so reaching any other member means + # putting two types on one task -- which the API rejects, usually by complaining about the + # cluster the seeded task carries ("For each does not need a cluster"). Locally the fake server + # accepted it, so ~2200 transitions were exercising configurations no user can write. + # + # Covering a member needs a corpus config whose task is of that type. Some exist -- + # job_apply_policy_default_values_for_each_task.yml.tmpl is one -- but this suite drives the + # simplest config per resource type, so they are not the one picked. Their fields are listed as + # uncovered rather than tested against a task that cannot hold them. + tasks[*].ai_runtime_task: another member of the same union as the seeded notebook_task + tasks[*].alert_task: another member of the same union as the seeded notebook_task + tasks[*].clean_rooms_notebook_task: another member of the same union as the seeded notebook_task + tasks[*].condition_task: another member of the same union as the seeded notebook_task + tasks[*].dashboard_task: another member of the same union as the seeded notebook_task + tasks[*].dbt_cloud_task: another member of the same union as the seeded notebook_task + tasks[*].dbt_platform_task: another member of the same union as the seeded notebook_task + tasks[*].dbt_task: another member of the same union as the seeded notebook_task + tasks[*].for_each_task: another member of the same union as the seeded notebook_task + tasks[*].gen_ai_compute_task: another member of the same union as the seeded notebook_task + tasks[*].pipeline_task: another member of the same union as the seeded notebook_task + tasks[*].power_bi_task: another member of the same union as the seeded notebook_task + tasks[*].python_wheel_task: another member of the same union as the seeded notebook_task + tasks[*].run_job_task: another member of the same union as the seeded notebook_task + tasks[*].spark_jar_task: another member of the same union as the seeded notebook_task + tasks[*].spark_python_task: another member of the same union as the seeded notebook_task + tasks[*].spark_submit_task: another member of the same union as the seeded notebook_task + tasks[*].sql_task: another member of the same union as the seeded notebook_task + + # A task's compute is a union too, and base seeds job_cluster_key. + tasks[*].existing_cluster_id: another way to name the task's compute than the seeded job_cluster_key + tasks[*].new_cluster: another way to name the task's compute than the seeded job_cluster_key + tasks[*].environment_key: only for a task type that runs in a serverless environment + + # Each names a workspace object this suite does not provision. + job_clusters[*].serverless_compute_id: needs a serverless compute to exist + schedule.sql_condition: needs a saved query and a warehouse to exist + trigger.sql_condition: needs a saved query and a warehouse to exist + trigger.model.securable_name: needs a registered model to exist + git_source.job_source: needs a job whose configuration lives in the repository + # run_as must name a principal allowed to run the job, which depends on the identity the + # suite runs as -- a service principal on cloud. + run_as: depends on the identity the suite runs as + # Only one of the three cloud attribute blocks applies, decided by the workspace's cloud. + job_clusters[*].new_cluster.azure_attributes: cloud-specific; only one attribute block applies + job_clusters[*].new_cluster.gcp_attributes: cloud-specific; only one attribute block applies + job_clusters[*].new_cluster.aws_attributes: cloud-specific; only one attribute block applies + fields: format: [SINGLE_TASK, MULTI_TASK] performance_target: [PERFORMANCE_OPTIMIZED, STANDARD] max_concurrent_runs: [1, 5] timeout_seconds: [600, 1200] git_source.git_provider: [gitHub, gitLab] + # A repository URL and a TZ database zone id, neither free text. + git_source.git_url: [https://github.com/databricks/cli.test, https://github.com/databricks/dabs.test] + schedule.timezone_id: [UTC, America/New_York] git_source.git_commit: [abc123, def456] From 9b4cb6994e841ce344467ce076a2c559620cbce5 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 18:54:27 +0200 Subject: [PATCH 065/118] Field catalog: give the last jobs reference fields reasons The second AWS pass over those 126 fields cut the divergence from 119 to 13, all of the same two shapes as everywhere else. A cluster policy, an instance pool, a KMS key, a second warehouse and an account-level usage policy are objects a test workspace does not have. A node type is cloud-specific and the suite has one variable for it, so there is no second value to move to. A job cluster key has to name a job_clusters entry, and this suite moves one field at a time, so the key on the task and the key on the definition cannot change together. A schedule is one of three arms of a choice the corpus config makes none of. And a job cluster's name is assigned by the backend. Co-authored-by: Isaac --- bundle/direct/tests/output/jobs.txt | 4 ++-- bundle/direct/tests/testdata/fields/jobs.yml | 23 +++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index 296416abb34..803c4a6ae3e 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -5,9 +5,9 @@ deployment.version_id y absent START_NOT_REA deployment.version_id y x START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) === summary -OK 777 +OK 699 OK_INERT 2 SUPPRESSED 53 NOT_OBSERVABLE 40 -SKIPPED 33 +SKIPPED 46 START_NOT_REACHED 4 diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/tests/testdata/fields/jobs.yml index 7e626f27833..4cab38590e3 100644 --- a/bundle/direct/tests/testdata/fields/jobs.yml +++ b/bundle/direct/tests/testdata/fields/jobs.yml @@ -108,6 +108,28 @@ skip: # run_as must name a principal allowed to run the job, which depends on the identity the # suite runs as -- a service principal on cloud. run_as: depends on the identity the suite runs as + # A job cluster key has to name an entry in job_clusters, and this suite drives one field at a + # time -- so renaming the key on the task and on the definition cannot happen together. + tasks[*].job_cluster_key: has to name a job_clusters entry, which cannot change with it + job_clusters[*].job_cluster_key: has to match the key the task names, which cannot change with it + # A node type is cloud-specific and the suite has one variable for it, so there is no second + # value to move to: "Node type x is not supported. Supported node types: r3.xlarge, ...". + job_clusters[*].new_cluster.node_type_id: cloud-specific; the suite has one node type + job_clusters[*].new_cluster.driver_node_type_id: cloud-specific; the suite has one node type + # An account-level policy, as with apps. + usage_policy_id: needs an account-level usage policy to exist + # The backend assigns a job cluster its name: "Cluster name should not be provided". + job_clusters[*].new_cluster.cluster_name: assigned by the backend for a job cluster + # Each names a workspace object this suite does not provision. + job_clusters[*].new_cluster.policy_id: needs a cluster policy to exist + job_clusters[*].new_cluster.instance_pool_id: needs an instance pool to exist + job_clusters[*].new_cluster.driver_instance_pool_id: needs an instance pool to exist + job_clusters[*].new_cluster.init_scripts[*].s3.kms_key: needs a KMS key registered for the workspace + job_clusters[*].new_cluster.cluster_log_conf.s3.kms_key: needs a KMS key registered for the workspace + tasks[*].notebook_task.warehouse_id: needs a second SQL warehouse to exist + # A trigger, a schedule and a continuous block are three arms of one choice, so a schedule + # field cannot move on a job the corpus config gives no schedule. + schedule: one of schedule, trigger or continuous, and the config declares none # Only one of the three cloud attribute blocks applies, decided by the workspace's cloud. job_clusters[*].new_cluster.azure_attributes: cloud-specific; only one attribute block applies job_clusters[*].new_cluster.gcp_attributes: cloud-specific; only one attribute block applies @@ -121,5 +143,4 @@ fields: git_source.git_provider: [gitHub, gitLab] # A repository URL and a TZ database zone id, neither free text. git_source.git_url: [https://github.com/databricks/cli.test, https://github.com/databricks/dabs.test] - schedule.timezone_id: [UTC, America/New_York] git_source.git_commit: [abc123, def456] From 6102255a23b8c4ff44de613fed1df38f30a7ec9e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 19:06:52 +0200 Subject: [PATCH 066/118] testserver: a job's parent folder is fixed at create The last divergence in the jobs sweep, and a real engine finding rather than a value problem: a job's parent_path cannot be changed. A reset naming a different folder is accepted and ignored, and a job with no folder omits the field on read rather than reporting it empty. The engine plans an update for it anyway, so a config that changes it never converges. The fake server applied the change, so the local report called it OK. It now behaves the way run_as already does here -- the same shape of quirk, verified the same way -- and the finding is recorded in output/jobs.txt on both sides: UPDATE_IGNORED setting it, START_NOT_REACHED for every move that would have to start from a value it will not take. Co-authored-by: Isaac --- bundle/direct/tests/output/jobs.txt | 11 +++++++++-- libs/testserver/jobs.go | 13 +++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index 803c4a6ae3e..176286becd5 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -3,11 +3,18 @@ deployment.version_id x absent START_NOT_REA deployment.version_id x y START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) deployment.version_id y x START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) +parent_path absent x UPDATE_IGNORED parent_path +parent_path absent y UPDATE_IGNORED parent_path +parent_path x absent START_NOT_REACHED parent_path +parent_path x y START_NOT_REACHED parent_path +parent_path y absent START_NOT_REACHED parent_path +parent_path y x START_NOT_REACHED parent_path === summary -OK 699 +OK 693 OK_INERT 2 SUPPRESSED 53 NOT_OBSERVABLE 40 +UPDATE_IGNORED 2 SKIPPED 46 -START_NOT_REACHED 4 +START_NOT_REACHED 8 diff --git a/libs/testserver/jobs.go b/libs/testserver/jobs.go index 796e7f890a3..232f20cee6a 100644 --- a/libs/testserver/jobs.go +++ b/libs/testserver/jobs.go @@ -146,6 +146,19 @@ func (s *FakeWorkspace) JobsReset(req Request) Response { request.NewSettings.RunAs = prevjob.Settings.RunAs } + // parent_path is the same shape and verified the same way: the folder a job lives in is + // fixed when the job is created, and a reset naming a different one is accepted and + // ignored. The engine plans an update for it anyway, which is why a config that changes it + // never converges -- recorded in bundle/direct/tests/output/jobs.txt. + request.NewSettings.ParentPath = prevjob.Settings.ParentPath + if request.NewSettings.ParentPath == "" { + // And a job with no parent folder omits the field on read rather than reporting it + // empty, so drop any ForceSendFields entry the request carried for it. + request.NewSettings.ForceSendFields = slices.DeleteFunc(request.NewSettings.ForceSendFields, func(name string) bool { + return name == "ParentPath" + }) + } + s.Jobs[jobId] = jobs.Job{ JobId: jobId, CreatorUserName: prevjob.CreatorUserName, From 4178686e6d83e53db451f99cbd5c9961d1873ff8 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 19:31:48 +0200 Subject: [PATCH 067/118] Field catalog: create the resource holding the starting value A transition whose starting value the API will not move a field to was recorded as START_NOT_REACHED after one retry on a fresh resource -- but that resource was built from the base config, which seeds the same value again, so the retry could not help. A schema's properties was the clearest case: clearing the map is ignored, so nothing could ever start from empty, and six of its twelve rows said only that. The retry now writes the starting value into the config before the first deploy, so the create carries it. Creating a schema with no properties works, and those six rows became real observations: four of them OK, which is the answer to whether properties can be set from nothing. Across the catalog this turns 113 of the 324 START_NOT_REACHED rows into observed transitions. The 211 that remain are ones where even a resource created holding the value did not end up with it. schemas matches AWS row for row. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 26 +++++-- bundle/direct/tests/output/apps.txt | 15 ++-- bundle/direct/tests/output/catalogs.txt | 11 +-- bundle/direct/tests/output/dashboards.txt | 6 +- .../tests/output/database_instances.txt | 71 +++++++++---------- bundle/direct/tests/output/genie_spaces.txt | 5 +- bundle/direct/tests/output/instance_pools.txt | 9 +-- bundle/direct/tests/output/jobs.txt | 17 ++--- .../tests/output/model_serving_endpoints.txt | 8 +-- bundle/direct/tests/output/models.txt | 19 +---- bundle/direct/tests/output/pipelines.txt | 16 ++--- .../direct/tests/output/registered_models.txt | 17 +---- bundle/direct/tests/output/schemas.txt | 11 +-- bundle/direct/tests/output/sql_warehouses.txt | 19 +---- 14 files changed, 94 insertions(+), 156 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 54e8547e04b..ce1aead8411 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -199,7 +199,10 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // created without the field genuinely starts absent. Rebuild and try the // transition once more, so it is observed rather than written off. if res.verdict == verdictStartNotReached { - rebuilt, err := rebuild(owner, ctx, client, user, cfg, fv, h) + // With the starting value written into the config before the create, so + // the new resource is built holding it: a field the API will not move to + // a value can still be created with it. + rebuilt, err := rebuild(owner, ctx, client, user, cfg, fv, h, preset{f.path, tr.from}) if err != nil { // Nothing was observed and there is no resource left to observe // it on, which is a different statement from the field refusing @@ -564,13 +567,22 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // config -- not the subtest that happened to ask for the rebuild. Registering the cleanup on // the subtest would destroy the resource as soon as that transition ended, and every // transition after it would silently be creating a new one. -func newBaseline(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues) (*bundleHarness, error) { +func newBaseline(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, presets ...preset) (*bundleHarness, error) { h, err := newHarness(owner, ctx, client, user, cfg.name, uniqueName(), fv.base) if err != nil { return nil, err } owner.Cleanup(func() { _ = h.destroy() }) + // Applied before the first deploy, so the create carries the value rather than an update + // having to reach it. That is the only way to start from a value the API will not move a + // field to -- most often absent, for a field it refuses to clear. + for _, p := range presets { + if err := h.setField(p.path, p.value); err != nil { + return nil, fmt.Errorf("presetting %s: %w", p.path, err) + } + } + action, diags := h.deploy() if diags.HasError() { // The whole diagnostics, not firstError's one-line form: the caller records this as @@ -583,12 +595,18 @@ func newBaseline(owner *testing.T, ctx context.Context, client *databricks.Works return h, nil } +// preset is a field value to write into the config before the resource is first deployed. +type preset struct { + path string + value any +} + // rebuild starts over on a fresh resource, leaving the old one to be destroyed at the // end of the test. A new name is what makes this cheap: reusing the old one can mean // waiting out an asynchronous delete. -func rebuild(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, old *bundleHarness) (*bundleHarness, error) { +func rebuild(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, old *bundleHarness, presets ...preset) (*bundleHarness, error) { owner.Cleanup(func() { _ = old.destroy() }) - return newBaseline(owner, ctx, client, user, cfg, fv) + return newBaseline(owner, ctx, client, user, cfg, fv, presets...) } // explainSkip says why a plan came back with nothing to do for the field under test. diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index 062230f5f13..bccbf620725 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -92,26 +92,25 @@ git_source.tag x absent START_NOT_REA git_source.tag x y START_NOT_REACHED git_source.tag git_source.tag y absent START_NOT_REACHED git_source.tag git_source.tag y x START_NOT_REACHED git_source.tag -source_code_path absent x-UNIQUE START_NOT_REACHED source_code_path -source_code_path absent y-UNIQUE START_NOT_REACHED source_code_path +lifecycle.started false true COLLATERAL_DRIFT git_source.git_repository +source_code_path absent x-UNIQUE SUPPRESSED no active deployment +source_code_path absent y-UNIQUE SUPPRESSED no active deployment source_code_path x-UNIQUE absent START_NOT_REACHED source_code_path source_code_path x-UNIQUE y-UNIQUE START_NOT_REACHED source_code_path source_code_path y-UNIQUE absent START_NOT_REACHED source_code_path source_code_path y-UNIQUE x-UNIQUE START_NOT_REACHED source_code_path -space x y START_NOT_REACHED space -space y absent START_NOT_REACHED space -space y x START_NOT_REACHED space user_api_scopes len1 absent UPDATE_IGNORED user_api_scopes user_api_scopes len1 len0 UPDATE_IGNORED user_api_scopes user_api_scopes len2 absent UPDATE_IGNORED user_api_scopes user_api_scopes len2 len0 UPDATE_IGNORED user_api_scopes === summary -OK 44 +OK 46 OK_RECREATE 2 -SUPPRESSED 21 +SUPPRESSED 23 NOT_OBSERVABLE 3 UPDATE_IGNORED 13 +COLLATERAL_DRIFT 1 UNSETTABLE 2 SKIPPED 84 -START_NOT_REACHED 77 +START_NOT_REACHED 72 diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index ed42204f1e5..9c5f918bf5b 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -1,19 +1,14 @@ === catalog.yml.tmpl custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours -properties absent keys0 START_NOT_REACHED properties -properties absent keys1 START_NOT_REACHED properties -properties keys0 absent START_NOT_REACHED properties -properties keys0 keys1 START_NOT_REACHED properties properties keys1 absent UPDATE_IGNORED properties properties keys1 keys0 UPDATE_IGNORED properties -properties['team'] absent x START_NOT_REACHED properties['team'] -properties['team'] absent y START_NOT_REACHED properties['team'] properties['team'] x absent UPDATE_IGNORED properties['team'] properties['team'] y absent UPDATE_IGNORED properties['team'] === summary -OK 14 +OK 18 +SUPPRESSED 1 +NOT_OBSERVABLE 1 UPDATE_IGNORED 6 SKIPPED 8 -START_NOT_REACHED 6 diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index d4488ba0915..14318190e98 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -3,8 +3,6 @@ dataset_catalog y absent START_NOT_REA dataset_schema x y START_NOT_REACHED dataset_schema dataset_schema y absent START_NOT_REACHED dataset_schema dataset_schema y x START_NOT_REACHED dataset_schema -display_name absent x-UNIQUE START_NOT_REACHED display_name -display_name absent y-UNIQUE START_NOT_REACHED display_name display_name x-UNIQUE absent UPDATE_IGNORED display_name display_name y-UNIQUE absent UPDATE_IGNORED display_name etag absent x START_NOT_REACHED etag @@ -15,9 +13,9 @@ etag y absent START_NOT_REA etag y x START_NOT_REACHED etag === summary -OK 14 +OK 16 SUPPRESSED 2 NOT_OBSERVABLE 6 UPDATE_IGNORED 2 SKIPPED 8 -START_NOT_REACHED 12 +START_NOT_REACHED 10 diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index f0cedfc7ae4..008b0c8f2c9 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -1,62 +1,61 @@ === database_instance.yml.tmpl capacity absent x UPDATE_IGNORED capacity capacity absent y UPDATE_IGNORED capacity -capacity x absent START_NOT_REACHED capacity -capacity x y START_NOT_REACHED capacity -capacity y absent START_NOT_REACHED capacity -capacity y x START_NOT_REACHED capacity +capacity x y POST_DEPLOY_DRIFT capacity +capacity y x POST_DEPLOY_DRIFT capacity custom_tags absent len1 COLLATERAL_DRIFT capacity custom_tags len0 len1 COLLATERAL_DRIFT capacity custom_tags len1 absent COLLATERAL_DRIFT capacity custom_tags len1 len0 COLLATERAL_DRIFT capacity -custom_tags[0].key absent x START_NOT_REACHED custom_tags[0].key -custom_tags[0].key absent y START_NOT_REACHED custom_tags[0].key -custom_tags[0].key x absent START_NOT_REACHED custom_tags[0].key -custom_tags[0].key x y START_NOT_REACHED custom_tags[0].key -custom_tags[0].key y absent START_NOT_REACHED custom_tags[0].key -custom_tags[0].key y x START_NOT_REACHED custom_tags[0].key -custom_tags[0].value absent x START_NOT_REACHED custom_tags[0].value -custom_tags[0].value absent y START_NOT_REACHED custom_tags[0].value -custom_tags[0].value x absent START_NOT_REACHED custom_tags[0].value -custom_tags[0].value x y START_NOT_REACHED custom_tags[0].value -custom_tags[0].value y absent START_NOT_REACHED custom_tags[0].value -custom_tags[0].value y x START_NOT_REACHED custom_tags[0].value +custom_tags[0].key absent x COLLATERAL_DRIFT capacity +custom_tags[0].key absent y COLLATERAL_DRIFT capacity +custom_tags[0].key x absent COLLATERAL_DRIFT capacity +custom_tags[0].key x y COLLATERAL_DRIFT capacity +custom_tags[0].key y absent COLLATERAL_DRIFT capacity +custom_tags[0].key y x COLLATERAL_DRIFT capacity +custom_tags[0].value absent x COLLATERAL_DRIFT capacity +custom_tags[0].value absent y COLLATERAL_DRIFT capacity +custom_tags[0].value x absent COLLATERAL_DRIFT capacity +custom_tags[0].value x y COLLATERAL_DRIFT capacity +custom_tags[0].value y absent COLLATERAL_DRIFT capacity +custom_tags[0].value y x COLLATERAL_DRIFT capacity enable_pg_native_login absent true COLLATERAL_DRIFT capacity enable_pg_native_login false true COLLATERAL_DRIFT capacity -enable_pg_native_login true absent START_NOT_REACHED enable_pg_native_login -enable_pg_native_login true false START_NOT_REACHED enable_pg_native_login +enable_pg_native_login true absent COLLATERAL_DRIFT capacity +enable_pg_native_login true false COLLATERAL_DRIFT capacity enable_readable_secondaries absent true COLLATERAL_DRIFT capacity enable_readable_secondaries false true COLLATERAL_DRIFT capacity -enable_readable_secondaries true absent START_NOT_REACHED enable_readable_secondaries -enable_readable_secondaries true false START_NOT_REACHED enable_readable_secondaries -node_count 1 2 START_NOT_REACHED node_count -node_count 1 absent START_NOT_REACHED node_count -node_count 2 1 START_NOT_REACHED node_count -node_count 2 absent START_NOT_REACHED node_count +enable_readable_secondaries true absent COLLATERAL_DRIFT capacity +enable_readable_secondaries true false COLLATERAL_DRIFT capacity +node_count 1 2 COLLATERAL_DRIFT capacity +node_count 1 absent COLLATERAL_DRIFT capacity +node_count 2 1 COLLATERAL_DRIFT capacity +node_count 2 absent COLLATERAL_DRIFT capacity node_count absent 1 COLLATERAL_DRIFT capacity node_count absent 2 COLLATERAL_DRIFT capacity -retention_window_in_days 1 2 START_NOT_REACHED retention_window_in_days -retention_window_in_days 1 absent START_NOT_REACHED retention_window_in_days -retention_window_in_days 2 1 START_NOT_REACHED retention_window_in_days -retention_window_in_days 2 absent START_NOT_REACHED retention_window_in_days +retention_window_in_days 1 2 COLLATERAL_DRIFT capacity +retention_window_in_days 1 absent COLLATERAL_DRIFT capacity +retention_window_in_days 2 1 COLLATERAL_DRIFT capacity +retention_window_in_days 2 absent COLLATERAL_DRIFT capacity retention_window_in_days absent 1 COLLATERAL_DRIFT capacity retention_window_in_days absent 2 COLLATERAL_DRIFT capacity stopped absent true COLLATERAL_DRIFT capacity stopped false true COLLATERAL_DRIFT capacity -stopped true absent START_NOT_REACHED stopped -stopped true false START_NOT_REACHED stopped +stopped true absent COLLATERAL_DRIFT capacity +stopped true false COLLATERAL_DRIFT capacity usage_policy_id absent x COLLATERAL_DRIFT capacity usage_policy_id absent y COLLATERAL_DRIFT capacity -usage_policy_id x absent START_NOT_REACHED usage_policy_id -usage_policy_id x y START_NOT_REACHED usage_policy_id -usage_policy_id y absent START_NOT_REACHED usage_policy_id -usage_policy_id y x START_NOT_REACHED usage_policy_id +usage_policy_id x absent COLLATERAL_DRIFT capacity +usage_policy_id x y COLLATERAL_DRIFT capacity +usage_policy_id y absent COLLATERAL_DRIFT capacity +usage_policy_id y x COLLATERAL_DRIFT capacity === summary +OK 2 OK_RECREATE 32 SUPPRESSED 6 NOT_OBSERVABLE 2 UPDATE_IGNORED 2 -COLLATERAL_DRIFT 16 +POST_DEPLOY_DRIFT 2 +COLLATERAL_DRIFT 46 SKIPPED 24 -START_NOT_REACHED 34 diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index c6bc5a92ff2..d69398c1136 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,14 +1,11 @@ === genie_space.yml.tmpl description x absent UPDATE_IGNORED description description y absent UPDATE_IGNORED description -title absent x-UNIQUE START_NOT_REACHED title -title absent y-UNIQUE START_NOT_REACHED title title x-UNIQUE absent UPDATE_IGNORED title title y-UNIQUE absent UPDATE_IGNORED title === summary -OK 6 +OK 8 NOT_OBSERVABLE 6 UPDATE_IGNORED 4 SKIPPED 4 -START_NOT_REACHED 2 diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt index 9a400dad3ac..e0635f66820 100644 --- a/bundle/direct/tests/output/instance_pools.txt +++ b/bundle/direct/tests/output/instance_pools.txt @@ -1,14 +1,11 @@ === instance_pool.yml.tmpl -enable_elastic_disk absent true UPDATE_IGNORED enable_elastic_disk enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk -enable_elastic_disk true absent START_NOT_REACHED enable_elastic_disk -enable_elastic_disk true false START_NOT_REACHED enable_elastic_disk +enable_elastic_disk true false UPDATE_IGNORED enable_elastic_disk === summary -OK 44 +OK 45 OK_RECREATE 46 -SUPPRESSED 4 +SUPPRESSED 5 NOT_OBSERVABLE 2 UPDATE_IGNORED 2 SKIPPED 6 -START_NOT_REACHED 2 diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index 176286becd5..b3cde0457cd 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -1,20 +1,15 @@ === job.yml.tmpl -deployment.version_id x absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) -deployment.version_id x y START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) -deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) -deployment.version_id y x START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) parent_path absent x UPDATE_IGNORED parent_path parent_path absent y UPDATE_IGNORED parent_path -parent_path x absent START_NOT_REACHED parent_path -parent_path x y START_NOT_REACHED parent_path -parent_path y absent START_NOT_REACHED parent_path -parent_path y x START_NOT_REACHED parent_path +parent_path x absent UPDATE_IGNORED parent_path +parent_path x y UPDATE_IGNORED parent_path +parent_path y absent UPDATE_IGNORED parent_path +parent_path y x UPDATE_IGNORED parent_path === summary OK 693 -OK_INERT 2 +OK_INERT 6 SUPPRESSED 53 NOT_OBSERVABLE 40 -UPDATE_IGNORED 2 +UPDATE_IGNORED 6 SKIPPED 46 -START_NOT_REACHED 8 diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/tests/output/model_serving_endpoints.txt index c06a74380fd..c44169eaec3 100644 --- a/bundle/direct/tests/output/model_serving_endpoints.txt +++ b/bundle/direct/tests/output/model_serving_endpoints.txt @@ -1,14 +1,8 @@ -=== model_serving_endpoint.yml.tmpl -budget_policy_id x absent START_NOT_REACHED budget_policy_id (declared inert: no_update_api) -budget_policy_id x y START_NOT_REACHED budget_policy_id (declared inert: no_update_api) -budget_policy_id y absent START_NOT_REACHED budget_policy_id (declared inert: no_update_api) -budget_policy_id y x START_NOT_REACHED budget_policy_id (declared inert: no_update_api) === summary OK 32 OK_RECREATE 12 -OK_INERT 2 +OK_INERT 6 SUPPRESSED 5 NOT_OBSERVABLE 3 SKIPPED 4 -START_NOT_REACHED 4 diff --git a/bundle/direct/tests/output/models.txt b/bundle/direct/tests/output/models.txt index f25306d6f88..c8a81a801cb 100644 --- a/bundle/direct/tests/output/models.txt +++ b/bundle/direct/tests/output/models.txt @@ -1,27 +1,10 @@ === model.yml.tmpl description x absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) description y absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) -tags absent len0 START_NOT_REACHED tags (declared inert: terraform_compat) -tags absent len1 START_NOT_REACHED tags (declared inert: terraform_compat) -tags len0 absent START_NOT_REACHED tags (declared inert: terraform_compat) -tags len0 len1 START_NOT_REACHED tags (declared inert: terraform_compat) -tags[0].key absent x START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key absent y START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key x absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key x y START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key y absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key y x START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].value absent x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value absent y START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value x absent START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value x y START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value y absent START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value y x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) === summary OK 4 OK_RECREATE 2 -OK_INERT 2 +OK_INERT 18 BACKEND_ERROR 2 SKIPPED 1 -START_NOT_REACHED 16 diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index c9627cddd14..639efdb6a0e 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -1,19 +1,11 @@ === pipeline.yml.tmpl -deployment.version_id x absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) -deployment.version_id x y START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) -deployment.version_id y absent START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) -deployment.version_id y x START_NOT_REACHED deployment.version_id (declared inert: managed by the deployment metadata service) dry_run absent true UPDATE_IGNORED dry_run -dry_run false true UPDATE_IGNORED dry_run -dry_run true absent START_NOT_REACHED dry_run -dry_run true false START_NOT_REACHED dry_run === summary -OK 741 +OK 743 OK_RECREATE 9 -OK_INERT 8 -SUPPRESSED 45 +OK_INERT 12 +SUPPRESSED 46 NOT_OBSERVABLE 29 -UPDATE_IGNORED 2 +UPDATE_IGNORED 1 SKIPPED 12 -START_NOT_REACHED 6 diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 299c1584522..70725b8c1fa 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -31,32 +31,21 @@ aliases[0].version_num absent 1 START_NOT_REA aliases[0].version_num absent 2 START_NOT_REACHED aliases[0].version_num catalog_name absent main BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) -full_name absent x UPDATE_IGNORED full_name full_name absent y UPDATE_IGNORED full_name -full_name x absent START_NOT_REACHED full_name -full_name x y START_NOT_REACHED full_name -full_name y absent START_NOT_REACHED full_name -full_name y x START_NOT_REACHED full_name name absent x-UNIQUE BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name absent y-UNIQUE BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name x-UNIQUE absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name y-UNIQUE absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -owner absent x UPDATE_IGNORED owner -owner absent y UPDATE_IGNORED owner -owner x absent START_NOT_REACHED owner -owner x y START_NOT_REACHED owner -owner y absent START_NOT_REACHED owner -owner y x START_NOT_REACHED owner schema_name absent default BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) schema_name default absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) === summary -OK 10 +OK 21 OK_RECREATE 2 SUPPRESSED 1 NOT_OBSERVABLE 1 -UPDATE_IGNORED 4 +UPDATE_IGNORED 1 BACKEND_ERROR 4 SKIPPED 9 BASE_ERROR 4 -START_NOT_REACHED 38 +START_NOT_REACHED 30 diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index 49456642c34..9ac4ecb6a87 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -1,20 +1,15 @@ === schema.yml.tmpl custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours -properties absent keys0 START_NOT_REACHED properties -properties absent keys1 START_NOT_REACHED properties -properties keys0 absent START_NOT_REACHED properties -properties keys0 keys1 START_NOT_REACHED properties properties keys1 absent UPDATE_IGNORED properties properties keys1 keys0 UPDATE_IGNORED properties -properties['team'] absent x START_NOT_REACHED properties['team'] -properties['team'] absent y START_NOT_REACHED properties['team'] properties['team'] x absent UPDATE_IGNORED properties['team'] properties['team'] y absent UPDATE_IGNORED properties['team'] === summary -OK 12 +OK 16 OK_RECREATE 2 +SUPPRESSED 1 +NOT_OBSERVABLE 1 UPDATE_IGNORED 6 SKIPPED 2 -START_NOT_REACHED 6 diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index cf773893e2f..6e5c2f50c4d 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -1,30 +1,17 @@ === sql_warehouse.yml.tmpl auto_stop_mins 10 absent UPDATE_IGNORED auto_stop_mins auto_stop_mins 20 absent UPDATE_IGNORED auto_stop_mins -auto_stop_mins absent 10 START_NOT_REACHED auto_stop_mins -auto_stop_mins absent 20 START_NOT_REACHED auto_stop_mins cluster_size 2X-Small absent UPDATE_IGNORED cluster_size cluster_size X-Small absent UPDATE_IGNORED cluster_size -cluster_size absent 2X-Small START_NOT_REACHED cluster_size -cluster_size absent X-Small START_NOT_REACHED cluster_size -enable_photon absent false START_NOT_REACHED enable_photon enable_photon true absent UPDATE_IGNORED enable_photon max_num_clusters 2 absent UPDATE_IGNORED max_num_clusters max_num_clusters 3 absent UPDATE_IGNORED max_num_clusters -max_num_clusters absent 2 START_NOT_REACHED max_num_clusters -max_num_clusters absent 3 START_NOT_REACHED max_num_clusters -name absent x-UNIQUE START_NOT_REACHED name -name absent y-UNIQUE START_NOT_REACHED name -name x-UNIQUE absent UPDATE_IGNORED name name y-UNIQUE absent UPDATE_IGNORED name spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy spot_instance_policy RELIABILITY_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy -spot_instance_policy absent COST_OPTIMIZED START_NOT_REACHED spot_instance_policy -spot_instance_policy absent RELIABILITY_OPTIMIZED START_NOT_REACHED spot_instance_policy === summary -OK 32 -SUPPRESSED 2 -UPDATE_IGNORED 11 +OK 43 +SUPPRESSED 3 +UPDATE_IGNORED 10 SKIPPED 5 -START_NOT_REACHED 11 From d5242dff2e6ae78573c6859db0e970b3e5b66fd3 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 19:37:53 +0200 Subject: [PATCH 068/118] Field catalog: fall back when a resource cannot be created holding the starting value Presetting the starting value before the create only helps if the resource is creatable that way. A dashboard has to have a display name, so presetting absent made the create fail, and the fake server allowed it -- so this only showed up against AWS. The suite records that as the answer for the transition ("cannot create the resource holding it: ...") and rebuilds without the preset so the field's remaining transitions still have a resource. The fake server refuses an empty display name too, with the API's own wording. Dashboards also skip three fields with reasons: etag is a concurrency token whose value the backend assigns -- genie_spaces declares the same field spec:output_only in the generated annotations and dashboards does not, an asymmetry worth noticing but not one this suite can settle -- and dataset_catalog and dataset_schema are query parameters on the create call, never returned by a read. dashboards matches AWS row for row. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 19 +++++++++++++++---- bundle/direct/tests/output/dashboards.txt | 18 +++++------------- .../tests/testdata/fields/dashboards.yml | 11 +++++++++++ libs/testserver/dashboards.go | 12 ++++++++++++ 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index ce1aead8411..3a5e24e49e2 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -203,17 +203,28 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // the new resource is built holding it: a field the API will not move to // a value can still be created with it. rebuilt, err := rebuild(owner, ctx, client, user, cfg, fv, h, preset{f.path, tr.from}) + uncreatable := err != nil + if uncreatable { + // The resource cannot even be created holding that value, which is the + // answer for this transition: a dashboard has to have a display name, + // so nothing starts from it being absent. Recorded, and a plain + // rebuild follows so the remaining transitions have a resource. + res.detail = "cannot create the resource holding it: " + oneLine(err.Error()) + res.evidence = withContext("error creating the resource with the starting value:", err.Error()) + rebuilt, err = rebuild(owner, ctx, client, user, cfg, fv, h) + } if err != nil { - // Nothing was observed and there is no resource left to observe - // it on, which is a different statement from the field refusing - // its starting value. + // No resource left to observe anything on, which is a different + // statement from the field refusing its starting value. res.verdict = verdictBaseError res.detail = oneLine(err.Error()) res.evidence = err.Error() broken = err } else { h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) - res = runTransition(t, h, cfg.name, f.path, tr, false, baseline) + if !uncreatable { + res = runTransition(t, h, cfg.name, f.path, tr, false, baseline) + } } } diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index 14318190e98..d7f987b0ffb 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -1,21 +1,13 @@ === dashboard.yml.tmpl -dataset_catalog y absent START_NOT_REACHED dataset_catalog -dataset_schema x y START_NOT_REACHED dataset_schema -dataset_schema y absent START_NOT_REACHED dataset_schema -dataset_schema y x START_NOT_REACHED dataset_schema +display_name absent x-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) +display_name absent y-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) display_name x-UNIQUE absent UPDATE_IGNORED display_name display_name y-UNIQUE absent UPDATE_IGNORED display_name -etag absent x START_NOT_REACHED etag -etag absent y START_NOT_REACHED etag -etag x absent START_NOT_REACHED etag -etag x y START_NOT_REACHED etag -etag y absent START_NOT_REACHED etag -etag y x START_NOT_REACHED etag === summary -OK 16 +OK 6 SUPPRESSED 2 NOT_OBSERVABLE 6 UPDATE_IGNORED 2 -SKIPPED 8 -START_NOT_REACHED 10 +SKIPPED 11 +START_NOT_REACHED 2 diff --git a/bundle/direct/tests/testdata/fields/dashboards.yml b/bundle/direct/tests/testdata/fields/dashboards.yml index f07320ae158..cb92e0dad83 100644 --- a/bundle/direct/tests/testdata/fields/dashboards.yml +++ b/bundle/direct/tests/testdata/fields/dashboards.yml @@ -8,3 +8,14 @@ skip: # Must name an existing SQL warehouse, and dashboards need one, so a value-to-value move # would need a second warehouse. warehouse_id: needs a second SQL warehouse to exist + + # A concurrency token the backend assigns: it can be sent on an update to say which version + # was last read, but its value is never the caller's. genie_spaces declares the same field + # spec:output_only in resources.generated.yml and dashboards does not, which is an asymmetry + # in the generated annotations rather than anything this suite can settle. + etag: a concurrency token whose value the backend assigns + + # Query parameters on the create call (json:"-", url:"dataset_catalog"), not part of the + # dashboard, so the backend never echoes them and nothing can be observed about them here. + dataset_catalog: a query parameter on create, never returned by a read + dataset_schema: a query parameter on create, never returned by a read diff --git a/libs/testserver/dashboards.go b/libs/testserver/dashboards.go index 84b8527212a..1b8c4e2bb5b 100644 --- a/libs/testserver/dashboards.go +++ b/libs/testserver/dashboards.go @@ -103,6 +103,18 @@ func (s *FakeWorkspace) DashboardCreate(req Request) Response { } } + // A dashboard is addressed by its display name, which becomes its file name, so the API + // refuses to create one without it. + if dashboard.DisplayName == "" { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "[Request Validation] display name cannot be empty", + }, + } + } + // Default to user's home directory if parent_path is not provided (matches cloud behavior) if dashboard.ParentPath == "" { dashboard.ParentPath = "/Users/" + s.CurrentUser().UserName From 963a078e37d3573fd3767164373662912a97e015 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 19:43:34 +0200 Subject: [PATCH 069/118] Field catalog: a declared-inert field's suppression is its expected end state The starting-value check treated any non-benign suppression as the value having failed to land. That is true, but it buries the more useful verdict for a field the resource declares it ignores local changes to: such a field never holds what the config asks for, so asking whether its starting value landed has no answer. All 18 of an experiment's tags rows had turned from OK_INERT, the declaration verified, into START_NOT_REACHED, which says nothing. A suppression whose reason is exactly what the resource declared now counts as reached, so the transition is classified normally and comes out OK_INERT again. START_NOT_REACHED across the catalog drops from 211 to 185 and OK_INERT rises from 42 to 60. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 23 ++++++++++++++++------ bundle/direct/tests/output/experiments.txt | 21 +------------------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 3a5e24e49e2..aec4293d29a 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -192,7 +192,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } reuse := deployedKnown && valueLabel(deployed) == valueLabel(tr.from) - res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline) + res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline, ignoredLocally) // A starting value the deployed resource will not take -- typically a // field the API refuses to clear -- is not a dead end: a fresh resource @@ -223,7 +223,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } else { h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) if !uncreatable { - res = runTransition(t, h, cfg.name, f.path, tr, false, baseline) + res = runTransition(t, h, cfg.name, f.path, tr, false, baseline, ignoredLocally) } } } @@ -416,13 +416,24 @@ func converged(plan *deployplan.Plan, node, path string) bool { // the config asked for. A pending change means it does not; so does a change the planner // dropped for a reason other than the value already being there -- an app whose compute is // stopped suppresses a command with "no active deployment", and the command never landed. -func reachedValue(change *deployplan.ChangeDesc) bool { - return change.Action == deployplan.Skip && benignSuppressions[change.Reason] +func reachedValue(change *deployplan.ChangeDesc, path string, inert []dresources.FieldRule) bool { + if change.Action != deployplan.Skip { + return false + } + if benignSuppressions[change.Reason] { + return true + } + // A field the resource declares it ignores local changes to never holds what the config + // asks for -- that is the whole point of the declaration. Asking whether its starting value + // landed has no answer, and treating "no" as a failure buried the useful verdict: whether + // the change was dropped for exactly the declared reason, which is OK_INERT. + reason, declared := ruleReason(inert, path) + return declared && reason == change.Reason } // runTransition moves one field from one value to another and reports what happened. // startsDeployed says the field already holds tr.from, so the setup deploy can be skipped. -func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool, baseline map[string]bool) result { +func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool, baseline map[string]bool, inert []dresources.FieldRule) result { from, to := tr.from, tr.to res := result{config: config, field: path, from: valueLabel(tr.from), to: valueLabel(tr.to)} //exhaustruct:ignore @@ -451,7 +462,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.detail = firstError(diags) return res } - if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change) && !baselineCovers(baseline, plan, h.node, path) { + if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change, path, inert) && !baselineCovers(baseline, plan, h.node, path) { res.verdict = verdictStartNotReached res.detail = path res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) diff --git a/bundle/direct/tests/output/experiments.txt b/bundle/direct/tests/output/experiments.txt index cdd8fea66c6..a1b5273daac 100644 --- a/bundle/direct/tests/output/experiments.txt +++ b/bundle/direct/tests/output/experiments.txt @@ -1,25 +1,6 @@ -=== experiment.yml.tmpl -tags absent len0 START_NOT_REACHED tags (declared inert: terraform_compat) -tags absent len1 START_NOT_REACHED tags (declared inert: terraform_compat) -tags len0 absent START_NOT_REACHED tags (declared inert: terraform_compat) -tags len0 len1 START_NOT_REACHED tags (declared inert: terraform_compat) -tags len1 absent START_NOT_REACHED tags (declared inert: terraform_compat) -tags len1 len0 START_NOT_REACHED tags (declared inert: terraform_compat) -tags[0].key absent x START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key absent y START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key x absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key x y START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key y absent START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].key y x START_NOT_REACHED tags[0].key (declared inert: terraform_compat) -tags[0].value absent x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value absent y START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value x absent START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value x y START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value y absent START_NOT_REACHED tags[0].value (declared inert: terraform_compat) -tags[0].value y x START_NOT_REACHED tags[0].value (declared inert: terraform_compat) === summary OK 2 OK_RECREATE 12 +OK_INERT 18 SKIPPED 2 -START_NOT_REACHED 18 From 1b6566e9244b62f922ef46d2a5095fa627790156 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 20:09:50 +0200 Subject: [PATCH 070/118] Field catalog: an input_only field's value counts as reached, and say why a start was not Two more root causes behind START_NOT_REACHED, which is now 75 rows rather than 324. A field the resource declares it does not compare the remote for -- a registered model's aliases are input_only -- is skipped with that reason once the config and state agree, which is the value having landed as far as the engine can tell. Treating it as a failure to reach the value cost all 30 of registered_models' alias rows; they are 51 OK now. The rule is the same shape as the inert one, so both go through one list of what the resource declares about deliberately not acting. And a row that does remain now carries the planner's own reason instead of just the field path. All 72 of the apps rows say "no active deployment": the corpus app is created without compute, so its config cannot be applied at all. That was invisible before, and it is the whole explanation. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 30 ++-- bundle/direct/tests/output/apps.txt | 144 +++++++++--------- bundle/direct/tests/output/clusters.txt | 6 +- .../direct/tests/output/postgres_projects.txt | 59 +++---- .../tests/output/postgres_synced_tables.txt | 53 +------ .../direct/tests/output/registered_models.txt | 33 +--- bundle/direct/tests/values_test.go | 13 ++ 7 files changed, 128 insertions(+), 210 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index aec4293d29a..0be02eb7aa5 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -154,6 +154,9 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // Fields the resource says it ignores local changes to. Not skipped: the declaration is // a claim about behaviour, and a transition either bears it out or does not. ignoredLocally := declaredIgnoredLocally(adapter) + // Every rule the resource declares about deliberately not acting, which is what makes a + // skipped change the expected outcome rather than a failure to reach a value. + deliberate := declaredDeliberate(adapter) for path, reason := range fv.skip { rep.add(result{cfg.name, path, "", "", verdictSkipped, reason, ""}) @@ -192,7 +195,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } reuse := deployedKnown && valueLabel(deployed) == valueLabel(tr.from) - res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline, ignoredLocally) + res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline, deliberate) // A starting value the deployed resource will not take -- typically a // field the API refuses to clear -- is not a dead end: a fresh resource @@ -223,7 +226,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } else { h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) if !uncreatable { - res = runTransition(t, h, cfg.name, f.path, tr, false, baseline, ignoredLocally) + res = runTransition(t, h, cfg.name, f.path, tr, false, baseline, deliberate) } } } @@ -416,24 +419,26 @@ func converged(plan *deployplan.Plan, node, path string) bool { // the config asked for. A pending change means it does not; so does a change the planner // dropped for a reason other than the value already being there -- an app whose compute is // stopped suppresses a command with "no active deployment", and the command never landed. -func reachedValue(change *deployplan.ChangeDesc, path string, inert []dresources.FieldRule) bool { +func reachedValue(change *deployplan.ChangeDesc, path string, deliberate []dresources.FieldRule) bool { if change.Action != deployplan.Skip { return false } if benignSuppressions[change.Reason] { return true } - // A field the resource declares it ignores local changes to never holds what the config - // asks for -- that is the whole point of the declaration. Asking whether its starting value - // landed has no answer, and treating "no" as a failure buried the useful verdict: whether - // the change was dropped for exactly the declared reason, which is OK_INERT. - reason, declared := ruleReason(inert, path) + // A skip for a reason the resource declares about this field is the engine doing what it + // says it does, so the config's value is as reached as it will ever be. Two shapes: a field + // whose local changes are dropped never holds what the config asks for (asking whether the + // starting value landed has no answer, and treating "no" as failure buried the useful + // OK_INERT verdict), and a field whose remote value is not compared -- an input_only alias -- + // has the value in state and on the wire, just not visible on a read. + reason, declared := ruleReason(deliberate, path) return declared && reason == change.Reason } // runTransition moves one field from one value to another and reports what happened. // startsDeployed says the field already holds tr.from, so the setup deploy can be skipped. -func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool, baseline map[string]bool, inert []dresources.FieldRule) result { +func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool, baseline map[string]bool, deliberate []dresources.FieldRule) result { from, to := tr.from, tr.to res := result{config: config, field: path, from: valueLabel(tr.from), to: valueLabel(tr.to)} //exhaustruct:ignore @@ -462,9 +467,14 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.detail = firstError(diags) return res } - if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change, path, inert) && !baselineCovers(baseline, plan, h.node, path) { + if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change, path, deliberate) && !baselineCovers(baseline, plan, h.node, path) { res.verdict = verdictStartNotReached + // The planner's own reason for not acting, when it gave one: without it the row says + // only which field, and "the app has no active deployment" is the whole explanation. res.detail = path + if change.Reason != "" { + res.detail = path + ": " + change.Reason + } res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) return res } diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index bccbf620725..b0dee962c6d 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -3,44 +3,44 @@ compute_max_instances 1 absent UPDATE_IGNORE compute_max_instances 2 absent UPDATE_IGNORED compute_max_instances compute_min_instances 1 absent UPDATE_IGNORED compute_min_instances compute_min_instances 2 absent UPDATE_IGNORED compute_min_instances -config.command absent len1 START_NOT_REACHED config.command -config.command absent len2 START_NOT_REACHED config.command -config.command len1 absent START_NOT_REACHED config.command -config.command len1 len2 START_NOT_REACHED config.command -config.command len2 absent START_NOT_REACHED config.command -config.command len2 len1 START_NOT_REACHED config.command +config.command absent len1 START_NOT_REACHED config.command: no active deployment +config.command absent len2 START_NOT_REACHED config.command: no active deployment +config.command len1 absent START_NOT_REACHED config.command: no active deployment +config.command len1 len2 START_NOT_REACHED config.command: no active deployment +config.command len2 absent START_NOT_REACHED config.command: no active deployment +config.command len2 len1 START_NOT_REACHED config.command: no active deployment config.command[0] absent x UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it config.command[0] absent y UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it -config.command[0] x absent START_NOT_REACHED config.command[0] -config.command[0] x y START_NOT_REACHED config.command[0] -config.command[0] y absent START_NOT_REACHED config.command[0] -config.command[0] y x START_NOT_REACHED config.command[0] -config.command[1] absent x START_NOT_REACHED config.command[1] -config.command[1] absent y START_NOT_REACHED config.command[1] -config.command[1] x absent START_NOT_REACHED config.command[1] -config.command[1] x y START_NOT_REACHED config.command[1] -config.command[1] y absent START_NOT_REACHED config.command[1] -config.command[1] y x START_NOT_REACHED config.command[1] -config.env absent len0 START_NOT_REACHED config.env -config.env absent len1 START_NOT_REACHED config.env -config.env len0 absent START_NOT_REACHED config.env -config.env len0 len1 START_NOT_REACHED config.env -config.env len1 absent START_NOT_REACHED config.env -config.env len1 len0 START_NOT_REACHED config.env -config.env[0].name x y START_NOT_REACHED config.env[0].name -config.env[0].name y x START_NOT_REACHED config.env[0].name -config.env[0].value absent x START_NOT_REACHED config.env[0].value -config.env[0].value absent y START_NOT_REACHED config.env[0].value -config.env[0].value x absent START_NOT_REACHED config.env[0].value -config.env[0].value x y START_NOT_REACHED config.env[0].value -config.env[0].value y absent START_NOT_REACHED config.env[0].value -config.env[0].value y x START_NOT_REACHED config.env[0].value -config.env[0].value_from absent x START_NOT_REACHED config.env[0].value_from -config.env[0].value_from absent y START_NOT_REACHED config.env[0].value_from -config.env[0].value_from x absent START_NOT_REACHED config.env[0].value_from -config.env[0].value_from x y START_NOT_REACHED config.env[0].value_from -config.env[0].value_from y absent START_NOT_REACHED config.env[0].value_from -config.env[0].value_from y x START_NOT_REACHED config.env[0].value_from +config.command[0] x absent START_NOT_REACHED config.command[0]: no active deployment +config.command[0] x y START_NOT_REACHED config.command[0]: no active deployment +config.command[0] y absent START_NOT_REACHED config.command[0]: no active deployment +config.command[0] y x START_NOT_REACHED config.command[0]: no active deployment +config.command[1] absent x START_NOT_REACHED config.command[1]: no active deployment +config.command[1] absent y START_NOT_REACHED config.command[1]: no active deployment +config.command[1] x absent START_NOT_REACHED config.command[1]: no active deployment +config.command[1] x y START_NOT_REACHED config.command[1]: no active deployment +config.command[1] y absent START_NOT_REACHED config.command[1]: no active deployment +config.command[1] y x START_NOT_REACHED config.command[1]: no active deployment +config.env absent len0 START_NOT_REACHED config.env: no active deployment +config.env absent len1 START_NOT_REACHED config.env: no active deployment +config.env len0 absent START_NOT_REACHED config.env: no active deployment +config.env len0 len1 START_NOT_REACHED config.env: no active deployment +config.env len1 absent START_NOT_REACHED config.env: no active deployment +config.env len1 len0 START_NOT_REACHED config.env: no active deployment +config.env[0].name x y START_NOT_REACHED config.env[0].name: no active deployment +config.env[0].name y x START_NOT_REACHED config.env[0].name: no active deployment +config.env[0].value absent x START_NOT_REACHED config.env[0].value: no active deployment +config.env[0].value absent y START_NOT_REACHED config.env[0].value: no active deployment +config.env[0].value x absent START_NOT_REACHED config.env[0].value: no active deployment +config.env[0].value x y START_NOT_REACHED config.env[0].value: no active deployment +config.env[0].value y absent START_NOT_REACHED config.env[0].value: no active deployment +config.env[0].value y x START_NOT_REACHED config.env[0].value: no active deployment +config.env[0].value_from absent x START_NOT_REACHED config.env[0].value_from: no active deployment +config.env[0].value_from absent y START_NOT_REACHED config.env[0].value_from: no active deployment +config.env[0].value_from x absent START_NOT_REACHED config.env[0].value_from: no active deployment +config.env[0].value_from x y START_NOT_REACHED config.env[0].value_from: no active deployment +config.env[0].value_from y absent START_NOT_REACHED config.env[0].value_from: no active deployment +config.env[0].value_from y x START_NOT_REACHED config.env[0].value_from: no active deployment description x absent UPDATE_IGNORED description description y absent UPDATE_IGNORED description git_repository.auto_deploy true absent UPDATE_IGNORED git_repository.auto_deploy @@ -48,57 +48,57 @@ git_repository.caller_credential_id 1 absent UPDATE_IGNORE git_repository.caller_credential_id 2 absent UPDATE_IGNORED git_repository.caller_credential_id git_source.branch absent x SUPPRESSED no active deployment git_source.branch absent y SUPPRESSED no active deployment -git_source.branch x absent START_NOT_REACHED git_source.branch -git_source.branch x y START_NOT_REACHED git_source.branch -git_source.branch y absent START_NOT_REACHED git_source.branch -git_source.branch y x START_NOT_REACHED git_source.branch +git_source.branch x absent START_NOT_REACHED git_source.branch: no active deployment +git_source.branch x y START_NOT_REACHED git_source.branch: no active deployment +git_source.branch y absent START_NOT_REACHED git_source.branch: no active deployment +git_source.branch y x START_NOT_REACHED git_source.branch: no active deployment git_source.commit absent x SUPPRESSED no active deployment git_source.commit absent y SUPPRESSED no active deployment -git_source.commit x absent START_NOT_REACHED git_source.commit -git_source.commit x y START_NOT_REACHED git_source.commit -git_source.commit y absent START_NOT_REACHED git_source.commit -git_source.commit y x START_NOT_REACHED git_source.commit +git_source.commit x absent START_NOT_REACHED git_source.commit: no active deployment +git_source.commit x y START_NOT_REACHED git_source.commit: no active deployment +git_source.commit y absent START_NOT_REACHED git_source.commit: no active deployment +git_source.commit y x START_NOT_REACHED git_source.commit: no active deployment git_source.git_repository.auto_deploy absent false SUPPRESSED no active deployment git_source.git_repository.auto_deploy absent true SUPPRESSED no active deployment -git_source.git_repository.auto_deploy false absent START_NOT_REACHED git_source.git_repository.auto_deploy -git_source.git_repository.auto_deploy false true START_NOT_REACHED git_source.git_repository.auto_deploy -git_source.git_repository.auto_deploy true absent START_NOT_REACHED git_source.git_repository.auto_deploy -git_source.git_repository.auto_deploy true false START_NOT_REACHED git_source.git_repository.auto_deploy -git_source.git_repository.caller_credential_id 1 2 START_NOT_REACHED git_source.git_repository.caller_credential_id -git_source.git_repository.caller_credential_id 1 absent START_NOT_REACHED git_source.git_repository.caller_credential_id -git_source.git_repository.caller_credential_id 2 1 START_NOT_REACHED git_source.git_repository.caller_credential_id -git_source.git_repository.caller_credential_id 2 absent START_NOT_REACHED git_source.git_repository.caller_credential_id +git_source.git_repository.auto_deploy false absent START_NOT_REACHED git_source.git_repository.auto_deploy: no active deployment +git_source.git_repository.auto_deploy false true START_NOT_REACHED git_source.git_repository.auto_deploy: no active deployment +git_source.git_repository.auto_deploy true absent START_NOT_REACHED git_source.git_repository.auto_deploy: no active deployment +git_source.git_repository.auto_deploy true false START_NOT_REACHED git_source.git_repository.auto_deploy: no active deployment +git_source.git_repository.caller_credential_id 1 2 START_NOT_REACHED git_source.git_repository.caller_credential_id: no active deployment +git_source.git_repository.caller_credential_id 1 absent START_NOT_REACHED git_source.git_repository.caller_credential_id: no active deployment +git_source.git_repository.caller_credential_id 2 1 START_NOT_REACHED git_source.git_repository.caller_credential_id: no active deployment +git_source.git_repository.caller_credential_id 2 absent START_NOT_REACHED git_source.git_repository.caller_credential_id: no active deployment git_source.git_repository.caller_credential_id absent 1 SUPPRESSED no active deployment git_source.git_repository.caller_credential_id absent 2 SUPPRESSED no active deployment -git_source.git_repository.provider x y START_NOT_REACHED git_source.git_repository.provider -git_source.git_repository.provider y x START_NOT_REACHED git_source.git_repository.provider -git_source.git_repository.url x y START_NOT_REACHED git_source.git_repository.url -git_source.git_repository.url y x START_NOT_REACHED git_source.git_repository.url +git_source.git_repository.provider x y START_NOT_REACHED git_source.git_repository.provider: no active deployment +git_source.git_repository.provider y x START_NOT_REACHED git_source.git_repository.provider: no active deployment +git_source.git_repository.url x y START_NOT_REACHED git_source.git_repository.url: no active deployment +git_source.git_repository.url y x START_NOT_REACHED git_source.git_repository.url: no active deployment git_source.resolved_commit absent x SUPPRESSED no active deployment git_source.resolved_commit absent y SUPPRESSED no active deployment -git_source.resolved_commit x absent START_NOT_REACHED git_source.resolved_commit -git_source.resolved_commit x y START_NOT_REACHED git_source.resolved_commit -git_source.resolved_commit y absent START_NOT_REACHED git_source.resolved_commit -git_source.resolved_commit y x START_NOT_REACHED git_source.resolved_commit +git_source.resolved_commit x absent START_NOT_REACHED git_source.resolved_commit: no active deployment +git_source.resolved_commit x y START_NOT_REACHED git_source.resolved_commit: no active deployment +git_source.resolved_commit y absent START_NOT_REACHED git_source.resolved_commit: no active deployment +git_source.resolved_commit y x START_NOT_REACHED git_source.resolved_commit: no active deployment git_source.source_code_path absent x SUPPRESSED no active deployment git_source.source_code_path absent y SUPPRESSED no active deployment -git_source.source_code_path x absent START_NOT_REACHED git_source.source_code_path -git_source.source_code_path x y START_NOT_REACHED git_source.source_code_path -git_source.source_code_path y absent START_NOT_REACHED git_source.source_code_path -git_source.source_code_path y x START_NOT_REACHED git_source.source_code_path +git_source.source_code_path x absent START_NOT_REACHED git_source.source_code_path: no active deployment +git_source.source_code_path x y START_NOT_REACHED git_source.source_code_path: no active deployment +git_source.source_code_path y absent START_NOT_REACHED git_source.source_code_path: no active deployment +git_source.source_code_path y x START_NOT_REACHED git_source.source_code_path: no active deployment git_source.tag absent x SUPPRESSED no active deployment git_source.tag absent y SUPPRESSED no active deployment -git_source.tag x absent START_NOT_REACHED git_source.tag -git_source.tag x y START_NOT_REACHED git_source.tag -git_source.tag y absent START_NOT_REACHED git_source.tag -git_source.tag y x START_NOT_REACHED git_source.tag +git_source.tag x absent START_NOT_REACHED git_source.tag: no active deployment +git_source.tag x y START_NOT_REACHED git_source.tag: no active deployment +git_source.tag y absent START_NOT_REACHED git_source.tag: no active deployment +git_source.tag y x START_NOT_REACHED git_source.tag: no active deployment lifecycle.started false true COLLATERAL_DRIFT git_source.git_repository source_code_path absent x-UNIQUE SUPPRESSED no active deployment source_code_path absent y-UNIQUE SUPPRESSED no active deployment -source_code_path x-UNIQUE absent START_NOT_REACHED source_code_path -source_code_path x-UNIQUE y-UNIQUE START_NOT_REACHED source_code_path -source_code_path y-UNIQUE absent START_NOT_REACHED source_code_path -source_code_path y-UNIQUE x-UNIQUE START_NOT_REACHED source_code_path +source_code_path x-UNIQUE absent START_NOT_REACHED source_code_path: no active deployment +source_code_path x-UNIQUE y-UNIQUE START_NOT_REACHED source_code_path: no active deployment +source_code_path y-UNIQUE absent START_NOT_REACHED source_code_path: no active deployment +source_code_path y-UNIQUE x-UNIQUE START_NOT_REACHED source_code_path: no active deployment user_api_scopes len1 absent UPDATE_IGNORED user_api_scopes user_api_scopes len1 len0 UPDATE_IGNORED user_api_scopes user_api_scopes len2 absent UPDATE_IGNORED user_api_scopes diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/tests/output/clusters.txt index 0e802c4e365..97e620e06b5 100644 --- a/bundle/direct/tests/output/clusters.txt +++ b/bundle/direct/tests/output/clusters.txt @@ -1,10 +1,6 @@ -=== cluster.yml.tmpl -aws_attributes.ebs_volume_count absent 1 START_NOT_REACHED aws_attributes.ebs_volume_count -aws_attributes.spot_bid_price_percent absent 1 START_NOT_REACHED aws_attributes.spot_bid_price_percent === summary -OK 427 +OK 429 SUPPRESSED 22 NOT_OBSERVABLE 9 SKIPPED 4 -START_NOT_REACHED 2 diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index 3472bd44a8e..38a36eeabf8 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -1,16 +1,12 @@ === postgres_project.yml.tmpl -budget_policy_id absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -budget_policy_id absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -budget_policy_id x absent START_NOT_REACHED budget_policy_id -budget_policy_id x y START_NOT_REACHED budget_policy_id -budget_policy_id y absent START_NOT_REACHED budget_policy_id -budget_policy_id y x START_NOT_REACHED budget_policy_id +budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... +budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... custom_tags absent len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... custom_tags len0 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... custom_tags len0 len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... -custom_tags len1 absent START_NOT_REACHED custom_tags -custom_tags len1 len0 START_NOT_REACHED custom_tags +custom_tags len1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... +custom_tags len1 len0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... custom_tags[0].key absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].key absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].key x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... @@ -25,30 +21,20 @@ custom_tags[0].value y absent BASE_ERROR c custom_tags[0].value y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_branch x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_branch y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... -default_endpoint_settings.autoscaling_limit_max_cu 1 2 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu -default_endpoint_settings.autoscaling_limit_max_cu 1 absent START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu -default_endpoint_settings.autoscaling_limit_max_cu 2 1 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu -default_endpoint_settings.autoscaling_limit_max_cu 2 absent START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu -default_endpoint_settings.autoscaling_limit_max_cu absent 1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_max_cu absent 2 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_max_cu -default_endpoint_settings.autoscaling_limit_min_cu 1 2 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu -default_endpoint_settings.autoscaling_limit_min_cu 1 absent START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu -default_endpoint_settings.autoscaling_limit_min_cu 2 1 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu -default_endpoint_settings.autoscaling_limit_min_cu 2 absent START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu -default_endpoint_settings.autoscaling_limit_min_cu absent 1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu absent 2 START_NOT_REACHED default_endpoint_settings.autoscaling_limit_min_cu -default_endpoint_settings.no_suspension absent false START_NOT_REACHED default_endpoint_settings.no_suspension -default_endpoint_settings.no_suspension absent true START_NOT_REACHED default_endpoint_settings.no_suspension -default_endpoint_settings.no_suspension false absent START_NOT_REACHED default_endpoint_settings.no_suspension -default_endpoint_settings.no_suspension false true START_NOT_REACHED default_endpoint_settings.no_suspension +default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... +default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension true absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension true false BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings absent keys0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... default_endpoint_settings.pg_settings absent keys1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... default_endpoint_settings.pg_settings keys0 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings keys0 keys1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings keys1 absent START_NOT_REACHED default_endpoint_settings.pg_settings -default_endpoint_settings.pg_settings keys1 keys0 START_NOT_REACHED default_endpoint_settings.pg_settings +default_endpoint_settings.pg_settings keys1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... +default_endpoint_settings.pg_settings keys1 keys0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings['work_mem'] absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings['work_mem'] absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.pg_settings['work_mem'] x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... @@ -57,21 +43,16 @@ default_endpoint_settings.pg_settings['work_mem'] y absent BASE_ERR default_endpoint_settings.pg_settings['work_mem'] y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name x absent START_NOT_REACHED display_name -display_name x y START_NOT_REACHED display_name -display_name y absent START_NOT_REACHED display_name -display_name y x START_NOT_REACHED display_name +display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... +display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... -enable_pg_native_login true false START_NOT_REACHED enable_pg_native_login -pg_version 16 absent START_NOT_REACHED pg_version -pg_version 17 16 START_NOT_REACHED pg_version -purge_on_delete true absent START_NOT_REACHED purge_on_delete +purge_on_delete true absent START_NOT_REACHED purge_on_delete: missing_in_remote === summary -OK 9 -OK_RECREATE 6 +OK 26 +OK_RECREATE 8 SUPPRESSED 4 -BACKEND_ERROR 3 +BACKEND_ERROR 17 SKIPPED 1 -BASE_ERROR 34 -START_NOT_REACHED 30 +BASE_ERROR 30 +START_NOT_REACHED 1 diff --git a/bundle/direct/tests/output/postgres_synced_tables.txt b/bundle/direct/tests/output/postgres_synced_tables.txt index 6d576d01d52..e6fae0817e4 100644 --- a/bundle/direct/tests/output/postgres_synced_tables.txt +++ b/bundle/direct/tests/output/postgres_synced_tables.txt @@ -1,57 +1,6 @@ -=== postgres_synced_table.yml.tmpl -existing_pipeline_id x absent START_NOT_REACHED existing_pipeline_id -extra_columns[0].column_name x y START_NOT_REACHED extra_columns[0].column_name -extra_columns[0].column_name y x START_NOT_REACHED extra_columns[0].column_name -extra_columns[0].column_type x y START_NOT_REACHED extra_columns[0].column_type -extra_columns[0].column_type y x START_NOT_REACHED extra_columns[0].column_type -extra_columns[0].compute absent x START_NOT_REACHED extra_columns[0].compute -extra_columns[0].compute absent y START_NOT_REACHED extra_columns[0].compute -extra_columns[0].compute x absent START_NOT_REACHED extra_columns[0].compute -extra_columns[0].compute x y START_NOT_REACHED extra_columns[0].compute -extra_columns[0].compute y absent START_NOT_REACHED extra_columns[0].compute -extra_columns[0].compute y x START_NOT_REACHED extra_columns[0].compute -extra_columns[0].maintenance STORED_GENERATED absent START_NOT_REACHED extra_columns[0].maintenance -extra_columns[0].maintenance absent STORED_GENERATED START_NOT_REACHED extra_columns[0].maintenance -new_pipeline_spec.budget_policy_id absent x START_NOT_REACHED new_pipeline_spec.budget_policy_id -new_pipeline_spec.budget_policy_id absent y START_NOT_REACHED new_pipeline_spec.budget_policy_id -new_pipeline_spec.budget_policy_id x absent START_NOT_REACHED new_pipeline_spec.budget_policy_id -new_pipeline_spec.budget_policy_id x y START_NOT_REACHED new_pipeline_spec.budget_policy_id -new_pipeline_spec.budget_policy_id y absent START_NOT_REACHED new_pipeline_spec.budget_policy_id -new_pipeline_spec.budget_policy_id y x START_NOT_REACHED new_pipeline_spec.budget_policy_id -new_pipeline_spec.pipeline_channel CURRENT PREVIEW START_NOT_REACHED new_pipeline_spec.pipeline_channel -new_pipeline_spec.pipeline_channel CURRENT absent START_NOT_REACHED new_pipeline_spec.pipeline_channel -new_pipeline_spec.pipeline_channel PREVIEW CURRENT START_NOT_REACHED new_pipeline_spec.pipeline_channel -new_pipeline_spec.pipeline_channel PREVIEW absent START_NOT_REACHED new_pipeline_spec.pipeline_channel -new_pipeline_spec.pipeline_channel absent CURRENT START_NOT_REACHED new_pipeline_spec.pipeline_channel -new_pipeline_spec.pipeline_channel absent PREVIEW START_NOT_REACHED new_pipeline_spec.pipeline_channel -new_pipeline_spec.storage_catalog absent x START_NOT_REACHED new_pipeline_spec.storage_catalog -new_pipeline_spec.storage_catalog absent y START_NOT_REACHED new_pipeline_spec.storage_catalog -new_pipeline_spec.storage_catalog x absent START_NOT_REACHED new_pipeline_spec.storage_catalog -new_pipeline_spec.storage_catalog x y START_NOT_REACHED new_pipeline_spec.storage_catalog -new_pipeline_spec.storage_catalog y absent START_NOT_REACHED new_pipeline_spec.storage_catalog -new_pipeline_spec.storage_catalog y x START_NOT_REACHED new_pipeline_spec.storage_catalog -new_pipeline_spec.storage_schema absent x START_NOT_REACHED new_pipeline_spec.storage_schema -new_pipeline_spec.storage_schema absent y START_NOT_REACHED new_pipeline_spec.storage_schema -new_pipeline_spec.storage_schema x absent START_NOT_REACHED new_pipeline_spec.storage_schema -new_pipeline_spec.storage_schema x y START_NOT_REACHED new_pipeline_spec.storage_schema -new_pipeline_spec.storage_schema y absent START_NOT_REACHED new_pipeline_spec.storage_schema -new_pipeline_spec.storage_schema y x START_NOT_REACHED new_pipeline_spec.storage_schema -primary_key_columns len1 absent START_NOT_REACHED primary_key_columns -timeseries_key y absent START_NOT_REACHED timeseries_key -type_overrides[0].column_name x y START_NOT_REACHED type_overrides[0].column_name -type_overrides[0].column_name y x START_NOT_REACHED type_overrides[0].column_name -type_overrides[0].pg_type PG_SPECIFIC_TYPE_HALFVEC PG_SPECIFIC_TYPE_VARCHAR START_NOT_REACHED type_overrides[0].pg_type -type_overrides[0].pg_type PG_SPECIFIC_TYPE_VARCHAR PG_SPECIFIC_TYPE_HALFVEC START_NOT_REACHED type_overrides[0].pg_type -type_overrides[0].size 1 2 START_NOT_REACHED type_overrides[0].size -type_overrides[0].size 1 absent START_NOT_REACHED type_overrides[0].size -type_overrides[0].size 2 1 START_NOT_REACHED type_overrides[0].size -type_overrides[0].size 2 absent START_NOT_REACHED type_overrides[0].size -type_overrides[0].size absent 1 START_NOT_REACHED type_overrides[0].size -type_overrides[0].size absent 2 START_NOT_REACHED type_overrides[0].size === summary -OK_RECREATE 61 +OK_RECREATE 110 SUPPRESSED 6 NOT_OBSERVABLE 4 SKIPPED 1 -START_NOT_REACHED 49 diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 70725b8c1fa..3d000a02bbf 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,34 +1,4 @@ === registered_model.yml.tmpl -aliases[0].alias_name absent x START_NOT_REACHED aliases[0].alias_name -aliases[0].alias_name absent y START_NOT_REACHED aliases[0].alias_name -aliases[0].alias_name x absent START_NOT_REACHED aliases[0].alias_name -aliases[0].alias_name x y START_NOT_REACHED aliases[0].alias_name -aliases[0].alias_name y absent START_NOT_REACHED aliases[0].alias_name -aliases[0].alias_name y x START_NOT_REACHED aliases[0].alias_name -aliases[0].catalog_name absent x START_NOT_REACHED aliases[0].catalog_name -aliases[0].catalog_name absent y START_NOT_REACHED aliases[0].catalog_name -aliases[0].catalog_name x absent START_NOT_REACHED aliases[0].catalog_name -aliases[0].catalog_name x y START_NOT_REACHED aliases[0].catalog_name -aliases[0].catalog_name y absent START_NOT_REACHED aliases[0].catalog_name -aliases[0].catalog_name y x START_NOT_REACHED aliases[0].catalog_name -aliases[0].model_name absent x START_NOT_REACHED aliases[0].model_name -aliases[0].model_name absent y START_NOT_REACHED aliases[0].model_name -aliases[0].model_name x absent START_NOT_REACHED aliases[0].model_name -aliases[0].model_name x y START_NOT_REACHED aliases[0].model_name -aliases[0].model_name y absent START_NOT_REACHED aliases[0].model_name -aliases[0].model_name y x START_NOT_REACHED aliases[0].model_name -aliases[0].schema_name absent x START_NOT_REACHED aliases[0].schema_name -aliases[0].schema_name absent y START_NOT_REACHED aliases[0].schema_name -aliases[0].schema_name x absent START_NOT_REACHED aliases[0].schema_name -aliases[0].schema_name x y START_NOT_REACHED aliases[0].schema_name -aliases[0].schema_name y absent START_NOT_REACHED aliases[0].schema_name -aliases[0].schema_name y x START_NOT_REACHED aliases[0].schema_name -aliases[0].version_num 1 2 START_NOT_REACHED aliases[0].version_num -aliases[0].version_num 1 absent START_NOT_REACHED aliases[0].version_num -aliases[0].version_num 2 1 START_NOT_REACHED aliases[0].version_num -aliases[0].version_num 2 absent START_NOT_REACHED aliases[0].version_num -aliases[0].version_num absent 1 START_NOT_REACHED aliases[0].version_num -aliases[0].version_num absent 2 START_NOT_REACHED aliases[0].version_num catalog_name absent main BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) full_name absent y UPDATE_IGNORED full_name @@ -40,7 +10,7 @@ schema_name absent default BASE_ERROR c schema_name default absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) === summary -OK 21 +OK 51 OK_RECREATE 2 SUPPRESSED 1 NOT_OBSERVABLE 1 @@ -48,4 +18,3 @@ UPDATE_IGNORED 1 BACKEND_ERROR 4 SKIPPED 9 BASE_ERROR 4 -START_NOT_REACHED 30 diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index bcd0a6f4839..de06ef2951d 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -98,6 +98,19 @@ func declaredIgnoredLocally(adapter *dresources.Adapter) []dresources.FieldRule return rules } +// declaredDeliberate collects every field rule the resource declares about not acting: the ones +// it ignores local changes to, and the ones whose remote value it does not compare. A plan that +// skips a field for one of these reasons has done what the resource says it does, so the config's +// value is as reached as it is ever going to be. +func declaredDeliberate(adapter *dresources.Adapter) []dresources.FieldRule { + var rules []dresources.FieldRule + for _, cfg := range lifecycleConfigs(adapter) { + rules = append(rules, cfg.IgnoreLocalChanges...) + rules = append(rules, cfg.IgnoreRemoteChanges...) + } + return rules +} + func lifecycleConfigs(adapter *dresources.Adapter) []*dresources.ResourceLifecycleConfig { var out []*dresources.ResourceLifecycleConfig for _, cfg := range []*dresources.ResourceLifecycleConfig{adapter.ResourceConfig(), adapter.GeneratedResourceConfig()} { From 258f17d17e775d5bf36e08b7ba1fd7405500d15e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 20:14:20 +0200 Subject: [PATCH 071/118] Field catalog: a write-only field has no remote to reach The engine's own definition of missing_in_remote is that the field has no place in the remote type, so the remote is always nil: a write-only input like a postgres project's purge_on_delete, which the bundle acts on at destroy and the API never returns. There is nothing for such a value to be reached in, so a skip for that reason counts as reached. That was the last root cause. START_NOT_REACHED is 74 rows, from 324, and every one now states why: 72 apps rows say "no active deployment", because the corpus app is created without compute and its config cannot be applied at all, and 2 dashboards rows say the resource cannot be created holding the starting value, because a dashboard has to have a display name. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 7 +++++++ bundle/direct/tests/output/postgres_projects.txt | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index 0be02eb7aa5..d103fcb0810 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -426,6 +426,13 @@ func reachedValue(change *deployplan.ChangeDesc, path string, deliberate []dreso if benignSuppressions[change.Reason] { return true } + // A field the remote type has no place for -- a write-only input like purge_on_delete, which + // the bundle acts on at destroy and the API never returns. The engine's own definition of the + // reason is that the remote is always nil, so there is nothing for the value to be reached + // in; it is in state and that is all there is. + if change.Reason == deployplan.ReasonMissingInRemote { + return true + } // A skip for a reason the resource declares about this field is the engine doing what it // says it does, so the config's value is as reached as it will ever be. Two shapes: a field // whose local changes are dropped never holds what the config asks for (asking whether the diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/tests/output/postgres_projects.txt index 38a36eeabf8..aeca51d0b8e 100644 --- a/bundle/direct/tests/output/postgres_projects.txt +++ b/bundle/direct/tests/output/postgres_projects.txt @@ -46,13 +46,11 @@ display_name absent y BASE_ERROR c display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... -purge_on_delete true absent START_NOT_REACHED purge_on_delete: missing_in_remote === summary -OK 26 +OK 27 OK_RECREATE 8 SUPPRESSED 4 BACKEND_ERROR 17 SKIPPED 1 BASE_ERROR 30 -START_NOT_REACHED 1 From e8e369b83274d1aa77efc4c1f3090eb9d7b2ce2a Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 20:25:41 +0200 Subject: [PATCH 072/118] Field catalog: drop the redundant field name, explain size labels in a legend A row's detail column repeated the field name, which the first column already carries. It now holds only what that column cannot say: the engine's reason for a suppression, the API message for a rejection, the other field for collateral drift. UPDATE_IGNORED and STALE_READ carry nothing, because the field and the verdict are the whole story. A container's label says how many entries it has, which needed a lookup to decode: keys1 means a different map for properties than it does for options. The full report now ends with a legend naming what each label stood for, per field, as JSON. It goes there rather than into the committed report because JSON churns whenever an SDK type gains a field, and a reader decoding a label is already in that file for the evidence. Printing the real value in the row itself was the alternative and is worse: a list of structs renders as nil-pointer soup with a pointer address in it, so the golden would not even be stable. Also: the identity the run deploys as is redacted now. It is tester@databricks.com against the fake server and a service principal's UUID on a real workspace, and it appears inside seeded values, so a report carrying it could never match between the two. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 45 ++++- bundle/direct/tests/harness_test.go | 1 + bundle/direct/tests/output/apps.txt | 170 +++++++++--------- bundle/direct/tests/output/catalogs.txt | 12 +- bundle/direct/tests/output/dashboards.txt | 4 +- .../tests/output/database_instances.txt | 4 +- bundle/direct/tests/output/genie_spaces.txt | 8 +- bundle/direct/tests/output/instance_pools.txt | 4 +- bundle/direct/tests/output/jobs.txt | 12 +- bundle/direct/tests/output/pipelines.txt | 2 +- .../direct/tests/output/registered_models.txt | 2 +- bundle/direct/tests/output/schemas.txt | 12 +- bundle/direct/tests/output/sql_warehouses.txt | 20 +-- bundle/direct/tests/report_test.go | 58 ++++++ 14 files changed, 221 insertions(+), 133 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index d103fcb0810..abee3bdf283 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -42,6 +42,7 @@ import ( "slices" "strconv" "strings" + "sync" "testing" "github.com/databricks/cli/bundle/deployplan" @@ -144,6 +145,16 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl declaredUnsettable(adapter), h.unique) rep.addCoverage(fields, uncovered) + // A container's label says only how many entries it has, so record what each one stands for. + for _, f := range fields { + if !isContainer(f.kind) { + continue + } + for _, value := range f.values { + rep.addLegend(f.path, valueLabel(value), value) + } + } + // Fields the resource declares a user cannot meaningfully set. Recorded with the // resource's own reason rather than tested: every transition would come back SUPPRESSED // with exactly that reason. @@ -476,12 +487,10 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans } if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change, path, deliberate) && !baselineCovers(baseline, plan, h.node, path) { res.verdict = verdictStartNotReached - // The planner's own reason for not acting, when it gave one: without it the row says - // only which field, and "the app has no active deployment" is the whole explanation. - res.detail = path - if change.Reason != "" { - res.detail = path + ": " + change.Reason - } + // The planner's own reason for not acting, when it gave one -- "the app has no active + // deployment" is the whole explanation. The field is already the row's first column, + // so the detail carries only what that column cannot say. + res.detail = change.Reason res.evidence = withContext("plan after deploying the starting value:", planJSON(plan)) return res } @@ -552,7 +561,6 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans switch { case wasIgnored(plan, second, h.node, path): res.verdict = verdictUpdateIgnored - res.detail = path res.evidence = withContext("plan taken twice after the deploy, still unchanged:", planJSON(second)) t.Logf("the write was accepted but the remote value did not move on two reads:\n%s", res.evidence) return res @@ -560,7 +568,6 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans case converged(second, h.node, path): // The remote moved and the plan is now clean, so the first read was behind. res.verdict = verdictStaleRead - res.detail = path res.evidence = withContext("plan taken right after the deploy:", planJSON(after)) return res @@ -909,11 +916,33 @@ var generatedIDs = []struct { {regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`), "[UUID]"}, } +// workspaceUserName holds the identity this run deploys as, so it can be redacted: it is +// tester@databricks.com against the fake server and a service principal's UUID on a real +// workspace, and it turns up inside seeded values and error messages alike. Written once with +// the same value by every harness, so the mutex only guards the race, not a decision. +var ( + workspaceUserMu sync.Mutex + workspaceUserName string +) + +func rememberWorkspaceUser(name string) { + workspaceUserMu.Lock() + defer workspaceUserMu.Unlock() + workspaceUserName = name +} + // redactIDs replaces every run-specific id in a message with a stable placeholder. func redactIDs(s string) string { for _, id := range generatedIDs { s = id.pattern.ReplaceAllString(s, id.replacement) } + + workspaceUserMu.Lock() + user := workspaceUserName + workspaceUserMu.Unlock() + if user != "" { + s = strings.ReplaceAll(s, user, "[USERNAME]") + } return s } diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/tests/harness_test.go index c708fd2d209..9bc3befbde6 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/tests/harness_test.go @@ -169,6 +169,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC if err != nil { return nil, err } + rememberWorkspaceUser(user.UserName) vars := templateVars(uniqueName, user.UserName) var missing string yml := os.Expand(string(src), func(key string) string { diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/tests/output/apps.txt index b0dee962c6d..852267e478d 100644 --- a/bundle/direct/tests/output/apps.txt +++ b/bundle/direct/tests/output/apps.txt @@ -1,108 +1,108 @@ === app.yml.tmpl -compute_max_instances 1 absent UPDATE_IGNORED compute_max_instances -compute_max_instances 2 absent UPDATE_IGNORED compute_max_instances -compute_min_instances 1 absent UPDATE_IGNORED compute_min_instances -compute_min_instances 2 absent UPDATE_IGNORED compute_min_instances -config.command absent len1 START_NOT_REACHED config.command: no active deployment -config.command absent len2 START_NOT_REACHED config.command: no active deployment -config.command len1 absent START_NOT_REACHED config.command: no active deployment -config.command len1 len2 START_NOT_REACHED config.command: no active deployment -config.command len2 absent START_NOT_REACHED config.command: no active deployment -config.command len2 len1 START_NOT_REACHED config.command: no active deployment +compute_max_instances 1 absent UPDATE_IGNORED +compute_max_instances 2 absent UPDATE_IGNORED +compute_min_instances 1 absent UPDATE_IGNORED +compute_min_instances 2 absent UPDATE_IGNORED +config.command absent len1 START_NOT_REACHED no active deployment +config.command absent len2 START_NOT_REACHED no active deployment +config.command len1 absent START_NOT_REACHED no active deployment +config.command len1 len2 START_NOT_REACHED no active deployment +config.command len2 absent START_NOT_REACHED no active deployment +config.command len2 len1 START_NOT_REACHED no active deployment config.command[0] absent x UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it config.command[0] absent y UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it -config.command[0] x absent START_NOT_REACHED config.command[0]: no active deployment -config.command[0] x y START_NOT_REACHED config.command[0]: no active deployment -config.command[0] y absent START_NOT_REACHED config.command[0]: no active deployment -config.command[0] y x START_NOT_REACHED config.command[0]: no active deployment -config.command[1] absent x START_NOT_REACHED config.command[1]: no active deployment -config.command[1] absent y START_NOT_REACHED config.command[1]: no active deployment -config.command[1] x absent START_NOT_REACHED config.command[1]: no active deployment -config.command[1] x y START_NOT_REACHED config.command[1]: no active deployment -config.command[1] y absent START_NOT_REACHED config.command[1]: no active deployment -config.command[1] y x START_NOT_REACHED config.command[1]: no active deployment -config.env absent len0 START_NOT_REACHED config.env: no active deployment -config.env absent len1 START_NOT_REACHED config.env: no active deployment -config.env len0 absent START_NOT_REACHED config.env: no active deployment -config.env len0 len1 START_NOT_REACHED config.env: no active deployment -config.env len1 absent START_NOT_REACHED config.env: no active deployment -config.env len1 len0 START_NOT_REACHED config.env: no active deployment -config.env[0].name x y START_NOT_REACHED config.env[0].name: no active deployment -config.env[0].name y x START_NOT_REACHED config.env[0].name: no active deployment -config.env[0].value absent x START_NOT_REACHED config.env[0].value: no active deployment -config.env[0].value absent y START_NOT_REACHED config.env[0].value: no active deployment -config.env[0].value x absent START_NOT_REACHED config.env[0].value: no active deployment -config.env[0].value x y START_NOT_REACHED config.env[0].value: no active deployment -config.env[0].value y absent START_NOT_REACHED config.env[0].value: no active deployment -config.env[0].value y x START_NOT_REACHED config.env[0].value: no active deployment -config.env[0].value_from absent x START_NOT_REACHED config.env[0].value_from: no active deployment -config.env[0].value_from absent y START_NOT_REACHED config.env[0].value_from: no active deployment -config.env[0].value_from x absent START_NOT_REACHED config.env[0].value_from: no active deployment -config.env[0].value_from x y START_NOT_REACHED config.env[0].value_from: no active deployment -config.env[0].value_from y absent START_NOT_REACHED config.env[0].value_from: no active deployment -config.env[0].value_from y x START_NOT_REACHED config.env[0].value_from: no active deployment -description x absent UPDATE_IGNORED description -description y absent UPDATE_IGNORED description -git_repository.auto_deploy true absent UPDATE_IGNORED git_repository.auto_deploy -git_repository.caller_credential_id 1 absent UPDATE_IGNORED git_repository.caller_credential_id -git_repository.caller_credential_id 2 absent UPDATE_IGNORED git_repository.caller_credential_id +config.command[0] x absent START_NOT_REACHED no active deployment +config.command[0] x y START_NOT_REACHED no active deployment +config.command[0] y absent START_NOT_REACHED no active deployment +config.command[0] y x START_NOT_REACHED no active deployment +config.command[1] absent x START_NOT_REACHED no active deployment +config.command[1] absent y START_NOT_REACHED no active deployment +config.command[1] x absent START_NOT_REACHED no active deployment +config.command[1] x y START_NOT_REACHED no active deployment +config.command[1] y absent START_NOT_REACHED no active deployment +config.command[1] y x START_NOT_REACHED no active deployment +config.env absent len0 START_NOT_REACHED no active deployment +config.env absent len1 START_NOT_REACHED no active deployment +config.env len0 absent START_NOT_REACHED no active deployment +config.env len0 len1 START_NOT_REACHED no active deployment +config.env len1 absent START_NOT_REACHED no active deployment +config.env len1 len0 START_NOT_REACHED no active deployment +config.env[0].name x y START_NOT_REACHED no active deployment +config.env[0].name y x START_NOT_REACHED no active deployment +config.env[0].value absent x START_NOT_REACHED no active deployment +config.env[0].value absent y START_NOT_REACHED no active deployment +config.env[0].value x absent START_NOT_REACHED no active deployment +config.env[0].value x y START_NOT_REACHED no active deployment +config.env[0].value y absent START_NOT_REACHED no active deployment +config.env[0].value y x START_NOT_REACHED no active deployment +config.env[0].value_from absent x START_NOT_REACHED no active deployment +config.env[0].value_from absent y START_NOT_REACHED no active deployment +config.env[0].value_from x absent START_NOT_REACHED no active deployment +config.env[0].value_from x y START_NOT_REACHED no active deployment +config.env[0].value_from y absent START_NOT_REACHED no active deployment +config.env[0].value_from y x START_NOT_REACHED no active deployment +description x absent UPDATE_IGNORED +description y absent UPDATE_IGNORED +git_repository.auto_deploy true absent UPDATE_IGNORED +git_repository.caller_credential_id 1 absent UPDATE_IGNORED +git_repository.caller_credential_id 2 absent UPDATE_IGNORED git_source.branch absent x SUPPRESSED no active deployment git_source.branch absent y SUPPRESSED no active deployment -git_source.branch x absent START_NOT_REACHED git_source.branch: no active deployment -git_source.branch x y START_NOT_REACHED git_source.branch: no active deployment -git_source.branch y absent START_NOT_REACHED git_source.branch: no active deployment -git_source.branch y x START_NOT_REACHED git_source.branch: no active deployment +git_source.branch x absent START_NOT_REACHED no active deployment +git_source.branch x y START_NOT_REACHED no active deployment +git_source.branch y absent START_NOT_REACHED no active deployment +git_source.branch y x START_NOT_REACHED no active deployment git_source.commit absent x SUPPRESSED no active deployment git_source.commit absent y SUPPRESSED no active deployment -git_source.commit x absent START_NOT_REACHED git_source.commit: no active deployment -git_source.commit x y START_NOT_REACHED git_source.commit: no active deployment -git_source.commit y absent START_NOT_REACHED git_source.commit: no active deployment -git_source.commit y x START_NOT_REACHED git_source.commit: no active deployment +git_source.commit x absent START_NOT_REACHED no active deployment +git_source.commit x y START_NOT_REACHED no active deployment +git_source.commit y absent START_NOT_REACHED no active deployment +git_source.commit y x START_NOT_REACHED no active deployment git_source.git_repository.auto_deploy absent false SUPPRESSED no active deployment git_source.git_repository.auto_deploy absent true SUPPRESSED no active deployment -git_source.git_repository.auto_deploy false absent START_NOT_REACHED git_source.git_repository.auto_deploy: no active deployment -git_source.git_repository.auto_deploy false true START_NOT_REACHED git_source.git_repository.auto_deploy: no active deployment -git_source.git_repository.auto_deploy true absent START_NOT_REACHED git_source.git_repository.auto_deploy: no active deployment -git_source.git_repository.auto_deploy true false START_NOT_REACHED git_source.git_repository.auto_deploy: no active deployment -git_source.git_repository.caller_credential_id 1 2 START_NOT_REACHED git_source.git_repository.caller_credential_id: no active deployment -git_source.git_repository.caller_credential_id 1 absent START_NOT_REACHED git_source.git_repository.caller_credential_id: no active deployment -git_source.git_repository.caller_credential_id 2 1 START_NOT_REACHED git_source.git_repository.caller_credential_id: no active deployment -git_source.git_repository.caller_credential_id 2 absent START_NOT_REACHED git_source.git_repository.caller_credential_id: no active deployment +git_source.git_repository.auto_deploy false absent START_NOT_REACHED no active deployment +git_source.git_repository.auto_deploy false true START_NOT_REACHED no active deployment +git_source.git_repository.auto_deploy true absent START_NOT_REACHED no active deployment +git_source.git_repository.auto_deploy true false START_NOT_REACHED no active deployment +git_source.git_repository.caller_credential_id 1 2 START_NOT_REACHED no active deployment +git_source.git_repository.caller_credential_id 1 absent START_NOT_REACHED no active deployment +git_source.git_repository.caller_credential_id 2 1 START_NOT_REACHED no active deployment +git_source.git_repository.caller_credential_id 2 absent START_NOT_REACHED no active deployment git_source.git_repository.caller_credential_id absent 1 SUPPRESSED no active deployment git_source.git_repository.caller_credential_id absent 2 SUPPRESSED no active deployment -git_source.git_repository.provider x y START_NOT_REACHED git_source.git_repository.provider: no active deployment -git_source.git_repository.provider y x START_NOT_REACHED git_source.git_repository.provider: no active deployment -git_source.git_repository.url x y START_NOT_REACHED git_source.git_repository.url: no active deployment -git_source.git_repository.url y x START_NOT_REACHED git_source.git_repository.url: no active deployment +git_source.git_repository.provider x y START_NOT_REACHED no active deployment +git_source.git_repository.provider y x START_NOT_REACHED no active deployment +git_source.git_repository.url x y START_NOT_REACHED no active deployment +git_source.git_repository.url y x START_NOT_REACHED no active deployment git_source.resolved_commit absent x SUPPRESSED no active deployment git_source.resolved_commit absent y SUPPRESSED no active deployment -git_source.resolved_commit x absent START_NOT_REACHED git_source.resolved_commit: no active deployment -git_source.resolved_commit x y START_NOT_REACHED git_source.resolved_commit: no active deployment -git_source.resolved_commit y absent START_NOT_REACHED git_source.resolved_commit: no active deployment -git_source.resolved_commit y x START_NOT_REACHED git_source.resolved_commit: no active deployment +git_source.resolved_commit x absent START_NOT_REACHED no active deployment +git_source.resolved_commit x y START_NOT_REACHED no active deployment +git_source.resolved_commit y absent START_NOT_REACHED no active deployment +git_source.resolved_commit y x START_NOT_REACHED no active deployment git_source.source_code_path absent x SUPPRESSED no active deployment git_source.source_code_path absent y SUPPRESSED no active deployment -git_source.source_code_path x absent START_NOT_REACHED git_source.source_code_path: no active deployment -git_source.source_code_path x y START_NOT_REACHED git_source.source_code_path: no active deployment -git_source.source_code_path y absent START_NOT_REACHED git_source.source_code_path: no active deployment -git_source.source_code_path y x START_NOT_REACHED git_source.source_code_path: no active deployment +git_source.source_code_path x absent START_NOT_REACHED no active deployment +git_source.source_code_path x y START_NOT_REACHED no active deployment +git_source.source_code_path y absent START_NOT_REACHED no active deployment +git_source.source_code_path y x START_NOT_REACHED no active deployment git_source.tag absent x SUPPRESSED no active deployment git_source.tag absent y SUPPRESSED no active deployment -git_source.tag x absent START_NOT_REACHED git_source.tag: no active deployment -git_source.tag x y START_NOT_REACHED git_source.tag: no active deployment -git_source.tag y absent START_NOT_REACHED git_source.tag: no active deployment -git_source.tag y x START_NOT_REACHED git_source.tag: no active deployment +git_source.tag x absent START_NOT_REACHED no active deployment +git_source.tag x y START_NOT_REACHED no active deployment +git_source.tag y absent START_NOT_REACHED no active deployment +git_source.tag y x START_NOT_REACHED no active deployment lifecycle.started false true COLLATERAL_DRIFT git_source.git_repository source_code_path absent x-UNIQUE SUPPRESSED no active deployment source_code_path absent y-UNIQUE SUPPRESSED no active deployment -source_code_path x-UNIQUE absent START_NOT_REACHED source_code_path: no active deployment -source_code_path x-UNIQUE y-UNIQUE START_NOT_REACHED source_code_path: no active deployment -source_code_path y-UNIQUE absent START_NOT_REACHED source_code_path: no active deployment -source_code_path y-UNIQUE x-UNIQUE START_NOT_REACHED source_code_path: no active deployment -user_api_scopes len1 absent UPDATE_IGNORED user_api_scopes -user_api_scopes len1 len0 UPDATE_IGNORED user_api_scopes -user_api_scopes len2 absent UPDATE_IGNORED user_api_scopes -user_api_scopes len2 len0 UPDATE_IGNORED user_api_scopes +source_code_path x-UNIQUE absent START_NOT_REACHED no active deployment +source_code_path x-UNIQUE y-UNIQUE START_NOT_REACHED no active deployment +source_code_path y-UNIQUE absent START_NOT_REACHED no active deployment +source_code_path y-UNIQUE x-UNIQUE START_NOT_REACHED no active deployment +user_api_scopes len1 absent UPDATE_IGNORED +user_api_scopes len1 len0 UPDATE_IGNORED +user_api_scopes len2 absent UPDATE_IGNORED +user_api_scopes len2 len0 UPDATE_IGNORED === summary OK 46 diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/tests/output/catalogs.txt index 9c5f918bf5b..8c467fe45ea 100644 --- a/bundle/direct/tests/output/catalogs.txt +++ b/bundle/direct/tests/output/catalogs.txt @@ -1,10 +1,10 @@ === catalog.yml.tmpl -custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours -custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours -properties keys1 absent UPDATE_IGNORED properties -properties keys1 keys0 UPDATE_IGNORED properties -properties['team'] x absent UPDATE_IGNORED properties['team'] -properties['team'] y absent UPDATE_IGNORED properties['team'] +custom_max_retention_hours 168 absent UPDATE_IGNORED +custom_max_retention_hours 720 absent UPDATE_IGNORED +properties keys1 absent UPDATE_IGNORED +properties keys1 keys0 UPDATE_IGNORED +properties['team'] x absent UPDATE_IGNORED +properties['team'] y absent UPDATE_IGNORED === summary OK 18 diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/tests/output/dashboards.txt index d7f987b0ffb..ec547c75555 100644 --- a/bundle/direct/tests/output/dashboards.txt +++ b/bundle/direct/tests/output/dashboards.txt @@ -1,8 +1,8 @@ === dashboard.yml.tmpl display_name absent x-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) display_name absent y-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) -display_name x-UNIQUE absent UPDATE_IGNORED display_name -display_name y-UNIQUE absent UPDATE_IGNORED display_name +display_name x-UNIQUE absent UPDATE_IGNORED +display_name y-UNIQUE absent UPDATE_IGNORED === summary OK 6 diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/tests/output/database_instances.txt index 008b0c8f2c9..40146fb5d8a 100644 --- a/bundle/direct/tests/output/database_instances.txt +++ b/bundle/direct/tests/output/database_instances.txt @@ -1,6 +1,6 @@ === database_instance.yml.tmpl -capacity absent x UPDATE_IGNORED capacity -capacity absent y UPDATE_IGNORED capacity +capacity absent x UPDATE_IGNORED +capacity absent y UPDATE_IGNORED capacity x y POST_DEPLOY_DRIFT capacity capacity y x POST_DEPLOY_DRIFT capacity custom_tags absent len1 COLLATERAL_DRIFT capacity diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/tests/output/genie_spaces.txt index d69398c1136..413ce0ea9af 100644 --- a/bundle/direct/tests/output/genie_spaces.txt +++ b/bundle/direct/tests/output/genie_spaces.txt @@ -1,8 +1,8 @@ === genie_space.yml.tmpl -description x absent UPDATE_IGNORED description -description y absent UPDATE_IGNORED description -title x-UNIQUE absent UPDATE_IGNORED title -title y-UNIQUE absent UPDATE_IGNORED title +description x absent UPDATE_IGNORED +description y absent UPDATE_IGNORED +title x-UNIQUE absent UPDATE_IGNORED +title y-UNIQUE absent UPDATE_IGNORED === summary OK 8 diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/tests/output/instance_pools.txt index e0635f66820..878363de75b 100644 --- a/bundle/direct/tests/output/instance_pools.txt +++ b/bundle/direct/tests/output/instance_pools.txt @@ -1,6 +1,6 @@ === instance_pool.yml.tmpl -enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk -enable_elastic_disk true false UPDATE_IGNORED enable_elastic_disk +enable_elastic_disk false true UPDATE_IGNORED +enable_elastic_disk true false UPDATE_IGNORED === summary OK 45 diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/tests/output/jobs.txt index b3cde0457cd..49e730ed0c6 100644 --- a/bundle/direct/tests/output/jobs.txt +++ b/bundle/direct/tests/output/jobs.txt @@ -1,10 +1,10 @@ === job.yml.tmpl -parent_path absent x UPDATE_IGNORED parent_path -parent_path absent y UPDATE_IGNORED parent_path -parent_path x absent UPDATE_IGNORED parent_path -parent_path x y UPDATE_IGNORED parent_path -parent_path y absent UPDATE_IGNORED parent_path -parent_path y x UPDATE_IGNORED parent_path +parent_path absent x UPDATE_IGNORED +parent_path absent y UPDATE_IGNORED +parent_path x absent UPDATE_IGNORED +parent_path x y UPDATE_IGNORED +parent_path y absent UPDATE_IGNORED +parent_path y x UPDATE_IGNORED === summary OK 693 diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 639efdb6a0e..66d87ec33e5 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -1,5 +1,5 @@ === pipeline.yml.tmpl -dry_run absent true UPDATE_IGNORED dry_run +dry_run absent true UPDATE_IGNORED === summary OK 743 diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index 3d000a02bbf..e35f310d093 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,7 +1,7 @@ === registered_model.yml.tmpl catalog_name absent main BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) -full_name absent y UPDATE_IGNORED full_name +full_name absent y UPDATE_IGNORED name absent x-UNIQUE BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name absent y-UNIQUE BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) name x-UNIQUE absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/tests/output/schemas.txt index 9ac4ecb6a87..2d8fff0e3d1 100644 --- a/bundle/direct/tests/output/schemas.txt +++ b/bundle/direct/tests/output/schemas.txt @@ -1,10 +1,10 @@ === schema.yml.tmpl -custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours -custom_max_retention_hours 720 absent UPDATE_IGNORED custom_max_retention_hours -properties keys1 absent UPDATE_IGNORED properties -properties keys1 keys0 UPDATE_IGNORED properties -properties['team'] x absent UPDATE_IGNORED properties['team'] -properties['team'] y absent UPDATE_IGNORED properties['team'] +custom_max_retention_hours 168 absent UPDATE_IGNORED +custom_max_retention_hours 720 absent UPDATE_IGNORED +properties keys1 absent UPDATE_IGNORED +properties keys1 keys0 UPDATE_IGNORED +properties['team'] x absent UPDATE_IGNORED +properties['team'] y absent UPDATE_IGNORED === summary OK 16 diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index 6e5c2f50c4d..32e74992024 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -1,14 +1,14 @@ === sql_warehouse.yml.tmpl -auto_stop_mins 10 absent UPDATE_IGNORED auto_stop_mins -auto_stop_mins 20 absent UPDATE_IGNORED auto_stop_mins -cluster_size 2X-Small absent UPDATE_IGNORED cluster_size -cluster_size X-Small absent UPDATE_IGNORED cluster_size -enable_photon true absent UPDATE_IGNORED enable_photon -max_num_clusters 2 absent UPDATE_IGNORED max_num_clusters -max_num_clusters 3 absent UPDATE_IGNORED max_num_clusters -name y-UNIQUE absent UPDATE_IGNORED name -spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy -spot_instance_policy RELIABILITY_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy +auto_stop_mins 10 absent UPDATE_IGNORED +auto_stop_mins 20 absent UPDATE_IGNORED +cluster_size 2X-Small absent UPDATE_IGNORED +cluster_size X-Small absent UPDATE_IGNORED +enable_photon true absent UPDATE_IGNORED +max_num_clusters 2 absent UPDATE_IGNORED +max_num_clusters 3 absent UPDATE_IGNORED +name y-UNIQUE absent UPDATE_IGNORED +spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED +spot_instance_policy RELIABILITY_OPTIMIZED absent UPDATE_IGNORED === summary OK 43 diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index 5fd8e795f06..9102f6facd6 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -2,6 +2,7 @@ package tests import ( "cmp" + "encoding/json" "errors" "fmt" "io/fs" @@ -176,6 +177,32 @@ type report struct { results []result wildcard map[string]bool covered map[string]bool + + // legend maps a size label back to the value it stands for, keyed by field: "keys1" means + // a different map for properties than it does for options, so the field is part of the key. + legend map[legendKey]string +} + +// legendKey identifies one size label of one field. +type legendKey struct { + field, label string +} + +// addLegend records what a size label stands for. Only containers get one: a scalar's label is +// the value, so there is nothing to explain. +func (r *report) addLegend(field, label string, value any) { + rendered, err := json.Marshal(value) + if err != nil { + return + } + // Redacted like everything else in the report: a seeded value can name the workspace user or + // carry this run's unique suffix, both of which differ between a fake server and a real one. + r.mu.Lock() + defer r.mu.Unlock() + if r.legend == nil { + r.legend = map[legendKey]string{} + } + r.legend[legendKey{field, label}] = redactIDs(string(rendered)) } func (r *report) add(res result) { @@ -265,12 +292,16 @@ func (r *report) render(problemsOnly bool) string { }) counts := map[verdict]int{} + // The labels the rendered rows actually use, so the legend explains those and no others. + used := map[legendKey]bool{} config := "" for _, res := range r.results { counts[res.verdict]++ if problemsOnly && !res.isProblem() { continue } + used[legendKey{res.field, res.from}] = true + used[legendKey{res.field, res.to}] = true if res.config != config { if config != "" { sb.WriteString("\n") @@ -311,6 +342,14 @@ func (r *report) render(problemsOnly bool) string { return sb.String() } + // The legend goes here and not into the committed report: it is JSON, so it would churn + // whenever an SDK type gains a field, and a reader decoding a label is already looking at + // this file for the evidence behind the row. + if legend := r.renderLegend(used); legend != "" { + sb.WriteString("\n=== values\n") + sb.WriteString(legend) + } + if gaps := r.uncoveredPaths(); len(gaps) > 0 { fmt.Fprintf(&sb, "\n=== not covered: %d fields with nothing to test\n", len(gaps)) fmt.Fprintf(&sb, "# either no config of this type declares the slice or map they sit in,\n") @@ -323,6 +362,25 @@ func (r *report) render(problemsOnly bool) string { return sb.String() } +// renderLegend spells out the size labels the rows used. Caller holds the lock. +func (r *report) renderLegend(used map[legendKey]bool) string { + keys := make([]legendKey, 0, len(used)) + for key := range used { + if _, ok := r.legend[key]; ok { + keys = append(keys, key) + } + } + slices.SortFunc(keys, func(a, b legendKey) int { + return cmp.Or(strings.Compare(a.field, b.field), strings.Compare(a.label, b.label)) + }) + + var sb strings.Builder + for _, key := range keys { + fmt.Fprintf(&sb, "%-44s %-10s %s\n", key.field, key.label, r.legend[key]) + } + return sb.String() +} + // write compares the report against its golden file, or rewrites it under -update. // Cloud runs go to a separate, uncommitted file: which values a real backend accepts // depends on the workspace, so those results are for reading, not for diffing. From da79408fe4a6813f731b44087af05c6ba5d9ee65 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 20:48:09 +0200 Subject: [PATCH 073/118] Field catalog: do not infer why a deploy was rejected I had added a verdict for "the API refused to clear this field", inferred from the failing deploy carrying only that one change. That inference does not hold: a recreate sends the whole resource, so a rejection during one is not attributable to the field under test, and an update can fail for reasons that have nothing to do with the field either. A rejection stays BASE_ERROR or BACKEND_ERROR, with the API's own message in the detail, which is what a reader needs in order to tell the causes apart. Of the rows the inference had grouped, some were an API contract ("'definition' must be supplied") and 25 were the engine naming a field in update_mask that it then omits -- opposite owners, and no verdict name could have covered both honestly. Co-authored-by: Isaac --- bundle/direct/tests/report_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index 9102f6facd6..f475772c266 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -331,7 +331,8 @@ func (r *report) render(problemsOnly bool) string { verdictBaselineDrift, verdictStaleRead, verdictUpdateIgnored, verdictDrift, verdictCollateral, verdictDriftChild, verdictBackendError, verdictDeployError, verdictTimeout, verdictPlanError, - verdictUnsettable, verdictSkipped, verdictBaseError, verdictStartNotReached, + verdictUnsettable, verdictSkipped, verdictBaseError, + verdictStartNotReached, } { if counts[v] > 0 { fmt.Fprintf(&sb, "%-18s %d\n", v, counts[v]) From 87988e0f412cc6611c62e27bb07684f62eedfcd5 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 20:57:50 +0200 Subject: [PATCH 074/118] Field catalog: recognise an absent ID field as the API's contract registered_models has three fields the bundle schema does not mark required but the API does: name, catalog_name and schema_name. Dropping any of them was recorded as an error, which read as eight findings where there is nothing to find -- a registered model has no identity without them. The engine already declares this, in provided_id_fields. A field listed there composes the resource's ID, and a local change to one recreates rather than updates, so an absent value means creating a resource with no name: OK_ID_FIELD_REQUIRED. Every input is a declaration or a fact about the run -- the field is named in provided_id_fields, the value is absent, the backend refused. None is the error's wording, which is exactly what could not be trusted here: a recreate carries the whole resource, so the field a message happens to name is not evidence about the field under test. secret_scopes shows the difference -- its backend_type rows fail a real cross-field constraint on a field no declaration names, and stay errors. Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 44 ++++++++++++++++--- .../direct/tests/output/registered_models.txt | 11 +---- bundle/direct/tests/report_test.go | 12 ++++- bundle/direct/tests/values_test.go | 13 ++++++ 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index abee3bdf283..fcfcf3915b6 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -167,7 +167,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl ignoredLocally := declaredIgnoredLocally(adapter) // Every rule the resource declares about deliberately not acting, which is what makes a // skipped change the expected outcome rather than a failure to reach a value. - deliberate := declaredDeliberate(adapter) + decl := declarations{deliberate: declaredDeliberate(adapter), idFields: declaredIDFields(adapter)} for path, reason := range fv.skip { rep.add(result{cfg.name, path, "", "", verdictSkipped, reason, ""}) @@ -206,7 +206,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } reuse := deployedKnown && valueLabel(deployed) == valueLabel(tr.from) - res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline, deliberate) + res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline, decl) // A starting value the deployed resource will not take -- typically a // field the API refuses to clear -- is not a dead end: a fresh resource @@ -237,7 +237,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } else { h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) if !uncreatable { - res = runTransition(t, h, cfg.name, f.path, tr, false, baseline, deliberate) + res = runTransition(t, h, cfg.name, f.path, tr, false, baseline, decl) } } } @@ -456,7 +456,17 @@ func reachedValue(change *deployplan.ChangeDesc, path string, deliberate []dreso // runTransition moves one field from one value to another and reports what happened. // startsDeployed says the field already holds tr.from, so the setup deploy can be skipped. -func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool, baseline map[string]bool, deliberate []dresources.FieldRule) result { +// declarations are the resource's own claims about its fields, read from resources.yml. Two +// slices of the same type as separate parameters would let a caller swap them unnoticed. +type declarations struct { + // deliberate: rules saying the engine does not act on a field, so a skipped change is the + // declared behaviour rather than a value that failed to land. + deliberate []dresources.FieldRule + // idFields: rules naming the fields that compose the resource's ID. + idFields []dresources.FieldRule +} + +func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool, baseline map[string]bool, decl declarations) result { from, to := tr.from, tr.to res := result{config: config, field: path, from: valueLabel(tr.from), to: valueLabel(tr.to)} //exhaustruct:ignore @@ -468,7 +478,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans } if !startsDeployed { if _, diags := h.deploy(); diags.HasError() { - res.verdict = verdictBaseError + res.verdict = idFieldRequired(from, path, decl, diags, verdictBaseError) res.detail = firstError(diags) res.evidence = withContext("error from the deploy:", allErrors(diags)) return res @@ -485,7 +495,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans res.detail = firstError(diags) return res } - if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change, path, deliberate) && !baselineCovers(baseline, plan, h.node, path) { + if change, ok := relatedChange(plan, h.node, path); ok && !reachedValue(change, path, decl.deliberate) && !baselineCovers(baseline, plan, h.node, path) { res.verdict = verdictStartNotReached // The planner's own reason for not acting, when it gave one -- "the app has no active // deployment" is the whole explanation. The field is already the row's first column, @@ -529,7 +539,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans case isTimeout(diags): res.verdict = verdictTimeout case isAPIError(diags): - res.verdict = verdictBackendError + res.verdict = idFieldRequired(to, path, decl, diags, verdictBackendError) default: res.verdict = verdictDeployError } @@ -764,6 +774,26 @@ func isTimeout(diags diag.Diagnostics) bool { return false } +// idFieldRequired downgrades a rejected deploy to OK_ID_FIELD_REQUIRED when the value it was +// carrying was absent on a field the resource declares as part of its ID. Every input is a +// declaration or a fact about the run -- the field is named in provided_id_fields, the value is +// absent, the backend refused -- and none is the error's wording, which cannot be attributed +// to a single field because a recreate carries the whole resource. +// +// The backend having refused still has to hold: an internal CLI failure on the same deploy is a +// real defect and keeps its own verdict. +func idFieldRequired(value any, path string, decl declarations, diags diag.Diagnostics, otherwise verdict) verdict { + // Same nil test valueLabel uses to print "absent", so the verdict and the row's own column + // can never disagree about which value this was. + if value != nil || !isAPIError(diags) { + return otherwise + } + if _, isID := ruleReason(decl.idFields, path); !isID { + return otherwise + } + return verdictIDFieldRequired +} + func isAPIError(diags diag.Diagnostics) bool { for _, d := range diags { for _, code := range apiErrorCodes { diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index e35f310d093..cd00fac3721 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,20 +1,11 @@ === registered_model.yml.tmpl -catalog_name absent main BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) -catalog_name main absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: catalog_name (400 INVALID_PARAMETER_VALUE) full_name absent y UPDATE_IGNORED -name absent x-UNIQUE BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name absent y-UNIQUE BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name x-UNIQUE absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -name y-UNIQUE absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: name (400 INVALID_PARAMETER_VALUE) -schema_name absent default BASE_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) -schema_name default absent BACKEND_ERROR cannot recreate resources.registered_models.foo: CreateRegisteredModel Missing required field: schema_name (400 INVALID_PARAMETER_VALUE) === summary OK 51 OK_RECREATE 2 +OK_ID_FIELD_REQUIRED 8 SUPPRESSED 1 NOT_OBSERVABLE 1 UPDATE_IGNORED 1 -BACKEND_ERROR 4 SKIPPED 9 -BASE_ERROR 4 diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/tests/report_test.go index f475772c266..95cbf559ebc 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/tests/report_test.go @@ -88,6 +88,13 @@ const ( // The value could not be written into the config at all: the parent object is // absent from this base config, or the type does not accept the value. + // The field composes the resource's ID, and the value under test is absent. There is no + // resource to create without its name, so the backend refusing the create states the API's + // contract rather than a defect. Read off the resource's own provided_id_fields declaration, + // never off the error: which field the message happens to name is not evidence, since a + // recreate carries the whole resource. + verdictIDFieldRequired verdict = "OK_ID_FIELD_REQUIRED" + verdictUnsettable verdict = "UNSETTABLE" // Left out on purpose by the resource's value library, with a reason. verdictSkipped verdict = "SKIPPED" @@ -143,7 +150,8 @@ var benignSuppressions = map[string]bool{ // report lists only these; the .full.txt companion lists everything. func (r result) isProblem() bool { switch r.verdict { - case verdictOK, verdictRecreate, verdictNotObservable, verdictSkipped, verdictInertConfirmed: + case verdictOK, verdictRecreate, verdictNotObservable, verdictSkipped, verdictInertConfirmed, + verdictIDFieldRequired: return false case verdictSuppressed: return !benignSuppressions[r.detail] @@ -326,7 +334,7 @@ func (r *report) render(problemsOnly bool) string { sb.WriteString("\n=== summary\n") for _, v := range []verdict{ - verdictOK, verdictRecreate, verdictInertConfirmed, verdictSuppressed, + verdictOK, verdictRecreate, verdictIDFieldRequired, verdictInertConfirmed, verdictSuppressed, verdictNotObservable, verdictNoPlan, verdictInertViolated, verdictBaselineDrift, verdictStaleRead, verdictUpdateIgnored, verdictDrift, verdictCollateral, verdictDriftChild, diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/tests/values_test.go index de06ef2951d..b4450757076 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/tests/values_test.go @@ -111,6 +111,19 @@ func declaredDeliberate(adapter *dresources.Adapter) []dresources.FieldRule { return rules } +// declaredIDFields collects the fields the resource declares as composing its ID -- its name, +// as opposed to a server-generated id. A resource cannot exist without one: the engine fetches +// it by that ID, and a local change to one recreates rather than updates (see adapter.go's +// recreateOnChange). So an absent ID field has no resource to create, and the backend refusing +// to create it is the API contract, not a finding. +func declaredIDFields(adapter *dresources.Adapter) []dresources.FieldRule { + var rules []dresources.FieldRule + for _, cfg := range lifecycleConfigs(adapter) { + rules = append(rules, cfg.ProvidedIDFields...) + } + return rules +} + func lifecycleConfigs(adapter *dresources.Adapter) []*dresources.ResourceLifecycleConfig { var out []*dresources.ResourceLifecycleConfig for _, cfg := range []*dresources.ResourceLifecycleConfig{adapter.ResourceConfig(), adapter.GeneratedResourceConfig()} { From 01c45ed1c20963bc591effe6c560f30384de136e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 21:25:20 +0200 Subject: [PATCH 075/118] Field catalog: stop a field's own baseline drift from whitewashing it Nine transitions were recorded OK that had in fact been ignored by the backend, and the mechanism was invisible: after a rebuild the suite re-measures which fields are already drifting, and any field in that set was then excused for the rest of the run -- including the field under test, and including on rows that exist to detect exactly that. Unlike the initial measurement, a re-measured baseline is not reported, so nothing in the output said why. Existing drift is only an excuse when it belongs to something else: the planner records a change against the key it diffed at, which can be an ancestor several fields share. Drift at the field's own key is the field not holding its value, which is the finding. Surfaced: registered_models full_name and owner (both computed by the backend), pipelines dry_run, sql_warehouses name cleared, and genie_spaces title cleared. Found because the fake server was hiding it locally: a genie space created with no title comes back named "New Agent", so the config and the remote disagree for as long as it lives. The fake stored an empty title, showed a clean plan, and the whitewash never triggered -- only the cloud run saw it. Teaching the fake that default is what makes the two agree, and the real API's own words are the evidence: it names an omitted title "New Agent" and rejects an explicit empty one outright with "Display name cannot be empty". Co-authored-by: Isaac --- bundle/direct/tests/fields_test.go | 15 ++++++++++++++- bundle/direct/tests/output/pipelines.txt | 5 +++-- bundle/direct/tests/output/registered_models.txt | 11 +++++++++-- bundle/direct/tests/output/sql_warehouses.txt | 5 +++-- libs/testserver/genie_spaces.go | 11 +++++++++++ 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/tests/fields_test.go index fcfcf3915b6..1a088b0b08f 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/tests/fields_test.go @@ -419,6 +419,19 @@ func baselineCovers(baseline map[string]bool, plan *deployplan.Plan, node, path return ok && baseline[key] } +// baselineCoversOther reports whether the drift this field would be blamed for was already +// there *and* belongs to something else. The planner records a change against the key it +// diffed at, which can be an ancestor several fields share, so existing drift at that ancestor +// says nothing about whether this field's write landed. +// +// Drift at the field's own key is a different matter: that is this field not holding its value, +// which is the very thing the transition set out to observe. Excusing it turned an ignored +// write into OK -- and silently, since a baseline re-measured after a rebuild is not reported. +func baselineCoversOther(baseline map[string]bool, plan *deployplan.Plan, node, path string) bool { + key, _, ok := relatedChangeKey(plan, node, path) + return ok && key != path && baseline[key] +} + // converged reports whether a plan no longer wants to change the field, at whatever // granularity the planner recorded it. func converged(plan *deployplan.Plan, node, path string) bool { @@ -561,7 +574,7 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // One read cannot tell that apart from a read that was merely stale, so take a second // one. If the value has appeared by then the write did land and the first read was // behind; if it still has not, the field really was ignored. - if !baselineCovers(baseline, plan, h.node, path) && wasIgnored(plan, after, h.node, path) { + if !baselineCoversOther(baseline, plan, h.node, path) && wasIgnored(plan, after, h.node, path) { second, diags := h.readPlan() if diags.HasError() { res.verdict = verdictPlanError diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/tests/output/pipelines.txt index 66d87ec33e5..b100335a9c1 100644 --- a/bundle/direct/tests/output/pipelines.txt +++ b/bundle/direct/tests/output/pipelines.txt @@ -1,11 +1,12 @@ === pipeline.yml.tmpl dry_run absent true UPDATE_IGNORED +dry_run false true UPDATE_IGNORED === summary -OK 743 +OK 742 OK_RECREATE 9 OK_INERT 12 SUPPRESSED 46 NOT_OBSERVABLE 29 -UPDATE_IGNORED 1 +UPDATE_IGNORED 2 SKIPPED 12 diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/tests/output/registered_models.txt index cd00fac3721..c3ce22a85c3 100644 --- a/bundle/direct/tests/output/registered_models.txt +++ b/bundle/direct/tests/output/registered_models.txt @@ -1,11 +1,18 @@ === registered_model.yml.tmpl +full_name absent x UPDATE_IGNORED full_name absent y UPDATE_IGNORED +full_name x y UPDATE_IGNORED +full_name y x UPDATE_IGNORED +owner absent x UPDATE_IGNORED +owner absent y UPDATE_IGNORED +owner x y UPDATE_IGNORED +owner y x UPDATE_IGNORED === summary -OK 51 +OK 44 OK_RECREATE 2 OK_ID_FIELD_REQUIRED 8 SUPPRESSED 1 NOT_OBSERVABLE 1 -UPDATE_IGNORED 1 +UPDATE_IGNORED 8 SKIPPED 9 diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/tests/output/sql_warehouses.txt index 32e74992024..3674a3bd567 100644 --- a/bundle/direct/tests/output/sql_warehouses.txt +++ b/bundle/direct/tests/output/sql_warehouses.txt @@ -6,12 +6,13 @@ cluster_size X-Small absent UPDATE_IGNORE enable_photon true absent UPDATE_IGNORED max_num_clusters 2 absent UPDATE_IGNORED max_num_clusters 3 absent UPDATE_IGNORED +name x-UNIQUE absent UPDATE_IGNORED name y-UNIQUE absent UPDATE_IGNORED spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy RELIABILITY_OPTIMIZED absent UPDATE_IGNORED === summary -OK 43 +OK 42 SUPPRESSED 3 -UPDATE_IGNORED 10 +UPDATE_IGNORED 11 SKIPPED 5 diff --git a/libs/testserver/genie_spaces.go b/libs/testserver/genie_spaces.go index b60df650dfd..717a6344c8a 100644 --- a/libs/testserver/genie_spaces.go +++ b/libs/testserver/genie_spaces.go @@ -10,6 +10,9 @@ import ( "github.com/databricks/databricks-sdk-go/service/dashboards" ) +// defaultGenieSpaceTitle is what the backend names a space created without a title. +const defaultGenieSpaceTitle = "New Agent" + // generateGenieSpaceId returns a random 32-character hex string. func generateGenieSpaceId() (string, error) { randomBytes := make([]byte, 16) @@ -64,6 +67,14 @@ func (s *FakeWorkspace) GenieSpaceCreate(req Request) Response { } } + // A create that names no title gets one from the backend, not an empty string (observed on + // aws, 2026-08). It matters because the config then has no title while the remote does, so + // the space drifts for as long as it lives -- a fake that stored "" would show a clean plan + // and hide that. + if createReq.Title == "" { + createReq.Title = defaultGenieSpaceTitle + } + // Default to user's home directory if parent_path is not provided (matches cloud behavior) if createReq.ParentPath == "" { createReq.ParentPath = "/Users/" + s.CurrentUser().UserName From 3c9294312433a33bfe56f584174e01424b2840b8 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 21:30:53 +0200 Subject: [PATCH 076/118] Field catalog: rename to autotest, add -sample N and the two cloud tasks The package is bundle/direct/autotest now; `tests` said nothing next to a tree full of tests. -sample N tests N fields per type instead of all of them, which is what makes a real-workspace run affordable on a PR: two fields per type against the fake server is 1s where the full sweep is 12s, and on cloud the difference is minutes against hours. Which fields get picked comes from HEAD, so successive commits cover different ground while one run's picks follow from its SHA. A sampled run is held to the same committed goldens, restricted to the rows of the fields it picked plus any row the harness records against itself -- so a type that no longer deploys fails the check however few fields were sampled. The summary counts every field and has no subset, so it is not compared, and a change between two passing verdicts shows only in the full run. -sample refuses to run with -update, which would truncate a golden to the sample. Tasks: autotest-cloud (every field, nightly) and autotest-cloud-pr (-sample 2). The local run needs no task, it is already part of ./task test. Co-authored-by: Isaac --- Taskfile.yml | 19 +++++- bundle/direct/{tests => autotest}/.gitignore | 0 bundle/direct/{tests => autotest}/README.md | 26 ++++++-- .../direct/{tests => autotest}/corpus_test.go | 2 +- .../direct/{tests => autotest}/fields_test.go | 66 +++++++++++++++++-- .../{tests => autotest}/harness_test.go | 2 +- .../{tests => autotest}/output/alerts.txt | 0 .../{tests => autotest}/output/apps.txt | 0 .../{tests => autotest}/output/catalogs.txt | 0 .../output/cluster_policies.txt | 0 .../{tests => autotest}/output/clusters.txt | 0 .../{tests => autotest}/output/configs.txt | 0 .../{tests => autotest}/output/dashboards.txt | 0 .../output/database_instances.txt | 0 .../output/experiments.txt | 0 .../output/external_locations.txt | 0 .../output/genie_spaces.txt | 0 .../output/instance_pools.txt | 0 .../{tests => autotest}/output/jobs.txt | 0 .../output/model_serving_endpoints.txt | 0 .../{tests => autotest}/output/models.txt | 0 .../{tests => autotest}/output/pipelines.txt | 0 .../output/postgres_projects.txt | 0 .../output/postgres_synced_tables.txt | 0 .../output/registered_models.txt | 0 .../{tests => autotest}/output/schemas.txt | 0 .../output/secret_scopes.txt | 0 .../output/sql_warehouses.txt | 0 .../{tests => autotest}/output/volumes.txt | 0 .../direct/{tests => autotest}/report_test.go | 51 +++++++++++++- .../testdata/fields/alerts.yml | 0 .../testdata/fields/apps.yml | 0 .../testdata/fields/catalogs.yml | 0 .../testdata/fields/cluster_policies.yml | 0 .../testdata/fields/clusters.yml | 0 .../testdata/fields/dashboards.yml | 0 .../testdata/fields/database_instances.yml | 0 .../testdata/fields/experiments.yml | 0 .../testdata/fields/external_locations.yml | 0 .../testdata/fields/genie_spaces.yml | 0 .../testdata/fields/instance_pools.yml | 0 .../testdata/fields/jobs.yml | 0 .../fields/model_serving_endpoints.yml | 0 .../testdata/fields/models.yml | 0 .../testdata/fields/pipelines.yml | 0 .../testdata/fields/postgres_projects.yml | 0 .../fields/postgres_synced_tables.yml | 0 .../testdata/fields/registered_models.yml | 0 .../testdata/fields/schemas.yml | 0 .../testdata/fields/sql_warehouses.yml | 0 .../testdata/fields/volumes.yml | 0 .../direct/{tests => autotest}/values_test.go | 4 +- .../{tests => autotest}/values_unit_test.go | 2 +- libs/testserver/jobs.go | 2 +- 54 files changed, 157 insertions(+), 17 deletions(-) rename bundle/direct/{tests => autotest}/.gitignore (100%) rename bundle/direct/{tests => autotest}/README.md (91%) rename bundle/direct/{tests => autotest}/corpus_test.go (99%) rename bundle/direct/{tests => autotest}/fields_test.go (93%) rename bundle/direct/{tests => autotest}/harness_test.go (99%) rename bundle/direct/{tests => autotest}/output/alerts.txt (100%) rename bundle/direct/{tests => autotest}/output/apps.txt (100%) rename bundle/direct/{tests => autotest}/output/catalogs.txt (100%) rename bundle/direct/{tests => autotest}/output/cluster_policies.txt (100%) rename bundle/direct/{tests => autotest}/output/clusters.txt (100%) rename bundle/direct/{tests => autotest}/output/configs.txt (100%) rename bundle/direct/{tests => autotest}/output/dashboards.txt (100%) rename bundle/direct/{tests => autotest}/output/database_instances.txt (100%) rename bundle/direct/{tests => autotest}/output/experiments.txt (100%) rename bundle/direct/{tests => autotest}/output/external_locations.txt (100%) rename bundle/direct/{tests => autotest}/output/genie_spaces.txt (100%) rename bundle/direct/{tests => autotest}/output/instance_pools.txt (100%) rename bundle/direct/{tests => autotest}/output/jobs.txt (100%) rename bundle/direct/{tests => autotest}/output/model_serving_endpoints.txt (100%) rename bundle/direct/{tests => autotest}/output/models.txt (100%) rename bundle/direct/{tests => autotest}/output/pipelines.txt (100%) rename bundle/direct/{tests => autotest}/output/postgres_projects.txt (100%) rename bundle/direct/{tests => autotest}/output/postgres_synced_tables.txt (100%) rename bundle/direct/{tests => autotest}/output/registered_models.txt (100%) rename bundle/direct/{tests => autotest}/output/schemas.txt (100%) rename bundle/direct/{tests => autotest}/output/secret_scopes.txt (100%) rename bundle/direct/{tests => autotest}/output/sql_warehouses.txt (100%) rename bundle/direct/{tests => autotest}/output/volumes.txt (100%) rename bundle/direct/{tests => autotest}/report_test.go (91%) rename bundle/direct/{tests => autotest}/testdata/fields/alerts.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/apps.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/catalogs.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/cluster_policies.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/clusters.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/dashboards.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/database_instances.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/experiments.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/external_locations.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/genie_spaces.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/instance_pools.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/jobs.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/model_serving_endpoints.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/models.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/pipelines.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/postgres_projects.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/postgres_synced_tables.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/registered_models.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/schemas.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/sql_warehouses.yml (100%) rename bundle/direct/{tests => autotest}/testdata/fields/volumes.yml (100%) rename bundle/direct/{tests => autotest}/values_test.go (99%) rename bundle/direct/{tests => autotest}/values_unit_test.go (99%) diff --git a/Taskfile.yml b/Taskfile.yml index b0aaa42ef9b..082e3432f5b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -10,7 +10,7 @@ vars: TEST_PACKAGES: ./acceptance/internal ./libs/... ./internal/... ./cmd/... ./bundle/... ./experimental/ssh/... . # Field-support catalog. Covered by TEST_PACKAGES for local/CI runs via ./bundle/..., # but named separately because the cloud tasks below list their packages explicitly. - FIELDS_PACKAGE: ./bundle/direct/tests + FIELDS_PACKAGE: ./bundle/direct/autotest ACCEPTANCE_TEST_FILTER: "" # Single brace-expansion glob covering every //go:embed target in the repo, # computed by grepping `//go:embed` directives. Evaluated lazily by Task so @@ -590,6 +590,23 @@ tasks: cmds: - "go test {{.FIELDS_PACKAGE}} -run '^TestFields$' -update -timeout=${LOCAL_TIMEOUT:-60m}" + # The catalog runs against the fake server as part of `./task test`, and needs no entry of its + # own for that. These two are the real-workspace runs: they need CLOUD_ENV and credentials, and + # hold the same committed reports as the local run, so a divergence is the fake server or the + # engine differing from the backend. + autotest-cloud: + desc: Run the resource field-support catalog against a real workspace (every field; nightly) + cmds: + - "go test {{.FIELDS_PACKAGE}} -run '^TestFields$' -count=1 -timeout=${CLOUD_TIMEOUT:-300m}" + + autotest-cloud-pr: + desc: Run a two-field sample of the field-support catalog against a real workspace (PRs) + cmds: + # Which fields are sampled comes from HEAD, so each commit covers different ground. This + # checks that every resource type still deploys and that the sampled fields' findings read + # as recorded; a change between two passing verdicts only shows in the full run above. + - "go test {{.FIELDS_PACKAGE}} -run '^TestFields$' -count=1 -sample 2 -timeout=${CLOUD_TIMEOUT:-90m}" + test-update-templates: desc: Update acceptance test template output sources: *ACC_SOURCES_UPDATE diff --git a/bundle/direct/tests/.gitignore b/bundle/direct/autotest/.gitignore similarity index 100% rename from bundle/direct/tests/.gitignore rename to bundle/direct/autotest/.gitignore diff --git a/bundle/direct/tests/README.md b/bundle/direct/autotest/README.md similarity index 91% rename from bundle/direct/tests/README.md rename to bundle/direct/autotest/README.md index 5fd84e5e7f0..1b6ab374529 100644 --- a/bundle/direct/tests/README.md +++ b/bundle/direct/autotest/README.md @@ -165,17 +165,33 @@ the report as `SKIPPED` so it is not mistaken for coverage. ## Running it ```bash -go test ./bundle/direct/tests # against the testserver -go test ./bundle/direct/tests -run 'TestFields/schemas' -v -./task test-update-fields # regenerate the goldens +go test ./bundle/direct/autotest # against the testserver +go test ./bundle/direct/autotest -run 'TestFields/schemas' -v +./task test-update-fields # regenerate the goldens ``` +The local run is part of `./task test`, so it needs no task of its own. The real-workspace runs +do, since they need `CLOUD_ENV` and credentials: + +```bash +./task autotest-cloud # every field; nightly +./task autotest-cloud-pr # -sample 2; PRs +``` + +`-sample N` tests N of each type's fields instead of all of them, picked from HEAD so that +successive commits cover different ground and one run's picks follow from its SHA. The reports +stay the same committed goldens: a sampled run is held to the rows belonging to the fields it +picked, plus any row the harness records against itself, which is what catches a type that no +longer deploys at all. The summary counts every field and has no meaningful subset, so it is +not compared -- a change between two *passing* verdicts shows up only in the full run. +`-sample` refuses to run with `-update`, which would truncate the goldens to the sample. + One transition is addressable on its own, and the subtest names carry no shell metacharacters so no quoting is needed. `-v` prints the post-deploy plan behind any problem verdict: ```bash -go test ./bundle/direct/tests -run TestFields/pipelines/.*/dry_run/absent_to_true -v +go test ./bundle/direct/autotest -run TestFields/pipelines/.*/dry_run/absent_to_true -v ``` ### Checking one field against a real workspace @@ -184,7 +200,7 @@ A whole resource type on cloud is slow (jobs is half an hour) and the interestin usually about a handful of fields. Every field is a subtest, so a few can be driven on their own: - CLOUD_ENV=aws go test ./bundle/direct/tests \ + CLOUD_ENV=aws go test ./bundle/direct/autotest \ -run 'TestFields/^apps$/[^/]*/^(budget_policy_id|usage_policy_id)$' The golden comparison fails on a partial run, which is expected: read the rows in diff --git a/bundle/direct/tests/corpus_test.go b/bundle/direct/autotest/corpus_test.go similarity index 99% rename from bundle/direct/tests/corpus_test.go rename to bundle/direct/autotest/corpus_test.go index 3913ecc08cc..9d349992b21 100644 --- a/bundle/direct/tests/corpus_test.go +++ b/bundle/direct/autotest/corpus_test.go @@ -1,4 +1,4 @@ -package tests +package autotest import ( "encoding/json" diff --git a/bundle/direct/tests/fields_test.go b/bundle/direct/autotest/fields_test.go similarity index 93% rename from bundle/direct/tests/fields_test.go rename to bundle/direct/autotest/fields_test.go index 1a088b0b08f..30e1bb8c7ee 100644 --- a/bundle/direct/tests/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -1,4 +1,4 @@ -// Package tests exercises the direct engine end to end -- plan, apply, and the plan +// Package autotest exercises the direct engine end to end -- plan, apply, and the plan // that follows -- rather than any one resource implementation. It lives beside the // engine for that reason, not under dresources. // @@ -27,16 +27,21 @@ // - remote drift (a change made outside the bundle); this suite only edits config // - configs with more than one resource, or with an -init.sh // - fields under a slice or map (listed at the end of each report) -package tests +package autotest import ( "bytes" "context" + "crypto/sha256" + "encoding/binary" "encoding/json" "errors" + "flag" "fmt" "hash/fnv" "maps" + "math/rand/v2" + "os/exec" "reflect" "regexp" "slices" @@ -48,6 +53,7 @@ import ( "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/testdiff" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/stretchr/testify/require" @@ -56,11 +62,23 @@ import ( func TestFields(t *testing.T) { usable, skipped := discoverConfigs(t) + // -update writes the report as the whole truth for a type, so it cannot come from a run + // that tested a few fields: the missing ones would read as removed. + if *sampleSize > 0 && testdiff.OverwriteMode { + t.Fatal("-sample cannot be combined with -update: a sampled run would truncate the reports") + } + // The order fields are tested in, and the order each field's values are visited in, // come from this seed. runSeed := orderSeed t.Logf("field and value order seeded from orderSeed %d", runSeed) + var sampleFields uint64 + if *sampleSize > 0 { + sampleFields = sampleSeed(t) + t.Logf("testing %d fields per resource type, seeded from HEAD (%d)", *sampleSize, sampleFields) + } + // One config per resource type: the simplest one, which is the shortest name -- a // ".yml.tmpl" sorts before its "_.yml.tmpl" siblings. The variants // exist to exercise specific engine behaviour, not extra fields, and anything a variant @@ -113,7 +131,7 @@ func TestFields(t *testing.T) { rep := &report{resourceType: resourceType} //exhaustruct:ignore t.Run(cfg.name, func(t *testing.T) { - runConfig(t, ctx, client, user, cfg, fv, rep, runSeed) + runConfig(t, ctx, client, user, cfg, fv, rep, runSeed, sampleFields) }) rep.write(t) }) @@ -123,7 +141,7 @@ func TestFields(t *testing.T) { // runConfig drives one config. Every resource it creates belongs to this test's lifetime, // which is what `owner` below carries into the subtests: a resource a subtest asks for is // reused by the transitions after it, so its cleanup cannot be the subtest's. -func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, rep *report, runSeed uint64) { +func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, rep *report, runSeed, sampleSeedValue uint64) { owner := t adapter, err := dresources.NewAdapter(dresources.SupportedResources[cfg.resourceType], cfg.resourceType, client) require.NoError(t, err) @@ -144,6 +162,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl fields, uncovered, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, resource, runSeed, declaredUnsettable(adapter), h.unique) rep.addCoverage(fields, uncovered) + fields = sample(fields, sampleSeedValue, rep) // A container's label says only how many entries it has, so record what each one stands for. for _, f := range fields { @@ -290,6 +309,27 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl } } +// sample cuts the field list down to -sample entries, drawn without replacement. Sorting first +// makes the draw depend only on the seed, not on the order enumeration happened to produce. +// The chosen paths go to the report, which needs them to compare a partial run against a +// golden written by a full one. +func sample(fields []field, seed uint64, rep *report) []field { + // A type with no more fields than the sample size is covered in full, so its report stays + // comparable whole -- rep.sampled is left nil. + if *sampleSize <= 0 || len(fields) <= *sampleSize { + return fields + } + ordered := slices.Clone(fields) + slices.SortFunc(ordered, func(a, b field) int { return strings.Compare(a.path, b.path) }) + rng := rand.New(rand.NewPCG(seed, 2)) + rng.Shuffle(len(ordered), func(i, j int) { ordered[i], ordered[j] = ordered[j], ordered[i] }) + ordered = ordered[:*sampleSize] + for _, f := range ordered { + rep.addSampled(f.path) + } + return ordered +} + // sameField reports whether a drifting path is the field under test. The planner may report // a change against an ancestor rather than the leaf -- a whole "config" block instead of // "config.auto_capture_config.catalog_name" -- and that is still the field's own drift, not @@ -999,6 +1039,24 @@ func simplerConfig(a, b string) bool { return a < b } +// sampleSize limits how many of a type's fields are tested, for a run that has to be cheap +// rather than exhaustive -- a PR check against a real workspace. Which fields are picked comes +// from the commit, so successive commits cover different ground, and a whole run's picks are +// reproducible from its SHA alone. +var sampleSize = flag.Int("sample", 0, "test only N randomly chosen fields per resource type (0 tests every field)") + +// sampleSeed derives the sample's seed from HEAD. Unlike orderSeed it may move freely: a +// sampled run compares each field's rows against the same golden as a full run, and never +// rewrites it, so the seed cannot invalidate anything committed. +func sampleSeed(t *testing.T) uint64 { + out, err := exec.Command("git", "rev-parse", "HEAD").Output() + // No fallback: a run that cannot name its commit would pick fields no one can reproduce, + // which is the one property sampling has to keep. + require.NoError(t, err, "sampling needs the commit to seed from") + sum := sha256.Sum256(out) + return binary.BigEndian.Uint64(sum[:8]) +} + // orderSeed fixes the order fields are tested in and the order each field's values are // visited in. Bump it to explore a different order, and regenerate the reports. // diff --git a/bundle/direct/tests/harness_test.go b/bundle/direct/autotest/harness_test.go similarity index 99% rename from bundle/direct/tests/harness_test.go rename to bundle/direct/autotest/harness_test.go index 9bc3befbde6..14988cc9a9b 100644 --- a/bundle/direct/tests/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -1,4 +1,4 @@ -package tests +package autotest import ( "context" diff --git a/bundle/direct/tests/output/alerts.txt b/bundle/direct/autotest/output/alerts.txt similarity index 100% rename from bundle/direct/tests/output/alerts.txt rename to bundle/direct/autotest/output/alerts.txt diff --git a/bundle/direct/tests/output/apps.txt b/bundle/direct/autotest/output/apps.txt similarity index 100% rename from bundle/direct/tests/output/apps.txt rename to bundle/direct/autotest/output/apps.txt diff --git a/bundle/direct/tests/output/catalogs.txt b/bundle/direct/autotest/output/catalogs.txt similarity index 100% rename from bundle/direct/tests/output/catalogs.txt rename to bundle/direct/autotest/output/catalogs.txt diff --git a/bundle/direct/tests/output/cluster_policies.txt b/bundle/direct/autotest/output/cluster_policies.txt similarity index 100% rename from bundle/direct/tests/output/cluster_policies.txt rename to bundle/direct/autotest/output/cluster_policies.txt diff --git a/bundle/direct/tests/output/clusters.txt b/bundle/direct/autotest/output/clusters.txt similarity index 100% rename from bundle/direct/tests/output/clusters.txt rename to bundle/direct/autotest/output/clusters.txt diff --git a/bundle/direct/tests/output/configs.txt b/bundle/direct/autotest/output/configs.txt similarity index 100% rename from bundle/direct/tests/output/configs.txt rename to bundle/direct/autotest/output/configs.txt diff --git a/bundle/direct/tests/output/dashboards.txt b/bundle/direct/autotest/output/dashboards.txt similarity index 100% rename from bundle/direct/tests/output/dashboards.txt rename to bundle/direct/autotest/output/dashboards.txt diff --git a/bundle/direct/tests/output/database_instances.txt b/bundle/direct/autotest/output/database_instances.txt similarity index 100% rename from bundle/direct/tests/output/database_instances.txt rename to bundle/direct/autotest/output/database_instances.txt diff --git a/bundle/direct/tests/output/experiments.txt b/bundle/direct/autotest/output/experiments.txt similarity index 100% rename from bundle/direct/tests/output/experiments.txt rename to bundle/direct/autotest/output/experiments.txt diff --git a/bundle/direct/tests/output/external_locations.txt b/bundle/direct/autotest/output/external_locations.txt similarity index 100% rename from bundle/direct/tests/output/external_locations.txt rename to bundle/direct/autotest/output/external_locations.txt diff --git a/bundle/direct/tests/output/genie_spaces.txt b/bundle/direct/autotest/output/genie_spaces.txt similarity index 100% rename from bundle/direct/tests/output/genie_spaces.txt rename to bundle/direct/autotest/output/genie_spaces.txt diff --git a/bundle/direct/tests/output/instance_pools.txt b/bundle/direct/autotest/output/instance_pools.txt similarity index 100% rename from bundle/direct/tests/output/instance_pools.txt rename to bundle/direct/autotest/output/instance_pools.txt diff --git a/bundle/direct/tests/output/jobs.txt b/bundle/direct/autotest/output/jobs.txt similarity index 100% rename from bundle/direct/tests/output/jobs.txt rename to bundle/direct/autotest/output/jobs.txt diff --git a/bundle/direct/tests/output/model_serving_endpoints.txt b/bundle/direct/autotest/output/model_serving_endpoints.txt similarity index 100% rename from bundle/direct/tests/output/model_serving_endpoints.txt rename to bundle/direct/autotest/output/model_serving_endpoints.txt diff --git a/bundle/direct/tests/output/models.txt b/bundle/direct/autotest/output/models.txt similarity index 100% rename from bundle/direct/tests/output/models.txt rename to bundle/direct/autotest/output/models.txt diff --git a/bundle/direct/tests/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt similarity index 100% rename from bundle/direct/tests/output/pipelines.txt rename to bundle/direct/autotest/output/pipelines.txt diff --git a/bundle/direct/tests/output/postgres_projects.txt b/bundle/direct/autotest/output/postgres_projects.txt similarity index 100% rename from bundle/direct/tests/output/postgres_projects.txt rename to bundle/direct/autotest/output/postgres_projects.txt diff --git a/bundle/direct/tests/output/postgres_synced_tables.txt b/bundle/direct/autotest/output/postgres_synced_tables.txt similarity index 100% rename from bundle/direct/tests/output/postgres_synced_tables.txt rename to bundle/direct/autotest/output/postgres_synced_tables.txt diff --git a/bundle/direct/tests/output/registered_models.txt b/bundle/direct/autotest/output/registered_models.txt similarity index 100% rename from bundle/direct/tests/output/registered_models.txt rename to bundle/direct/autotest/output/registered_models.txt diff --git a/bundle/direct/tests/output/schemas.txt b/bundle/direct/autotest/output/schemas.txt similarity index 100% rename from bundle/direct/tests/output/schemas.txt rename to bundle/direct/autotest/output/schemas.txt diff --git a/bundle/direct/tests/output/secret_scopes.txt b/bundle/direct/autotest/output/secret_scopes.txt similarity index 100% rename from bundle/direct/tests/output/secret_scopes.txt rename to bundle/direct/autotest/output/secret_scopes.txt diff --git a/bundle/direct/tests/output/sql_warehouses.txt b/bundle/direct/autotest/output/sql_warehouses.txt similarity index 100% rename from bundle/direct/tests/output/sql_warehouses.txt rename to bundle/direct/autotest/output/sql_warehouses.txt diff --git a/bundle/direct/tests/output/volumes.txt b/bundle/direct/autotest/output/volumes.txt similarity index 100% rename from bundle/direct/tests/output/volumes.txt rename to bundle/direct/autotest/output/volumes.txt diff --git a/bundle/direct/tests/report_test.go b/bundle/direct/autotest/report_test.go similarity index 91% rename from bundle/direct/tests/report_test.go rename to bundle/direct/autotest/report_test.go index 95cbf559ebc..b2d1e398f0d 100644 --- a/bundle/direct/tests/report_test.go +++ b/bundle/direct/autotest/report_test.go @@ -1,4 +1,4 @@ -package tests +package autotest import ( "cmp" @@ -189,6 +189,20 @@ type report struct { // legend maps a size label back to the value it stands for, keyed by field: "keys1" means // a different map for properties than it does for options, so the field is part of the key. legend map[legendKey]string + + // sampled holds the fields a -sample run tested, and is nil when every field was tested. + // The report is then compared against the same subset of its golden rather than whole. + sampled map[string]bool +} + +// addSampled records that this field is one of the sampled ones. +func (r *report) addSampled(path string) { + r.mu.Lock() + defer r.mu.Unlock() + if r.sampled == nil { + r.sampled = map[string]bool{} + } + r.sampled[path] = true } // legendKey identifies one size label of one field. @@ -417,6 +431,20 @@ func (r *report) write(t testutil.TestingT) { return } + // A sampled run knows about a few of the type's fields, so its report is held to those + // fields' rows and nothing else. Their verdicts must read exactly as the golden records + // them; the summary counts every field and has no meaningful subset, so both sides drop it. + if r.sampled != nil { + expected, err := os.ReadFile(name) + if err != nil { + t.Errorf("reading %s: %s (run ./task test-update-fields)", name, err) + return + } + want := sampledRows(testdiff.NormalizeNewlines(string(expected)), r.sampled) + testdiff.AssertEqualTexts(t, name, name, want, sampledRows(body, r.sampled)) + return + } + // Not tolerated as missing: a report with no golden would silently pass, so adding a // resource type without generating its report, or deleting one, would go unnoticed. expected, err := os.ReadFile(name) @@ -427,6 +455,27 @@ func (r *report) write(t testutil.TestingT) { testdiff.AssertEqualTexts(t, name, name, testdiff.NormalizeNewlines(string(expected)), body) } +// sampledRows keeps the report rows belonging to the sampled fields, dropping section headers, +// blank lines and the summary. Rows the harness records against itself rather than a field -- +// "(base config)", "(rebuild)" -- are always kept: they mean the run for that type did not +// happen, which no sample should be allowed to hide. +func sampledRows(body string, sampled map[string]bool) string { + var sb strings.Builder + for line := range strings.SplitSeq(body, "\n") { + if strings.HasPrefix(line, "=== summary") { + break + } + if line == "" || strings.HasPrefix(line, "=== ") { + continue + } + field, _, _ := strings.Cut(line, " ") + if sampled[field] || strings.HasPrefix(field, "(") { + sb.WriteString(line + "\n") + } + } + return sb.String() +} + func writeReport(t testutil.TestingT, name, body string) { if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil { t.Errorf("creating %s: %s", filepath.Dir(name), err) diff --git a/bundle/direct/tests/testdata/fields/alerts.yml b/bundle/direct/autotest/testdata/fields/alerts.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/alerts.yml rename to bundle/direct/autotest/testdata/fields/alerts.yml diff --git a/bundle/direct/tests/testdata/fields/apps.yml b/bundle/direct/autotest/testdata/fields/apps.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/apps.yml rename to bundle/direct/autotest/testdata/fields/apps.yml diff --git a/bundle/direct/tests/testdata/fields/catalogs.yml b/bundle/direct/autotest/testdata/fields/catalogs.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/catalogs.yml rename to bundle/direct/autotest/testdata/fields/catalogs.yml diff --git a/bundle/direct/tests/testdata/fields/cluster_policies.yml b/bundle/direct/autotest/testdata/fields/cluster_policies.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/cluster_policies.yml rename to bundle/direct/autotest/testdata/fields/cluster_policies.yml diff --git a/bundle/direct/tests/testdata/fields/clusters.yml b/bundle/direct/autotest/testdata/fields/clusters.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/clusters.yml rename to bundle/direct/autotest/testdata/fields/clusters.yml diff --git a/bundle/direct/tests/testdata/fields/dashboards.yml b/bundle/direct/autotest/testdata/fields/dashboards.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/dashboards.yml rename to bundle/direct/autotest/testdata/fields/dashboards.yml diff --git a/bundle/direct/tests/testdata/fields/database_instances.yml b/bundle/direct/autotest/testdata/fields/database_instances.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/database_instances.yml rename to bundle/direct/autotest/testdata/fields/database_instances.yml diff --git a/bundle/direct/tests/testdata/fields/experiments.yml b/bundle/direct/autotest/testdata/fields/experiments.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/experiments.yml rename to bundle/direct/autotest/testdata/fields/experiments.yml diff --git a/bundle/direct/tests/testdata/fields/external_locations.yml b/bundle/direct/autotest/testdata/fields/external_locations.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/external_locations.yml rename to bundle/direct/autotest/testdata/fields/external_locations.yml diff --git a/bundle/direct/tests/testdata/fields/genie_spaces.yml b/bundle/direct/autotest/testdata/fields/genie_spaces.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/genie_spaces.yml rename to bundle/direct/autotest/testdata/fields/genie_spaces.yml diff --git a/bundle/direct/tests/testdata/fields/instance_pools.yml b/bundle/direct/autotest/testdata/fields/instance_pools.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/instance_pools.yml rename to bundle/direct/autotest/testdata/fields/instance_pools.yml diff --git a/bundle/direct/tests/testdata/fields/jobs.yml b/bundle/direct/autotest/testdata/fields/jobs.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/jobs.yml rename to bundle/direct/autotest/testdata/fields/jobs.yml diff --git a/bundle/direct/tests/testdata/fields/model_serving_endpoints.yml b/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/model_serving_endpoints.yml rename to bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml diff --git a/bundle/direct/tests/testdata/fields/models.yml b/bundle/direct/autotest/testdata/fields/models.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/models.yml rename to bundle/direct/autotest/testdata/fields/models.yml diff --git a/bundle/direct/tests/testdata/fields/pipelines.yml b/bundle/direct/autotest/testdata/fields/pipelines.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/pipelines.yml rename to bundle/direct/autotest/testdata/fields/pipelines.yml diff --git a/bundle/direct/tests/testdata/fields/postgres_projects.yml b/bundle/direct/autotest/testdata/fields/postgres_projects.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/postgres_projects.yml rename to bundle/direct/autotest/testdata/fields/postgres_projects.yml diff --git a/bundle/direct/tests/testdata/fields/postgres_synced_tables.yml b/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/postgres_synced_tables.yml rename to bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml diff --git a/bundle/direct/tests/testdata/fields/registered_models.yml b/bundle/direct/autotest/testdata/fields/registered_models.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/registered_models.yml rename to bundle/direct/autotest/testdata/fields/registered_models.yml diff --git a/bundle/direct/tests/testdata/fields/schemas.yml b/bundle/direct/autotest/testdata/fields/schemas.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/schemas.yml rename to bundle/direct/autotest/testdata/fields/schemas.yml diff --git a/bundle/direct/tests/testdata/fields/sql_warehouses.yml b/bundle/direct/autotest/testdata/fields/sql_warehouses.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/sql_warehouses.yml rename to bundle/direct/autotest/testdata/fields/sql_warehouses.yml diff --git a/bundle/direct/tests/testdata/fields/volumes.yml b/bundle/direct/autotest/testdata/fields/volumes.yml similarity index 100% rename from bundle/direct/tests/testdata/fields/volumes.yml rename to bundle/direct/autotest/testdata/fields/volumes.yml diff --git a/bundle/direct/tests/values_test.go b/bundle/direct/autotest/values_test.go similarity index 99% rename from bundle/direct/tests/values_test.go rename to bundle/direct/autotest/values_test.go index b4450757076..b064c8aabc5 100644 --- a/bundle/direct/tests/values_test.go +++ b/bundle/direct/autotest/values_test.go @@ -1,4 +1,4 @@ -package tests +package autotest import ( "encoding/json" @@ -349,7 +349,7 @@ type transition struct { // label names the transition for the subtest. Kept free of shell metacharacters so a // single case can be re-run without quoting: // -// go test ./bundle/direct/tests -run TestFields/pipelines/.*/dry_run/absent_to_true -v +// go test ./bundle/direct/autotest -run TestFields/pipelines/.*/dry_run/absent_to_true -v func (t transition) label() string { return valueLabel(t.from) + "_to_" + valueLabel(t.to) } diff --git a/bundle/direct/tests/values_unit_test.go b/bundle/direct/autotest/values_unit_test.go similarity index 99% rename from bundle/direct/tests/values_unit_test.go rename to bundle/direct/autotest/values_unit_test.go index 491dcf45fec..d85dbdf1d39 100644 --- a/bundle/direct/tests/values_unit_test.go +++ b/bundle/direct/autotest/values_unit_test.go @@ -1,4 +1,4 @@ -package tests +package autotest import ( "fmt" diff --git a/libs/testserver/jobs.go b/libs/testserver/jobs.go index 232f20cee6a..a4c3e07a63d 100644 --- a/libs/testserver/jobs.go +++ b/libs/testserver/jobs.go @@ -149,7 +149,7 @@ func (s *FakeWorkspace) JobsReset(req Request) Response { // parent_path is the same shape and verified the same way: the folder a job lives in is // fixed when the job is created, and a reset naming a different one is accepted and // ignored. The engine plans an update for it anyway, which is why a config that changes it - // never converges -- recorded in bundle/direct/tests/output/jobs.txt. + // never converges -- recorded in bundle/direct/autotest/output/jobs.txt. request.NewSettings.ParentPath = prevjob.Settings.ParentPath if request.NewSettings.ParentPath == "" { // And a job with no parent folder omits the field on read rather than reporting it From 25920598763edb08adaf6e57b249e68f09b2e83b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 21:39:40 +0200 Subject: [PATCH 077/118] Field catalog: verify a cloud-specific service where it exists Postgres was declared local_only because the service is AWS-only, which meant no workspace ever confirmed those two types -- including the aws workspace that has it. local_only is for a type the suite cannot drive anywhere: external_locations needs a storage credential with IAM behind it, instance_pools cannot be deleted again afterwards. `clouds: [aws]` says where the service exists instead, so the type is verified there and skipped elsewhere, the way acceptance/bundle/resources/postgres_*/test.toml already disables gcp and azure by name. Also switches the sample seed from a `git rev-parse` subprocess to libs/git's LatestCommit, which reads $GIT_DIR/HEAD in process and resolves a worktree's git dir -- this checkout is one. Co-authored-by: Isaac --- bundle/direct/autotest/fields_test.go | 19 ++++++++++++++++--- .../testdata/fields/postgres_projects.yml | 8 +++++--- .../fields/postgres_synced_tables.yml | 8 +++++--- bundle/direct/autotest/values_test.go | 10 +++++++++- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index 30e1bb8c7ee..f028f759f70 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -41,7 +41,7 @@ import ( "hash/fnv" "maps" "math/rand/v2" - "os/exec" + "os" "reflect" "regexp" "slices" @@ -53,7 +53,10 @@ import ( "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/folders" + "github.com/databricks/cli/libs/git" "github.com/databricks/cli/libs/testdiff" + "github.com/databricks/cli/libs/vfs" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/stretchr/testify/require" @@ -125,6 +128,9 @@ func TestFields(t *testing.T) { if fv.localOnly != "" && isCloud() { t.Skipf("local only: %s", fv.localOnly) } + if len(fv.clouds) > 0 && isCloud() && !slices.Contains(fv.clouds, cloudName()) { + t.Skipf("not available on %s: the service exists on %s", cloudName(), strings.Join(fv.clouds, ", ")) + } // Outlives the field-level subtests that rebuild harnesses. ctx := t.Context() @@ -1049,11 +1055,18 @@ var sampleSize = flag.Int("sample", 0, "test only N randomly chosen fields per r // sampled run compares each field's rows against the same golden as a full run, and never // rewrites it, so the seed cannot invalidate anything committed. func sampleSeed(t *testing.T) uint64 { - out, err := exec.Command("git", "rev-parse", "HEAD").Output() + wd, err := os.Getwd() + require.NoError(t, err) + root, err := folders.FindDirWithLeaf(wd, git.GitDirectoryName) + require.NoError(t, err) + repo, err := git.NewRepository(t.Context(), vfs.MustNew(root)) + require.NoError(t, err) + commit, err := repo.LatestCommit() // No fallback: a run that cannot name its commit would pick fields no one can reproduce, // which is the one property sampling has to keep. require.NoError(t, err, "sampling needs the commit to seed from") - sum := sha256.Sum256(out) + require.NotEmpty(t, commit, "sampling needs the commit to seed from") + sum := sha256.Sum256([]byte(commit)) return binary.BigEndian.Uint64(sum[:8]) } diff --git a/bundle/direct/autotest/testdata/fields/postgres_projects.yml b/bundle/direct/autotest/testdata/fields/postgres_projects.yml index d83e4f20359..e5484dd8b00 100644 --- a/bundle/direct/autotest/testdata/fields/postgres_projects.yml +++ b/bundle/direct/autotest/testdata/fields/postgres_projects.yml @@ -1,8 +1,10 @@ # Value library for postgres projects. See ../../README.md. -# Postgres resources are only available on AWS, and the invariant suite -# excludes them from every cloud run for that reason. -local_only: Postgres resources are only available on AWS +# Postgres is an AWS-only service, so an aws workspace is the one place the cloud run can +# confirm this type. The invariant suite drops these configs from every cloud run rather than +# just the ones without the service; acceptance/bundle/resources/postgres_*/test.toml is the +# closer precedent, disabling gcp and azure by name. +clouds: [aws] base: custom_tags: - key: team diff --git a/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml b/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml index ec61b871bc9..a9fe0244211 100644 --- a/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml +++ b/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml @@ -1,8 +1,10 @@ # Value library for postgres synced tables. See ../../README.md. -# Postgres resources are only available on AWS, and the invariant suite -# excludes them from every cloud run for that reason. -local_only: Postgres resources are only available on AWS +# Postgres is an AWS-only service, so an aws workspace is the one place the cloud run can +# confirm this type. The invariant suite drops these configs from every cloud run rather than +# just the ones without the service; acceptance/bundle/resources/postgres_*/test.toml is the +# closer precedent, disabling gcp and azure by name. +clouds: [aws] base: extra_columns: - column_name: ingested_at diff --git a/bundle/direct/autotest/values_test.go b/bundle/direct/autotest/values_test.go index b064c8aabc5..95af13d13f5 100644 --- a/bundle/direct/autotest/values_test.go +++ b/bundle/direct/autotest/values_test.go @@ -61,6 +61,12 @@ type fieldValues struct { // Such a type is skipped on cloud rather than reported: the local golden stands, and a // cloud run neither confirms nor contradicts it. localOnly string + + // clouds names the clouds whose workspaces can host this resource type, for a service that + // exists on some and not others. Empty means every cloud. Unlike localOnly this does not + // give up on the cloud run: the type is verified where the service exists and skipped + // where it does not, which mirrors CloudEnvs in acceptance/bundle/resources/*/test.toml. + clouds []string } // fieldsDir holds the per-resource-type value libraries. @@ -170,7 +176,7 @@ var cliManagedFields = map[string]string{ // corpus configs use so a value can name the workspace's own user rather than a placeholder // only the fake server knows. func loadFieldValues(resourceType string, vars map[string]string) (*fieldValues, error) { - fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, base: nil, localOnly: ""} + fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, base: nil, localOnly: "", clouds: nil} maps.Copy(fv.skip, cliManagedFields) path := filepath.Join(fieldsDir, resourceType+".yml") @@ -199,6 +205,7 @@ func loadFieldValues(resourceType string, vars map[string]string) (*fieldValues, Fields map[string][]any `yaml:"fields"` Base any `yaml:"base"` LocalOnly string `yaml:"local_only"` + Clouds []string `yaml:"clouds"` } if err := yaml.Unmarshal([]byte(expanded), &file); err != nil { return nil, fmt.Errorf("%s: %w", path, err) @@ -207,6 +214,7 @@ func loadFieldValues(resourceType string, vars map[string]string) (*fieldValues, maps.Copy(fv.fields, file.Fields) fv.base = file.Base fv.localOnly = file.LocalOnly + fv.clouds = file.Clouds return fv, nil } From 31c447ee2f928d0c43859efba11092a77847ab2b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 21:44:19 +0200 Subject: [PATCH 078/118] Field catalog: unit-test the sampling Covers what a -sample run rests on: the draw depends on the seed and nothing else, so the same seed picks the same fields whatever order enumeration produced them in; and the golden subset keeps a sampled field's rows, keeps a row the harness records against itself (a type that did not run at all must fail however few fields were picked), and drops the headers and summary that have no per-field meaning. The "a different seed picks differently" case scans a range of seeds instead of comparing two: two of five fields collide once in ten, so the single-comparison version failed on its own about as often as it would have caught anything. It did fail that way on the first run. Co-authored-by: Isaac --- bundle/direct/autotest/values_unit_test.go | 78 ++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/bundle/direct/autotest/values_unit_test.go b/bundle/direct/autotest/values_unit_test.go index d85dbdf1d39..f49a1921058 100644 --- a/bundle/direct/autotest/values_unit_test.go +++ b/bundle/direct/autotest/values_unit_test.go @@ -3,6 +3,8 @@ package autotest import ( "fmt" "reflect" + "slices" + "strings" "testing" "github.com/databricks/cli/libs/structs/structpath" @@ -287,3 +289,79 @@ func TestShortLabel(t *testing.T) { // And the same value always gets the same one. assert.Equal(t, long, shortLabel(`{"spark_version":{"type":"fixed","value":"13.3"}}`)) } + +// A sampled run is held to the rows of the fields it picked. Section headers, blank lines and +// the summary have no per-field meaning and are dropped from both sides; a row the harness +// records against itself is always kept, since it means the type did not run at all. +func TestSampledRows(t *testing.T) { + body := strings.Join([]string{ + "=== schema.yml.tmpl", + "comment x absent UPDATE_IGNORED", + "properties keys1 absent UPDATE_IGNORED", + "(base config) BASE_ERROR boom", + "", + "=== summary", + "OK 16", + "", + }, "\n") + + kept := sampledRows(body, map[string]bool{"properties": true}) + assert.Equal(t, "properties keys1 absent UPDATE_IGNORED\n"+ + "(base config) BASE_ERROR boom\n", kept) + + // Nothing sampled still keeps the harness's own row. + assert.Equal(t, "(base config) BASE_ERROR boom\n", + sampledRows(body, map[string]bool{})) +} + +// The draw is by seed alone, so the same seed picks the same fields whatever order enumeration +// produced, and the picks are recorded for the golden comparison. +func TestSample(t *testing.T) { + defer func(previous int) { sampleSize = &previous }(*sampleSize) + + var fields []field + for _, path := range []string{"a", "b", "c", "d", "e"} { + fields = append(fields, field{path: path}) //exhaustruct:ignore + } + + two := 2 + sampleSize = &two + rep := &report{resourceType: "t"} //exhaustruct:ignore + got := sample(fields, 7, rep) + require.Len(t, got, 2) + assert.Len(t, rep.sampled, 2) + for _, f := range got { + assert.True(t, rep.sampled[f.path], "%s recorded", f.path) + } + + // Same seed, reversed input: the same two fields. + reversed := slices.Clone(fields) + slices.Reverse(reversed) + again := sample(reversed, 7, &report{resourceType: "t"}) //exhaustruct:ignore + assert.Equal(t, paths(got), paths(again)) + // The seed is what decides the pick, so some seed picks something else. Scanned rather than + // asserted against one other seed: two of five fields collide once in ten, which would make + // this test fail on its own about as often as it caught anything. + distinct := map[string]bool{} + for seed := range uint64(20) { + distinct[strings.Join(paths(sample(fields, seed, &report{resourceType: "t"})), ",")] = true //exhaustruct:ignore + } + assert.Greater(t, len(distinct), 1, "the seed decides the pick") + + // A type with no more fields than the sample size is covered whole, and records nothing: + // its report stays comparable in full, summary included. + six := 6 + sampleSize = &six + whole := &report{resourceType: "t"} //exhaustruct:ignore + assert.Len(t, sample(fields, 7, whole), 5) + assert.Nil(t, whole.sampled) +} + +func paths(fields []field) []string { + out := make([]string, 0, len(fields)) + for _, f := range fields { + out = append(out, f.path) + } + slices.Sort(out) + return out +} From 1b390f0cbc60096ffd17c671bfaef1837a5c7b61 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 21:45:08 +0200 Subject: [PATCH 079/118] Field catalog: document clouds: alongside local_only Co-authored-by: Isaac --- bundle/direct/autotest/README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/bundle/direct/autotest/README.md b/bundle/direct/autotest/README.md index 1b6ab374529..30b643456c9 100644 --- a/bundle/direct/autotest/README.md +++ b/bundle/direct/autotest/README.md @@ -139,9 +139,15 @@ is a record of what the fake server does: `external_locations` has 62 such rows, naming cloud storage the suite does not provision. A type that cannot run against a real workspace at all declares `local_only: ` and -is skipped on cloud — an external location needs a storage credential with cloud IAM behind -it, and Lakebase and Postgres resources are not available in every cloud. This mirrors the -per-config cloud exclusions in `acceptance/bundle/invariant/test.toml`. +is skipped on cloud — an external location needs a storage credential with cloud IAM behind it, +and an instance pool cannot be deleted again afterwards. + +A service that exists on some clouds and not others is a different case, and declares where it +does: `clouds: [aws]` runs the type on an aws workspace and skips it elsewhere, so a cloud-specific +service is still confirmed somewhere rather than nowhere. This mirrors `CloudEnvs.gcp = false` in +`acceptance/bundle/resources/postgres_*/test.toml`; the blanket exclusions in +`acceptance/bundle/invariant/test.toml` drop those configs from every cloud, including the one +that has the service. A `skip` key may be a pattern (`aliases[*].id`), matched the way the planner matches its own field rules, and naming a block skips everything beneath it. From 84c991e1bcc34c48bbb7b729098c3c23d954250f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 22:26:11 +0200 Subject: [PATCH 080/118] Field catalog: own the fixtures instead of borrowing the invariant corpus The catalog read a resource definition from acceptance/bundle/invariant/configs and then patched `base` over it. For the types that matter most that split was lopsided: the corpus config gave jobs its name and nothing else, while `base` gave it git_source, tasks, job_clusters, environments, parameters, tags, health, both notification blocks and a trigger. Pipelines was the same shape. So what a run deployed lived in two files, with the interesting half in the less obvious one, and an edit to someone else's fixture could move these goldens. Now testdata/fields/.yml is the whole fixture: `base` is the resource, rendered into a one-resource databricks.yml and loaded normally. Each fixture gained exactly what its corpus config had been supplying -- mostly a name, at most six fields. The invariant suite is untouched, and the `# ACTION:` annotations that drive apply_mutation.py stayed with it rather than being copied into libraries where they mean nothing. Going through the config format instead of patching the typed struct after the mutator pipeline is what makes the fixture honest, and it cost alerts 54 rows: an alert built from a .dbalert.json takes everything but warehouse_id, display_name and file_path from that file, and load_dbalert_files rejects the rest outright. `base` had been seeding `evaluation` and `parameters` past that check, so those rows measured a bundle the CLI refuses. They are recorded as skipped with that reason. An inline alert fixture would cover them through the path a user actually has; that is a coverage change, not a move, so it is not in here. Falling out of it: one fixture per type makes the config subtest level and the report's config column redundant, so a transition is now TestFields///_to_ and the goldens lose their section header. configs.txt stops being a corpus report and becomes a coverage one -- which types have a library and which of the engine's own supported types do not, leaving out permissions and grants, which are stripped from every fixture by design. Co-authored-by: Isaac --- bundle/direct/autotest/README.md | 48 +++-- bundle/direct/autotest/corpus_test.go | 186 ------------------ bundle/direct/autotest/fields_test.go | 115 ++++------- bundle/direct/autotest/fixtures_test.go | 75 +++++++ bundle/direct/autotest/harness_test.go | 95 +++++---- bundle/direct/autotest/output/alerts.txt | 5 +- bundle/direct/autotest/output/apps.txt | 1 - bundle/direct/autotest/output/catalogs.txt | 1 - .../autotest/output/cluster_policies.txt | 1 - bundle/direct/autotest/output/configs.txt | 93 ++++----- bundle/direct/autotest/output/dashboards.txt | 1 - .../autotest/output/database_instances.txt | 1 - .../direct/autotest/output/genie_spaces.txt | 1 - .../direct/autotest/output/instance_pools.txt | 1 - bundle/direct/autotest/output/jobs.txt | 1 - bundle/direct/autotest/output/models.txt | 1 - bundle/direct/autotest/output/pipelines.txt | 1 - .../autotest/output/postgres_projects.txt | 1 - .../autotest/output/registered_models.txt | 1 - bundle/direct/autotest/output/schemas.txt | 1 - .../direct/autotest/output/secret_scopes.txt | 1 - .../direct/autotest/output/sql_warehouses.txt | 1 - bundle/direct/autotest/report_test.go | 37 +--- .../autotest/testdata/fields/alerts.yml | 18 +- .../direct/autotest/testdata/fields/apps.yml | 2 + .../autotest/testdata/fields/catalogs.yml | 2 + .../testdata/fields/cluster_policies.yml | 2 + .../autotest/testdata/fields/clusters.yml | 5 + .../autotest/testdata/fields/dashboards.yml | 5 + .../testdata/fields/database_instances.yml | 2 + .../autotest/testdata/fields/experiments.yml | 1 + .../testdata/fields/external_locations.yml | 6 + .../autotest/testdata/fields/genie_spaces.yml | 11 ++ .../testdata/fields/instance_pools.yml | 2 + .../direct/autotest/testdata/fields/jobs.yml | 1 + .../fields/model_serving_endpoints.yml | 1 + .../autotest/testdata/fields/models.yml | 1 + .../autotest/testdata/fields/pipelines.yml | 1 + .../testdata/fields/postgres_projects.yml | 3 + .../fields/postgres_synced_tables.yml | 10 + .../testdata/fields/registered_models.yml | 3 + .../autotest/testdata/fields/schemas.yml | 3 + .../testdata/fields/secret_scopes.yml | 7 + .../testdata/fields/sql_warehouses.yml | 8 + .../autotest/testdata/fields/volumes.yml | 5 + bundle/direct/autotest/values_test.go | 6 + 46 files changed, 343 insertions(+), 431 deletions(-) delete mode 100644 bundle/direct/autotest/corpus_test.go create mode 100644 bundle/direct/autotest/fixtures_test.go create mode 100644 bundle/direct/autotest/testdata/fields/secret_scopes.yml diff --git a/bundle/direct/autotest/README.md b/bundle/direct/autotest/README.md index 30b643456c9..b7b4dfc9b35 100644 --- a/bundle/direct/autotest/README.md +++ b/bundle/direct/autotest/README.md @@ -11,8 +11,8 @@ changing a field's type. ## What it does -For each config in `acceptance/bundle/invariant/configs` that describes a single -resource, the suite deploys it once, then walks the resource's input struct the way +For each resource type with a value library in `testdata/fields`, the suite deploys that +library's `base` as a one-resource bundle, then walks the resource's input struct the way `cmd/bundle/debug` refschema does. For each field it moves the field through every ordered pair of a small value set — with `absent` in the set, so adding and removing a field are just the pairs with `absent` on one side — and records three things per move: @@ -99,17 +99,21 @@ the engine explaining itself, which is different. ## Value library -`testdata/fields/.yml` supplies values the generic per-kind defaults cannot -guess — enums, ids, anything the backend constrains. A field naming another object needs a -name that exists: a real workspace rejects the generic `x` outright, so the field would -report nothing but `BACKEND_ERROR`. +`testdata/fields/.yml` is the whole fixture for a type: the resource to deploy, +and the values to move its fields through. There is one per type, and it is the only place a +run reads a resource definition from. + +`fields` supplies values the generic per-kind defaults cannot guess — enums, ids, anything the +backend constrains. A field naming another object needs a name that exists: a real workspace +rejects the generic `x` outright, so the field would report nothing but `BACKEND_ERROR`. ```yaml -# Seeded into the resource before the first deploy, so this block's fields become -# reachable. Some blocks only validate as a whole -- a job's git_source needs a provider, -# a url and exactly one ref -- and cannot be built up one field at a time from nothing. -# Keep path-valued fields out: base is merged after the mutator pipeline has run. +# The resource, rendered into a one-resource databricks.yml and deployed before anything is +# measured. What it declares is what can be tested: a block absent here has no entry for its +# fields to live in, and some blocks only validate as a whole -- a job's git_source needs a +# provider, a url and exactly one ref -- so they cannot be built up one field at a time. base: + name: test-job-$UNIQUE_NAME git_source: git_provider: gitHub git_url: https://github.com/databricks/cli.test @@ -154,14 +158,20 @@ field rules, and naming a block skips everything beneath it. A field that names the resource gets the run's own unique suffix appended to its values, so two runs against one workspace never ask for the same name — the second would get "already -exists", which says nothing about the engine. Such a field is recognised by the corpus config -templating its value with `$UNIQUE_NAME`. The suffix is a placeholder until the value is +exists", which says nothing about the engine. Such a field is recognised by `base` templating +its value with `$UNIQUE_NAME`. The suffix is a placeholder until the value is written, since a rebuilt resource has a new one while the old is still alive, and it is redacted in the report so the golden is the same on every run. -`base` is expanded with the same `$VARS` the corpus configs use — `$CURRENT_USER_NAME`, -`$UNIQUE_NAME`, `$NODE_TYPE_ID` and the rest — so a seeded value can name the workspace's -own user rather than a placeholder only the fake server knows. +`base` is the whole resource, and is expanded with the `$VARS` an acceptance config would get — +`$CURRENT_USER_NAME`, `$NODE_TYPE_ID`, `$TEST_DEFAULT_WAREHOUSE_ID` and the rest — so a value can +name the workspace's own user rather than a placeholder only the fake server knows. +`$UNIQUE_NAME` is expanded later, per deploy, because a rebuild gets a new one. + +Because `base` becomes the bundle, it can only hold what a bundle can: a field the config format +rejects is rejected here too. That is deliberate — an alert built from a `.dbalert.json` takes +everything but `warehouse_id`, `display_name` and `file_path` from that file, and seeding +`evaluation` in the bundle is a shape the CLI refuses. `skip` is for a field no single-field edit can exercise — one that only validates as part of a set (a job's `git_source`), or whose change is correct but ruinously slow (an @@ -224,11 +234,11 @@ faithfully, which is worth failing over. The full report goes to ## Out of scope for now - remote drift — a change made outside the bundle; this suite only edits config -- `permissions` and `grants`, which are stripped from every config before planning: they +- `permissions` and `grants`, which are stripped from every fixture before planning: they are separate plan nodes describing an ACL, and leaving them in made every recreate - report a drifted child against whichever field triggered it. Two configs that differ - only in those blocks therefore collapse to one, recorded in `output/configs.txt`. -- configs with more than one resource, or with an `-init.sh` + report a drifted child against whichever field triggered it. They are supported resource + types in their own right, and `output/configs.txt` leaves them out for the same reason. +- resource types with no value library yet, listed in `output/configs.txt` - fields under a slice or map, listed at the end of each report Three divergences from a real workspace are known and left as findings rather than modelled, diff --git a/bundle/direct/autotest/corpus_test.go b/bundle/direct/autotest/corpus_test.go deleted file mode 100644 index 9d349992b21..00000000000 --- a/bundle/direct/autotest/corpus_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package autotest - -import ( - "encoding/json" - "fmt" - "maps" - "os" - "path/filepath" - "slices" - "strconv" - "strings" - "testing" - - bundleconfig "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/bundle/direct/dresources" - "github.com/stretchr/testify/require" - "go.yaml.in/yaml/v3" -) - -// The suite reuses the acceptance invariant corpus rather than growing a second set -// of bundle fixtures: those configs are already known-deployable and maintained. -const ( - invariantDir = "../../../acceptance/bundle/invariant" - configsDir = invariantDir + "/configs" - dataDir = invariantDir + "/data" -) - -// testConfig is one usable entry of the corpus. -type testConfig struct { - name string // "schema.yml.tmpl" - resourceType string // "schemas" -} - -// discoverConfigs lists the corpus entries this suite can drive today, and returns the -// rest with the reason they were left out so the report can account for every config. -// -// Out of scope for now: -// - an -init.sh counterpart: the config needs shell setup this suite does not run -// - more than one resource: field edits would need the whole dependency graph -// - permissions and grants sub-resources: they are separate plan nodes whose fields -// describe an ACL rather than the resource, and they need a parent to attach to -func discoverConfigs(t *testing.T) (usable []testConfig, skipped map[string]string) { - entries, err := filepath.Glob(filepath.Join(configsDir, "*.yml.tmpl")) - require.NoError(t, err) - require.NotEmpty(t, entries) - - skipped = map[string]string{} - seen := map[string]string{} - for _, path := range entries { - name := filepath.Base(path) - - if _, err := os.Stat(path + "-init.sh"); err == nil { - skipped[name] = "has -init.sh" - continue - } - - nodes, err := resourceNodes(path) - if err != nil { - skipped[name] = "unparseable: " + err.Error() - continue - } - if len(nodes) != 1 { - skipped[name] = "has " + strconv.Itoa(len(nodes)) + " resources" - continue - } - resourceType := bundleconfig.GetResourceTypeFromKey(nodes[0]) - if _, ok := dresources.SupportedResources[resourceType]; !ok { - skipped[name] = "unsupported resource type " + resourceType - continue - } - - // The suite strips permissions and grants, so two configs differing only in - // those blocks would test exactly the same fields twice. - fingerprint, err := resourceFingerprint(path) - if err != nil { - skipped[name] = "unfingerprintable: " + err.Error() - continue - } - if first, dup := seen[fingerprint]; dup { - skipped[name] = "same resource as " + first + " once permissions and grants are removed" - continue - } - seen[fingerprint] = name - - usable = append(usable, testConfig{name: name, resourceType: resourceType}) - } - - slices.SortFunc(usable, func(a, b testConfig) int { return strings.Compare(a.name, b.name) }) - return usable, skipped -} - -// resourceFingerprint identifies the resources a config declares, as this suite will -// actually deploy them: sub-resources removed, and the bundle block ignored because it -// carries only the run's unique name. -func resourceFingerprint(path string) (string, error) { - root, err := loadTemplate(path) - if err != nil { - return "", err - } - for _, resource := range declaredResources(root) { - for _, kind := range subResourceKinds { - delete(resource, kind) - } - } - encoded, err := json.Marshal(root["resources"]) - if err != nil { - return "", err - } - return string(encoded), nil -} - -// loadTemplate parses a config template with its variables blanked out; only the shape -// matters to the callers here. -func loadTemplate(path string) (map[string]any, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - yml := os.Expand(string(data), func(string) string { return "x" }) - var root map[string]any - if err := yaml.Unmarshal([]byte(yml), &root); err != nil { - return nil, fmt.Errorf("%s: %w", path, err) - } - return root, nil -} - -// resourceNodes returns the "resources.." keys declared by a config template. -func resourceNodes(path string) ([]string, error) { - root, err := loadTemplate(path) - if err != nil { - return nil, err - } - var nodes []string - for _, group := range sortedKeys(mapAt(root, "resources")) { - for _, name := range sortedKeys(mapAt(mapAt(root, "resources"), group)) { - nodes = append(nodes, "resources."+group+"."+name) - } - } - return nodes, nil -} - -// declaredResources returns every resource body of a parsed template. -func declaredResources(root map[string]any) []map[string]any { - var out []map[string]any - resources := mapAt(root, "resources") - for _, group := range sortedKeys(resources) { - byName := mapAt(resources, group) - for _, name := range sortedKeys(byName) { - if body, ok := byName[name].(map[string]any); ok { - out = append(out, body) - } - } - } - return out -} - -func mapAt(parent map[string]any, key string) map[string]any { - value, _ := parent[key].(map[string]any) - return value -} - -func sortedKeys(m map[string]any) []string { - return slices.Sorted(maps.Keys(m)) -} - -// soleResourceNode returns the single resource node of an initialized config. -func soleResourceNode(root *bundleconfig.Root) (string, error) { - nodes, err := initializedNodes(root) - if err != nil { - return "", err - } - if len(nodes) != 1 { - return "", fmt.Errorf("expected exactly one resource node, got %v", nodes) - } - return nodes[0], nil -} - -func initializedNodes(root *bundleconfig.Root) ([]string, error) { - var nodes []string - err := forEachResource(root, func(node string, _ any) error { - nodes = append(nodes, node) - return nil - }) - slices.Sort(nodes) - return nodes, err -} diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index f028f759f70..3670934a6f3 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -63,7 +63,7 @@ import ( ) func TestFields(t *testing.T) { - usable, skipped := discoverConfigs(t) + driven, undriven := discoverFixtures(t) // -update writes the report as the whole truth for a type, so it cannot come from a run // that tested a few fields: the missing ones would read as removed. @@ -82,32 +82,9 @@ func TestFields(t *testing.T) { t.Logf("testing %d fields per resource type, seeded from HEAD (%d)", *sampleSize, sampleFields) } - // One config per resource type: the simplest one, which is the shortest name -- a - // ".yml.tmpl" sorts before its "_.yml.tmpl" siblings. The variants - // exist to exercise specific engine behaviour, not extra fields, and anything a variant - // declares that the plain config does not is better seeded through testdata/fields, where - // it applies to every run rather than only where a corpus config happens to have it. - // - // It also removes the per-config workspace problem outright: a field's values are the same - // for every config, so two configs of one type would rename their resource to the same - // value and delete each other's. - byType := map[string]testConfig{} - for _, c := range usable { - if existing, ok := byType[c.resourceType]; ok && simplerConfig(existing.name, c.name) { - continue - } - byType[c.resourceType] = c - } - // Whatever lost the pick is not covered, and saying so is the point of the report. - for _, c := range usable { - if winner := byType[c.resourceType]; winner.name != c.name { - skipped[c.name] = "a simpler config of this type is driven instead: " + winner.name - } - } - - writeCorpusReport(t, byType, skipped) + writeCoverageReport(t, driven, undriven) - for resourceType, cfg := range byType { + for _, resourceType := range driven { t.Run(resourceType, func(t *testing.T) { // One resource type per goroutine: the types are independent, and the // slowest type then sets the wall time instead of their sum. @@ -118,12 +95,10 @@ func TestFields(t *testing.T) { client := newClient(t) user := workspaceUser(t, client) - // A value library may name the workspace user or a shared test object, so it is - // rendered with the same variables as a corpus config -- minus UNIQUE_NAME, which - // belongs to one deploy and would be wrong for a value reused across rebuilds. - vars := templateVars("", user.UserName) - delete(vars, "UNIQUE_NAME") - fv, err := loadFieldValues(resourceType, vars) + // A value library may name the workspace user or a shared test object, so the same + // variables a bundle gets are expanded here too. UNIQUE_NAME is the exception and + // is left to renderBundle, which knows the deploy it belongs to. + fv, err := loadFieldValues(resourceType, templateVars("", user.UserName)) require.NoError(t, err) if fv.localOnly != "" && isCloud() { t.Skipf("local only: %s", fv.localOnly) @@ -136,27 +111,25 @@ func TestFields(t *testing.T) { ctx := t.Context() rep := &report{resourceType: resourceType} //exhaustruct:ignore - t.Run(cfg.name, func(t *testing.T) { - runConfig(t, ctx, client, user, cfg, fv, rep, runSeed, sampleFields) - }) + runType(t, ctx, client, user, resourceType, fv, rep, runSeed, sampleFields) rep.write(t) }) } } -// runConfig drives one config. Every resource it creates belongs to this test's lifetime, +// runType drives one resource type. Every resource it creates belongs to this test's lifetime, // which is what `owner` below carries into the subtests: a resource a subtest asks for is // reused by the transitions after it, so its cleanup cannot be the subtest's. -func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, rep *report, runSeed, sampleSeedValue uint64) { +func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, resourceType string, fv *fieldValues, rep *report, runSeed, sampleSeedValue uint64) { owner := t - adapter, err := dresources.NewAdapter(dresources.SupportedResources[cfg.resourceType], cfg.resourceType, client) + adapter, err := dresources.NewAdapter(dresources.SupportedResources[resourceType], resourceType, client) require.NoError(t, err) // The base deploy establishes that the config itself is deployable. If it is not, // nothing below can be attributed to a field. - h, err := newBaseline(t, ctx, client, user, cfg, fv) + h, err := newBaseline(t, ctx, client, user, resourceType, fv) if err != nil { - rep.add(result{cfg.name, "(base config)", "", "create", verdictBaseError, oneLine(err.Error()), err.Error()}) + rep.add(result{"(base config)", "", "create", verdictBaseError, oneLine(err.Error()), err.Error()}) return } @@ -165,7 +138,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl base := h.snapshot() resource, err := h.resource() require.NoError(t, err) - fields, uncovered, inert := enumerateFields(cfg.resourceType, adapter.InputConfigType(), fv, resource, runSeed, + fields, uncovered, inert := enumerateFields(resourceType, adapter.InputConfigType(), fv, resource, runSeed, declaredUnsettable(adapter), h.unique) rep.addCoverage(fields, uncovered) fields = sample(fields, sampleSeedValue, rep) @@ -184,7 +157,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // resource's own reason rather than tested: every transition would come back SUPPRESSED // with exactly that reason. for _, path := range slices.Sorted(maps.Keys(inert)) { - rep.add(result{cfg.name, path, "", "", verdictSkipped, "backend output: " + inert[path], ""}) + rep.add(result{path, "", "", verdictSkipped, "backend output: " + inert[path], ""}) } // Fields the resource says it ignores local changes to. Not skipped: the declaration is @@ -195,7 +168,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl decl := declarations{deliberate: declaredDeliberate(adapter), idFields: declaredIDFields(adapter)} for path, reason := range fv.skip { - rep.add(result{cfg.name, path, "", "", verdictSkipped, reason, ""}) + rep.add(result{path, "", "", verdictSkipped, reason, ""}) } // Some resources do not converge even with nothing changed: a field the read never @@ -204,11 +177,11 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // gets blamed on whichever field happened to be under test. baseline, err := baselineDrift(h) if err != nil { - rep.add(result{cfg.name, "(baseline)", "", "", verdictPlanError, oneLine(err.Error()), err.Error()}) + rep.add(result{"(baseline)", "", "", verdictPlanError, oneLine(err.Error()), err.Error()}) return } for _, path := range slices.Sorted(maps.Keys(baseline)) { - rep.add(result{cfg.name, path, "", "", verdictBaselineDrift, "drifts with no config change", ""}) + rep.add(result{path, "", "", verdictBaselineDrift, "drifts with no config change", ""}) } for _, f := range fields { @@ -226,12 +199,12 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl for _, tr := range f.transitions() { t.Run(tr.label(), func(t *testing.T) { if broken != nil { - rep.add(result{cfg.name, f.path, valueLabel(tr.from), valueLabel(tr.to), verdictBaseError, oneLine(broken.Error()), broken.Error()}) + rep.add(result{f.path, valueLabel(tr.from), valueLabel(tr.to), verdictBaseError, oneLine(broken.Error()), broken.Error()}) return } reuse := deployedKnown && valueLabel(deployed) == valueLabel(tr.from) - res := runTransition(t, h, cfg.name, f.path, tr, reuse, baseline, decl) + res := runTransition(t, h, f.path, tr, reuse, baseline, decl) // A starting value the deployed resource will not take -- typically a // field the API refuses to clear -- is not a dead end: a fresh resource @@ -241,7 +214,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // With the starting value written into the config before the create, so // the new resource is built holding it: a field the API will not move to // a value can still be created with it. - rebuilt, err := rebuild(owner, ctx, client, user, cfg, fv, h, preset{f.path, tr.from}) + rebuilt, err := rebuild(owner, ctx, client, user, resourceType, fv, h, preset{f.path, tr.from}) uncreatable := err != nil if uncreatable { // The resource cannot even be created holding that value, which is the @@ -250,7 +223,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // rebuild follows so the remaining transitions have a resource. res.detail = "cannot create the resource holding it: " + oneLine(err.Error()) res.evidence = withContext("error creating the resource with the starting value:", err.Error()) - rebuilt, err = rebuild(owner, ctx, client, user, cfg, fv, h) + rebuilt, err = rebuild(owner, ctx, client, user, resourceType, fv, h) } if err != nil { // No resource left to observe anything on, which is a different @@ -260,9 +233,9 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl res.evidence = err.Error() broken = err } else { - h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) + h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, rep) if !uncreatable { - res = runTransition(t, h, cfg.name, f.path, tr, false, baseline, decl) + res = runTransition(t, h, f.path, tr, false, baseline, decl) } } } @@ -276,7 +249,7 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl if res.verdict == verdictRecreate { // A recreate replaced the resource without going through rebuild, so the // baseline belongs to one that no longer exists. - baseline = remeasure(h, baseline, cfg.name, rep) + baseline = remeasure(h, baseline, rep) } if broken != nil || res.verdict.leavesResourceUsable() { @@ -285,18 +258,18 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl deployedKnown = false // The resource is in an unknown state; carrying it into the next // transition would turn one failure into a run of them. - rebuilt, err := rebuild(owner, ctx, client, user, cfg, fv, h) + rebuilt, err := rebuild(owner, ctx, client, user, resourceType, fv, h) if err != nil { broken = err return } - h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) + h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, rep) }) } }) if broken != nil { // The type has no usable resource left, so no later field can be observed either. - rep.add(result{cfg.name, "(rebuild)", "", "", verdictBaseError, oneLine(broken.Error()), broken.Error()}) + rep.add(result{"(rebuild)", "", "", verdictBaseError, oneLine(broken.Error()), broken.Error()}) return } // Put the field back and confirm the resource converged. A field the API cannot @@ -305,12 +278,12 @@ func runConfig(t *testing.T, ctx context.Context, client *databricks.WorkspaceCl // what makes that cheap: reusing the old one waits out an asynchronous delete. h.restore(base) if !h.converged() { - rebuilt, err := rebuild(owner, ctx, client, user, cfg, fv, h) + rebuilt, err := rebuild(owner, ctx, client, user, resourceType, fv, h) if err != nil { - rep.add(result{cfg.name, f.path, "", "", verdictBaseError, oneLine(err.Error()), err.Error()}) + rep.add(result{f.path, "", "", verdictBaseError, oneLine(err.Error()), err.Error()}) return } - h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, cfg.name, rep) + h, base, baseline = rebuilt, rebuilt.snapshot(), remeasure(rebuilt, baseline, rep) } } } @@ -441,14 +414,14 @@ func hasFieldChanges(plan *deployplan.Plan, node string) bool { // A plan that fails here leaves the previous measurement in place, which is closer than // nothing, and says so: every verdict after it is measured against a baseline that may not be // this resource's. -func remeasure(h *bundleHarness, previous map[string]bool, config string, rep *report) map[string]bool { +func remeasure(h *bundleHarness, previous map[string]bool, rep *report) map[string]bool { measured, err := baselineDrift(h) if err == nil { return measured } // Recorded, not just logged: every verdict after this is measured against a baseline that // belongs to a resource which no longer exists, and a reader of the report has to know. - rep.add(result{config, "(baseline)", "", "", verdictPlanError, oneLine(err.Error()), err.Error()}) + rep.add(result{"(baseline)", "", "", verdictPlanError, oneLine(err.Error()), err.Error()}) return previous } @@ -525,9 +498,9 @@ type declarations struct { idFields []dresources.FieldRule } -func runTransition(t *testing.T, h *bundleHarness, config, path string, tr transition, startsDeployed bool, baseline map[string]bool, decl declarations) result { +func runTransition(t *testing.T, h *bundleHarness, path string, tr transition, startsDeployed bool, baseline map[string]bool, decl declarations) result { from, to := tr.from, tr.to - res := result{config: config, field: path, from: valueLabel(tr.from), to: valueLabel(tr.to)} //exhaustruct:ignore + res := result{field: path, from: valueLabel(tr.from), to: valueLabel(tr.to)} //exhaustruct:ignore // Reach the starting value. if err := h.setField(path, from); err != nil { @@ -682,8 +655,8 @@ func runTransition(t *testing.T, h *bundleHarness, config, path string, tr trans // config -- not the subtest that happened to ask for the rebuild. Registering the cleanup on // the subtest would destroy the resource as soon as that transition ended, and every // transition after it would silently be creating a new one. -func newBaseline(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, presets ...preset) (*bundleHarness, error) { - h, err := newHarness(owner, ctx, client, user, cfg.name, uniqueName(), fv.base) +func newBaseline(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, resourceType string, fv *fieldValues, presets ...preset) (*bundleHarness, error) { + h, err := newHarness(owner, ctx, client, user, resourceType, uniqueName(), fv.base) if err != nil { return nil, err } @@ -719,9 +692,9 @@ type preset struct { // rebuild starts over on a fresh resource, leaving the old one to be destroyed at the // end of the test. A new name is what makes this cheap: reusing the old one can mean // waiting out an asynchronous delete. -func rebuild(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, cfg testConfig, fv *fieldValues, old *bundleHarness, presets ...preset) (*bundleHarness, error) { +func rebuild(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, resourceType string, fv *fieldValues, old *bundleHarness, presets ...preset) (*bundleHarness, error) { owner.Cleanup(func() { _ = old.destroy() }) - return newBaseline(owner, ctx, client, user, cfg, fv, presets...) + return newBaseline(owner, ctx, client, user, resourceType, fv, presets...) } // explainSkip says why a plan came back with nothing to do for the field under test. @@ -1035,16 +1008,6 @@ func redactIDs(s string) string { return s } -// simplerConfig reports whether a is the simpler of two config names for one resource type. -// Shorter wins, so "job.yml.tmpl" beats "job_with_depends_on.yml.tmpl"; length ties break -// alphabetically so the choice is stable. -func simplerConfig(a, b string) bool { - if len(a) != len(b) { - return len(a) < len(b) - } - return a < b -} - // sampleSize limits how many of a type's fields are tested, for a run that has to be cheap // rather than exhaustive -- a PR check against a real workspace. Which fields are picked comes // from the commit, so successive commits cover different ground, and a whole run's picks are diff --git a/bundle/direct/autotest/fixtures_test.go b/bundle/direct/autotest/fixtures_test.go new file mode 100644 index 00000000000..4a63f8c8f8b --- /dev/null +++ b/bundle/direct/autotest/fixtures_test.go @@ -0,0 +1,75 @@ +package autotest + +import ( + "fmt" + "path/filepath" + "slices" + "strings" + "testing" + + bundleconfig "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/direct/dresources" + "github.com/stretchr/testify/require" +) + +// dataDir holds the files fixtures reference by path -- an app's source directory, a +// dashboard's serialized definition, a pipeline's notebook. These are shared test assets rather +// than resource definitions, so they stay where every suite reads them from; the fixtures +// themselves live in testdata/fields and belong to this suite alone. +const dataDir = "../../../acceptance/bundle/invariant/data" + +// discoverFixtures returns the resource types this suite drives -- one value library each -- +// and the supported types that have none, so the report accounts for every type the direct +// engine knows rather than only the ones covered. +// +// Sub-resources are not among either: permissions and grants are separate plan nodes whose +// fields describe an ACL rather than the resource, they need a parent to attach to, and the +// suite strips them from every fixture. Listing them as gaps would suggest fixtures are missing. +func discoverFixtures(t *testing.T) (driven, undriven []string) { + entries, err := filepath.Glob(filepath.Join(fieldsDir, "*.yml")) + require.NoError(t, err) + require.NotEmpty(t, entries) + + has := map[string]bool{} + for _, path := range entries { + resourceType := strings.TrimSuffix(filepath.Base(path), ".yml") + // A library for a type the engine does not support is a leftover, and silently + // ignoring it would let a rename go unnoticed. + _, supported := dresources.SupportedResources[resourceType] + require.True(t, supported, "%s names no supported resource type", path) + has[resourceType] = true + driven = append(driven, resourceType) + } + + for resourceType := range dresources.SupportedResources { + if !has[resourceType] && !strings.Contains(resourceType, ".") { + undriven = append(undriven, resourceType) + } + } + + slices.Sort(driven) + slices.Sort(undriven) + return driven, undriven +} + +// soleResourceNode returns the single resource node of an initialized config. +func soleResourceNode(root *bundleconfig.Root) (string, error) { + nodes, err := initializedNodes(root) + if err != nil { + return "", err + } + if len(nodes) != 1 { + return "", fmt.Errorf("expected exactly one resource node, got %v", nodes) + } + return nodes[0], nil +} + +func initializedNodes(root *bundleconfig.Root) ([]string, error) { + var nodes []string + err := forEachResource(root, func(node string, _ any) error { + nodes = append(nodes, node) + return nil + }) + slices.Sort(nodes) + return nodes, err +} diff --git a/bundle/direct/autotest/harness_test.go b/bundle/direct/autotest/harness_test.go index 14988cc9a9b..0b1a93f9b0c 100644 --- a/bundle/direct/autotest/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -34,6 +34,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/iam" "github.com/google/uuid" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" ) // bundleHarness drives the direct engine over one bundle config in-process. Nothing @@ -96,9 +97,13 @@ const testUserName = "tester@databricks.com" // templateVars mirrors the variables acceptance/acceptance_test.go exports to the // invariant configs. On cloud the harness that launched us provides the real ids. +// uniqueNameVar names the run's own suffix. Every fixture uses it for the resource name, and it +// is the one variable a value library cannot expand for itself. +const uniqueNameVar = "UNIQUE_NAME" + func templateVars(uniqueName, userName string) map[string]string { vars := map[string]string{ - "UNIQUE_NAME": uniqueName, + uniqueNameVar: uniqueName, // Matches defaultSparkVersion in acceptance/acceptance_test.go. "DEFAULT_SPARK_VERSION": "13.3.x-snapshot-scala2.12", "NODE_TYPE_ID": nodeTypeID(), @@ -112,7 +117,7 @@ func templateVars(uniqueName, userName string) map[string]string { for key := range vars { // UNIQUE_NAME is this run's own, and CURRENT_USER_NAME is already the resolved // workspace user, which is more reliable than an env var that may not be set. - if key == "UNIQUE_NAME" || key == "CURRENT_USER_NAME" { + if key == uniqueNameVar || key == "CURRENT_USER_NAME" { continue } if value := os.Getenv(key); value != "" { @@ -159,31 +164,17 @@ func maxWaitSeconds() string { // built is usually the environment (an expired token, a workspace that rejects the // config), and the run is more useful if that lands in the report as one bad verdict // than if it aborts thousands of pending observations. -func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, configName, uniqueName string, base any) (*bundleHarness, error) { +func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, resourceType, uniqueName string, base any) (*bundleHarness, error) { dir := t.TempDir() if err := copyDir(dataDir, dir); err != nil { return nil, err } - src, err := os.ReadFile(filepath.Join(configsDir, configName)) + rememberWorkspaceUser(user.UserName) + yml, err := renderBundle(resourceType, uniqueName, user.UserName, base) if err != nil { return nil, err } - rememberWorkspaceUser(user.UserName) - vars := templateVars(uniqueName, user.UserName) - var missing string - yml := os.Expand(string(src), func(key string) string { - value, ok := vars[key] - if !ok { - // Expanding an unknown variable to "" turns a required field into null, and - // the failure then surfaces as a confusing validation error much later. - missing = key - } - return value - }) - if missing != "" { - return nil, fmt.Errorf("%s uses $%s, which this suite does not provide", configName, missing) - } if err := os.WriteFile(filepath.Join(dir, "databricks.yml"), []byte(yml), 0o600); err != nil { return nil, err } @@ -219,7 +210,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC phases.LoadDefaultTarget(ctx, b) if diags := logdiag.FlushCollected(ctx); diags.HasError() { - return nil, fmt.Errorf("loading %s: %s", configName, firstError(diags)) + return nil, fmt.Errorf("loading %s: %s", resourceType, firstError(diags)) } // Pin the user so PopulateCurrentUser makes no API call: a harness is built per @@ -231,7 +222,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC phases.Initialize(ctx, b) if diags := logdiag.FlushCollected(ctx); diags.HasError() { - return nil, fmt.Errorf("initializing %s: %s", configName, firstError(diags)) + return nil, fmt.Errorf("initializing %s: %s", resourceType, firstError(diags)) } // Drop the sub-resource blocks before anything is planned. They are separate plan @@ -258,7 +249,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC // any field. bundle.ApplySeq(ctx, b, deploy.ResourcePathMkdir()) if diags := logdiag.FlushCollected(ctx); diags.HasError() { - return nil, fmt.Errorf("creating the resource path for %s: %s", configName, firstError(diags)) + return nil, fmt.Errorf("creating the resource path for %s: %s", resourceType, firstError(diags)) } harness := &bundleHarness{ @@ -271,12 +262,6 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC unique: uniqueName, } - if base != nil { - if err := harness.edit(func(resource any) error { return seedBase(resource, base) }); err != nil { - return nil, err - } - } - return harness, nil } @@ -433,6 +418,49 @@ func (h *bundleHarness) resource() (any, error) { return structaccess.GetByString(&h.bundle.Config, h.node) } +// resourceKey is the name every fixture's resource is declared under. One resource per bundle, +// so the name carries nothing and only has to be stable. +const resourceKey = "foo" + +// renderBundle writes the fixture's base out as a bundle. The value library holds the whole +// resource, so this is the only place a databricks.yml comes from: the base is marshalled back +// to YAML, indented under its resource type, and given the bundle block. +// +// $UNIQUE_NAME survives loadFieldValues unexpanded and is expanded here, because it belongs to +// one deploy: a rebuild gets a new name, while a value library is read once for the whole run. +func renderBundle(resourceType, uniqueName, userName string, base any) (string, error) { + body, err := yaml.Marshal(base) + if err != nil { + return "", err + } + + var sb strings.Builder + fmt.Fprintf(&sb, "bundle:\n name: test-bundle-$UNIQUE_NAME\n\nresources:\n %s:\n %s:\n", resourceType, resourceKey) + for line := range strings.SplitSeq(strings.TrimRight(string(body), "\n"), "\n") { + if line == "" { + sb.WriteString("\n") + continue + } + sb.WriteString(" " + line + "\n") + } + + vars := templateVars(uniqueName, userName) + var missing string + yml := os.Expand(sb.String(), func(key string) string { + value, ok := vars[key] + if !ok { + // Expanding an unknown variable to "" turns a required field into null, and the + // failure then surfaces as a confusing validation error much later. + missing = key + } + return value + }) + if missing != "" { + return "", fmt.Errorf("%s.yml uses $%s, which this suite does not provide", resourceType, missing) + } + return yml, nil +} + func copyDir(src, dst string) error { return filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error { if err != nil { @@ -734,17 +762,6 @@ func (h *bundleHarness) restore(snapshot []byte) { }) } -// seedBase folds the value library's base fragment into the resource, so the fields it -// declares are present and can be varied. Unmarshalling merges: a field the fragment does -// not mention keeps whatever the config gave it. -func seedBase(resource, base any) error { - body, err := json.Marshal(base) - if err != nil { - return err - } - return json.Unmarshal(body, resource) -} - // subResourceKinds are the child plan nodes this suite removes from every config; see // stripSubResources. var subResourceKinds = []string{"permissions", "grants"} diff --git a/bundle/direct/autotest/output/alerts.txt b/bundle/direct/autotest/output/alerts.txt index f69dc102c74..d99a05171ff 100644 --- a/bundle/direct/autotest/output/alerts.txt +++ b/bundle/direct/autotest/output/alerts.txt @@ -1,6 +1,5 @@ === summary -OK 80 -SUPPRESSED 3 -NOT_OBSERVABLE 9 +OK 26 +NOT_OBSERVABLE 6 SKIPPED 17 diff --git a/bundle/direct/autotest/output/apps.txt b/bundle/direct/autotest/output/apps.txt index 852267e478d..2020abfb9de 100644 --- a/bundle/direct/autotest/output/apps.txt +++ b/bundle/direct/autotest/output/apps.txt @@ -1,4 +1,3 @@ -=== app.yml.tmpl compute_max_instances 1 absent UPDATE_IGNORED compute_max_instances 2 absent UPDATE_IGNORED compute_min_instances 1 absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/catalogs.txt b/bundle/direct/autotest/output/catalogs.txt index 8c467fe45ea..ff32ebc7ef7 100644 --- a/bundle/direct/autotest/output/catalogs.txt +++ b/bundle/direct/autotest/output/catalogs.txt @@ -1,4 +1,3 @@ -=== catalog.yml.tmpl custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours 720 absent UPDATE_IGNORED properties keys1 absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/cluster_policies.txt b/bundle/direct/autotest/output/cluster_policies.txt index 1d6567eec79..fe9d5760087 100644 --- a/bundle/direct/autotest/output/cluster_policies.txt +++ b/bundle/direct/autotest/output/cluster_policies.txt @@ -1,4 +1,3 @@ -=== cluster_policy.yml.tmpl definition absent spark_version_:_ty~69b3 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) definition absent spark_version_:_ty~cbc0 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) definition spark_version_:_ty~69b3 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/configs.txt b/bundle/direct/autotest/output/configs.txt index bfc63525f72..35129310071 100644 --- a/bundle/direct/autotest/output/configs.txt +++ b/bundle/direct/autotest/output/configs.txt @@ -1,60 +1,37 @@ === covered -alert.yml.tmpl alerts -app.yml.tmpl apps -catalog.yml.tmpl catalogs -cluster_policy.yml.tmpl cluster_policies -cluster.yml.tmpl clusters -dashboard.yml.tmpl dashboards -database_instance.yml.tmpl database_instances -experiment.yml.tmpl experiments -external_location.yml.tmpl external_locations -genie_space.yml.tmpl genie_spaces -instance_pool.yml.tmpl instance_pools -job.yml.tmpl jobs -model_serving_endpoint.yml.tmpl model_serving_endpoints -model.yml.tmpl models -pipeline.yml.tmpl pipelines -postgres_project.yml.tmpl postgres_projects -postgres_synced_table.yml.tmpl postgres_synced_tables -registered_model.yml.tmpl registered_models -schema.yml.tmpl schemas -secret_scope.yml.tmpl secret_scopes -sql_warehouse.yml.tmpl sql_warehouses -volume.yml.tmpl volumes +alerts +apps +catalogs +cluster_policies +clusters +dashboards +database_instances +experiments +external_locations +genie_spaces +instance_pools +jobs +model_serving_endpoints +models +pipelines +postgres_projects +postgres_synced_tables +registered_models +schemas +secret_scopes +sql_warehouses +volumes -=== not covered -app_lifecycle_ref.yml.tmpl has 2 resources -catalog_optional_fields.yml.tmpl a simpler config of this type is driven instead: catalog.yml.tmpl -cluster_apply_policy_default_values.yml.tmpl a simpler config of this type is driven instead: cluster.yml.tmpl -database_catalog.yml.tmpl has 2 resources -job_apply_policy_default_values_for_each_task.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl -job_apply_policy_default_values_job_cluster.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl -job_apply_policy_default_values_task_cluster.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl -job_cross_resource_ref.yml.tmpl has 3 resources -job_permission_ref.yml.tmpl has 2 resources -job_pydabs_1000_tasks.yml.tmpl has -init.sh -job_pydabs_10_tasks.yml.tmpl has -init.sh -job_run.yml.tmpl has 2 resources -job_run_job_ref.yml.tmpl has 2 resources -job_with_depends_on.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl -job_with_task.yml.tmpl a simpler config of this type is driven instead: job.yml.tmpl -model_serving_endpoint_telemetry.yml.tmpl a simpler config of this type is driven instead: model_serving_endpoint.yml.tmpl -pipeline_allow_duplicate_names.yml.tmpl a simpler config of this type is driven instead: pipeline.yml.tmpl -pipeline_apply_policy_default_values.yml.tmpl a simpler config of this type is driven instead: pipeline.yml.tmpl -pipeline_config_dots.yml.tmpl has 2 resources -postgres_branch.yml.tmpl has 2 resources -postgres_catalog.yml.tmpl has 2 resources -postgres_database.yml.tmpl has 4 resources -postgres_endpoint.yml.tmpl has 3 resources -postgres_role.yml.tmpl has 3 resources -schema_empty_grants.yml.tmpl a simpler config of this type is driven instead: schema.yml.tmpl -schema_grant_ref.yml.tmpl has 2 resources -schema_uppercase_name.yml.tmpl a simpler config of this type is driven instead: schema.yml.tmpl -secret.yml.tmpl has -init.sh -secret_scope_default_backend_type.yml.tmpl a simpler config of this type is driven instead: secret_scope.yml.tmpl -synced_database_table.yml.tmpl has -init.sh -vector_search_endpoint.yml.tmpl has 2 resources -vector_search_index.yml.tmpl has -init.sh -volume_external.yml.tmpl a simpler config of this type is driven instead: volume.yml.tmpl -volume_path_job_ref.yml.tmpl has 3 resources -volume_uppercase_name.yml.tmpl has 2 resources +=== not covered: no value library in testdata/fields +database_catalogs +job_runs +postgres_branches +postgres_catalogs +postgres_databases +postgres_endpoints +postgres_roles +quality_monitors +secrets +synced_database_tables +vector_search_endpoints +vector_search_indexes diff --git a/bundle/direct/autotest/output/dashboards.txt b/bundle/direct/autotest/output/dashboards.txt index ec547c75555..76830d53ab4 100644 --- a/bundle/direct/autotest/output/dashboards.txt +++ b/bundle/direct/autotest/output/dashboards.txt @@ -1,4 +1,3 @@ -=== dashboard.yml.tmpl display_name absent x-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) display_name absent y-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) display_name x-UNIQUE absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/database_instances.txt b/bundle/direct/autotest/output/database_instances.txt index 40146fb5d8a..00bd3a9c8cb 100644 --- a/bundle/direct/autotest/output/database_instances.txt +++ b/bundle/direct/autotest/output/database_instances.txt @@ -1,4 +1,3 @@ -=== database_instance.yml.tmpl capacity absent x UPDATE_IGNORED capacity absent y UPDATE_IGNORED capacity x y POST_DEPLOY_DRIFT capacity diff --git a/bundle/direct/autotest/output/genie_spaces.txt b/bundle/direct/autotest/output/genie_spaces.txt index 413ce0ea9af..29fdc33e9f8 100644 --- a/bundle/direct/autotest/output/genie_spaces.txt +++ b/bundle/direct/autotest/output/genie_spaces.txt @@ -1,4 +1,3 @@ -=== genie_space.yml.tmpl description x absent UPDATE_IGNORED description y absent UPDATE_IGNORED title x-UNIQUE absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/instance_pools.txt b/bundle/direct/autotest/output/instance_pools.txt index 878363de75b..ebf95e63e98 100644 --- a/bundle/direct/autotest/output/instance_pools.txt +++ b/bundle/direct/autotest/output/instance_pools.txt @@ -1,4 +1,3 @@ -=== instance_pool.yml.tmpl enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk true false UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/jobs.txt b/bundle/direct/autotest/output/jobs.txt index 49e730ed0c6..da8f019bc59 100644 --- a/bundle/direct/autotest/output/jobs.txt +++ b/bundle/direct/autotest/output/jobs.txt @@ -1,4 +1,3 @@ -=== job.yml.tmpl parent_path absent x UPDATE_IGNORED parent_path absent y UPDATE_IGNORED parent_path x absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/models.txt b/bundle/direct/autotest/output/models.txt index c8a81a801cb..d573aee7b0a 100644 --- a/bundle/direct/autotest/output/models.txt +++ b/bundle/direct/autotest/output/models.txt @@ -1,4 +1,3 @@ -=== model.yml.tmpl description x absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) description y absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index b100335a9c1..b315a092f07 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -1,4 +1,3 @@ -=== pipeline.yml.tmpl dry_run absent true UPDATE_IGNORED dry_run false true UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/postgres_projects.txt b/bundle/direct/autotest/output/postgres_projects.txt index aeca51d0b8e..bb14d49c82d 100644 --- a/bundle/direct/autotest/output/postgres_projects.txt +++ b/bundle/direct/autotest/output/postgres_projects.txt @@ -1,4 +1,3 @@ -=== postgres_project.yml.tmpl budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... diff --git a/bundle/direct/autotest/output/registered_models.txt b/bundle/direct/autotest/output/registered_models.txt index c3ce22a85c3..384a73e8855 100644 --- a/bundle/direct/autotest/output/registered_models.txt +++ b/bundle/direct/autotest/output/registered_models.txt @@ -1,4 +1,3 @@ -=== registered_model.yml.tmpl full_name absent x UPDATE_IGNORED full_name absent y UPDATE_IGNORED full_name x y UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/schemas.txt b/bundle/direct/autotest/output/schemas.txt index 2d8fff0e3d1..0d65e6f3fb3 100644 --- a/bundle/direct/autotest/output/schemas.txt +++ b/bundle/direct/autotest/output/schemas.txt @@ -1,4 +1,3 @@ -=== schema.yml.tmpl custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours 720 absent UPDATE_IGNORED properties keys1 absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/secret_scopes.txt b/bundle/direct/autotest/output/secret_scopes.txt index 60b3f54311f..e4fa3ffc993 100644 --- a/bundle/direct/autotest/output/secret_scopes.txt +++ b/bundle/direct/autotest/output/secret_scopes.txt @@ -1,4 +1,3 @@ -=== secret_scope.yml.tmpl backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... backend_type AZURE_KEYVAULT absent BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... diff --git a/bundle/direct/autotest/output/sql_warehouses.txt b/bundle/direct/autotest/output/sql_warehouses.txt index 3674a3bd567..d72746daaef 100644 --- a/bundle/direct/autotest/output/sql_warehouses.txt +++ b/bundle/direct/autotest/output/sql_warehouses.txt @@ -1,4 +1,3 @@ -=== sql_warehouse.yml.tmpl auto_stop_mins 10 absent UPDATE_IGNORED auto_stop_mins 20 absent UPDATE_IGNORED cluster_size 2X-Small absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/report_test.go b/bundle/direct/autotest/report_test.go index b2d1e398f0d..adc29a0ae5d 100644 --- a/bundle/direct/autotest/report_test.go +++ b/bundle/direct/autotest/report_test.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io/fs" - "maps" "os" "path/filepath" "slices" @@ -162,7 +161,6 @@ func (r result) isProblem() bool { // result is one line of the report. type result struct { - config string field string from, to string verdict verdict @@ -306,7 +304,6 @@ func (r *report) render(problemsOnly bool) string { slices.SortStableFunc(r.results, func(a, b result) int { return cmp.Or( - strings.Compare(a.config, b.config), strings.Compare(a.field, b.field), strings.Compare(a.from, b.from), strings.Compare(a.to, b.to), @@ -316,7 +313,6 @@ func (r *report) render(problemsOnly bool) string { counts := map[verdict]int{} // The labels the rendered rows actually use, so the legend explains those and no others. used := map[legendKey]bool{} - config := "" for _, res := range r.results { counts[res.verdict]++ if problemsOnly && !res.isProblem() { @@ -324,13 +320,6 @@ func (r *report) render(problemsOnly bool) string { } used[legendKey{res.field, res.from}] = true used[legendKey{res.field, res.to}] = true - if res.config != config { - if config != "" { - sb.WriteString("\n") - } - fmt.Fprintf(&sb, "=== %s\n", res.config) - config = res.config - } line := fmt.Sprintf("%-44s %-10s %-10s %s", res.field, res.from, res.to, res.verdict) if res.detail != "" { line += " " + res.detail @@ -486,26 +475,18 @@ func writeReport(t testutil.TestingT, name, body string) { } } -// writeCorpusReport records which configs the suite drives and why each of the others is -// left out, so the catalog accounts for the whole corpus rather than just the part it -// happens to cover. Only one config per resource type is driven, so a corpus config that -// merely lost the pick is listed as not covered too -- naming the one that won. -func writeCorpusReport(t testutil.TestingT, driven map[string]testConfig, skipped map[string]string) { - names := make([]string, 0, len(skipped)) - for name := range skipped { - names = append(names, name) - } - +// writeCoverageReport records which resource types the suite drives and which supported types +// it does not, so the catalog accounts for everything the direct engine knows rather than only +// what it happens to cover. +func writeCoverageReport(t testutil.TestingT, driven, undriven []string) { var sb strings.Builder sb.WriteString("=== covered\n") - for _, resourceType := range slices.Sorted(maps.Keys(driven)) { - fmt.Fprintf(&sb, "%-48s %s\n", driven[resourceType].name, resourceType) + for _, resourceType := range driven { + fmt.Fprintf(&sb, "%s\n", resourceType) } - - slices.Sort(names) - fmt.Fprintf(&sb, "\n=== not covered\n") - for _, name := range names { - fmt.Fprintf(&sb, "%-48s %s\n", name, skipped[name]) + sb.WriteString("\n=== not covered: no value library in testdata/fields\n") + for _, resourceType := range undriven { + fmt.Fprintf(&sb, "%s\n", resourceType) } name := reportPath("configs.txt") diff --git a/bundle/direct/autotest/testdata/fields/alerts.yml b/bundle/direct/autotest/testdata/fields/alerts.yml index 38d1f18f324..94e403cf6be 100644 --- a/bundle/direct/autotest/testdata/fields/alerts.yml +++ b/bundle/direct/autotest/testdata/fields/alerts.yml @@ -4,15 +4,19 @@ # as not covered. Seeding one entry each makes both testable: the containers themselves and # the fields inside an entry. base: - evaluation: - notification: - subscriptions: - - user_email: $CURRENT_USER_NAME - parameters: - - name: threshold - value: "1" + warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID + display_name: test-alert-$UNIQUE_NAME + file_path: ./alert.dbalert.json skip: + # An alert built from a .dbalert.json takes everything but warehouse_id, display_name and + # file_path from that file: load_dbalert_files rejects any other field in the bundle YAML + # ("field X is not allowed in the bundle configuration"). So these are not bundle fields at + # all, and the values the suite used to seed reached the resource only by being patched in + # after that mutator had run. + evaluation: settable only in the .dbalert.json, not in the bundle + parameters: settable only in the .dbalert.json, not in the bundle + # Must name an existing SQL warehouse, and alerts require one, so there is no absent # transition either -- a value-to-value move would need a second warehouse to exist. warehouse_id: needs a second SQL warehouse to exist diff --git a/bundle/direct/autotest/testdata/fields/apps.yml b/bundle/direct/autotest/testdata/fields/apps.yml index 826984874f1..49f742b052c 100644 --- a/bundle/direct/autotest/testdata/fields/apps.yml +++ b/bundle/direct/autotest/testdata/fields/apps.yml @@ -8,6 +8,8 @@ # The corpus config declares neither block. config carries the app's command and env; the # resources list is a union where an entry names one referenced object. base: + name: app-$UNIQUE_NAME + source_code_path: ./app config: command: - python diff --git a/bundle/direct/autotest/testdata/fields/catalogs.yml b/bundle/direct/autotest/testdata/fields/catalogs.yml index 36aa6110e70..600e387d4eb 100644 --- a/bundle/direct/autotest/testdata/fields/catalogs.yml +++ b/bundle/direct/autotest/testdata/fields/catalogs.yml @@ -2,6 +2,8 @@ # properties is a map the config does not declare, so its entries would be unreachable. base: + name: test-catalog-$UNIQUE_NAME + comment: This is a test catalog properties: team: eng diff --git a/bundle/direct/autotest/testdata/fields/cluster_policies.yml b/bundle/direct/autotest/testdata/fields/cluster_policies.yml index 6ae4355692b..2e34a58519a 100644 --- a/bundle/direct/autotest/testdata/fields/cluster_policies.yml +++ b/bundle/direct/autotest/testdata/fields/cluster_policies.yml @@ -6,6 +6,8 @@ # on this entry, which is not something a user can write -- the backend keeps whichever it # prefers and the whole block then reads as drifting. base: + name: test-cluster-policy-$UNIQUE_NAME + definition: '{"spark_version":{"type":"fixed","value":"13.3.x-scala2.12"}}' libraries: - jar: dbfs:/FileStore/test.jar diff --git a/bundle/direct/autotest/testdata/fields/clusters.yml b/bundle/direct/autotest/testdata/fields/clusters.yml index 409e3e98810..a52cbe87f94 100644 --- a/bundle/direct/autotest/testdata/fields/clusters.yml +++ b/bundle/direct/autotest/testdata/fields/clusters.yml @@ -5,6 +5,11 @@ # cloud-specific storage, and the suite runs against all three clouds -- so the rest stay # uncovered for this config, which the report says. base: + cluster_name: test-cluster-$UNIQUE_NAME + spark_version: 13.3.x-scala2.12 + node_type_id: $NODE_TYPE_ID + instance_pool_id: $TEST_INSTANCE_POOL_ID + num_workers: 1 custom_tags: team: eng spark_conf: diff --git a/bundle/direct/autotest/testdata/fields/dashboards.yml b/bundle/direct/autotest/testdata/fields/dashboards.yml index cb92e0dad83..f36565046cd 100644 --- a/bundle/direct/autotest/testdata/fields/dashboards.yml +++ b/bundle/direct/autotest/testdata/fields/dashboards.yml @@ -1,5 +1,10 @@ # Value library for dashboards. See ../../README.md. +base: + warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID + display_name: test-dashboard-$UNIQUE_NAME + file_path: ./dashboard.lvdash.json + skip: # Must name a workspace folder that exists, and this suite provisions only the bundle's own. # A dashboard also cannot be moved between folders without being recreated, and the recreate diff --git a/bundle/direct/autotest/testdata/fields/database_instances.yml b/bundle/direct/autotest/testdata/fields/database_instances.yml index bccfd3743b0..0932f25ff0f 100644 --- a/bundle/direct/autotest/testdata/fields/database_instances.yml +++ b/bundle/direct/autotest/testdata/fields/database_instances.yml @@ -4,6 +4,8 @@ # on every cloud, and the invariant suite excludes it from every cloud run for that reason. local_only: Lakebase v1 is not available in every cloud base: + name: test-db-instance-$UNIQUE_NAME + capacity: CU_1 custom_tags: - key: team value: eng diff --git a/bundle/direct/autotest/testdata/fields/experiments.yml b/bundle/direct/autotest/testdata/fields/experiments.yml index 04603974d31..b2f3490820c 100644 --- a/bundle/direct/autotest/testdata/fields/experiments.yml +++ b/bundle/direct/autotest/testdata/fields/experiments.yml @@ -7,6 +7,7 @@ # The UC trace location is the same case as a job's git_source: the backend requires the # catalog and the schema together, so neither can be reached one field at a time. base: + name: /Users/$CURRENT_USER_NAME/test-experiment-$UNIQUE_NAME tags: - key: team value: eng diff --git a/bundle/direct/autotest/testdata/fields/external_locations.yml b/bundle/direct/autotest/testdata/fields/external_locations.yml index f699b63cd02..8bcb6c53864 100644 --- a/bundle/direct/autotest/testdata/fields/external_locations.yml +++ b/bundle/direct/autotest/testdata/fields/external_locations.yml @@ -4,4 +4,10 @@ # cloud IAM set up for the workspace. The corpus config names a credential the mock server # invents, so the whole type is local-only -- the same reason the invariant suite excludes # this config from its cloud run. +base: + name: test_location_$UNIQUE_NAME + url: s3://test-bucket/path + credential_name: test_storage_credential + comment: "Test external location from DABs" + local_only: needs a storage credential with cloud IAM behind it diff --git a/bundle/direct/autotest/testdata/fields/genie_spaces.yml b/bundle/direct/autotest/testdata/fields/genie_spaces.yml index e71940ca8cc..98e7863fe1d 100644 --- a/bundle/direct/autotest/testdata/fields/genie_spaces.yml +++ b/bundle/direct/autotest/testdata/fields/genie_spaces.yml @@ -1,5 +1,16 @@ # Value library for Genie spaces. See ../../README.md. +base: + warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID + title: test-genie-space-$UNIQUE_NAME + # Structured (inline) serialized_space is marshalled to a JSON string by + # ConfigureGenieSpaceSerializedSpace; this config doubles as a regression + # guard that the normalization produces a drift-free deploy. Kept minimal + # ({version: 1}) so the real backend accepts it on cloud: it rejects + # unknown fields such as display_name with INVALID_PARAMETER_VALUE. + serialized_space: + version: 1 + skip: # Same two as dashboards: both name workspace objects this suite does not provision a second # of. See dashboards.yml. diff --git a/bundle/direct/autotest/testdata/fields/instance_pools.yml b/bundle/direct/autotest/testdata/fields/instance_pools.yml index 1bd08e569f9..337b27d4d1c 100644 --- a/bundle/direct/autotest/testdata/fields/instance_pools.yml +++ b/bundle/direct/autotest/testdata/fields/instance_pools.yml @@ -6,6 +6,8 @@ # thousand recreates in a row does not, so the catalog for this type is the local one. local_only: a real workspace refuses to delete a pool, and most fields recreate base: + instance_pool_name: test-instance-pool-$UNIQUE_NAME + node_type_id: $NODE_TYPE_ID custom_tags: team: eng preloaded_spark_versions: diff --git a/bundle/direct/autotest/testdata/fields/jobs.yml b/bundle/direct/autotest/testdata/fields/jobs.yml index 4cab38590e3..ccabeac5816 100644 --- a/bundle/direct/autotest/testdata/fields/jobs.yml +++ b/bundle/direct/autotest/testdata/fields/jobs.yml @@ -7,6 +7,7 @@ # job_clusters, triggers and environments are declared by no config in the corpus, so their # fields would go untested; seeding one entry each makes them reachable. base: + name: test-job-$UNIQUE_NAME git_source: git_provider: gitHub git_url: https://github.com/databricks/cli.test diff --git a/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml b/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml index 78fe9ed630b..b441e97e338 100644 --- a/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml +++ b/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml @@ -12,6 +12,7 @@ # config that declares a served entity, which needs a model in the workspace -- so the report # lists their fields as uncovered. base: + name: test-endpoint-$UNIQUE_NAME email_notifications: on_update_failure: - notify@databricks.invalid diff --git a/bundle/direct/autotest/testdata/fields/models.yml b/bundle/direct/autotest/testdata/fields/models.yml index c2518d7cf46..a31a03a546d 100644 --- a/bundle/direct/autotest/testdata/fields/models.yml +++ b/bundle/direct/autotest/testdata/fields/models.yml @@ -1,5 +1,6 @@ # Value library for MLflow models. See ../../README.md. base: + name: test-model-$UNIQUE_NAME tags: - key: team value: eng diff --git a/bundle/direct/autotest/testdata/fields/pipelines.yml b/bundle/direct/autotest/testdata/fields/pipelines.yml index 2a66b507fda..6af40411318 100644 --- a/bundle/direct/autotest/testdata/fields/pipelines.yml +++ b/bundle/direct/autotest/testdata/fields/pipelines.yml @@ -4,6 +4,7 @@ # fields, including the init_scripts union and the tag maps -- would go untested. Values # that differ per cloud come from the same variables the corpus configs use. base: + name: test-pipeline-$UNIQUE_NAME clusters: - label: default node_type_id: $NODE_TYPE_ID diff --git a/bundle/direct/autotest/testdata/fields/postgres_projects.yml b/bundle/direct/autotest/testdata/fields/postgres_projects.yml index e5484dd8b00..d239a52800a 100644 --- a/bundle/direct/autotest/testdata/fields/postgres_projects.yml +++ b/bundle/direct/autotest/testdata/fields/postgres_projects.yml @@ -6,6 +6,9 @@ # closer precedent, disabling gcp and azure by name. clouds: [aws] base: + project_id: test-pg-project-$UNIQUE_NAME + display_name: Test Postgres Project + enable_pg_native_login: false custom_tags: - key: team value: eng diff --git a/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml b/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml index a9fe0244211..30702cf7775 100644 --- a/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml +++ b/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml @@ -6,6 +6,16 @@ # closer precedent, disabling gcp and azure by name. clouds: [aws] base: + synced_table_id: lakebase_$UNIQUE_NAME.public.trips_synced + source_table_full_name: main.raw.trips + primary_key_columns: [id] + scheduling_policy: SNAPSHOT + postgres_database: appdb + branch: projects/test-pg-project-$UNIQUE_NAME/branches/production + create_database_objects_if_missing: true + new_pipeline_spec: + storage_catalog: main + storage_schema: pipelines extra_columns: - column_name: ingested_at column_type: TIMESTAMP diff --git a/bundle/direct/autotest/testdata/fields/registered_models.yml b/bundle/direct/autotest/testdata/fields/registered_models.yml index 30ed9145944..1e3637e3092 100644 --- a/bundle/direct/autotest/testdata/fields/registered_models.yml +++ b/bundle/direct/autotest/testdata/fields/registered_models.yml @@ -4,6 +4,9 @@ # writing the model -- but the OpenAPI spec does not annotate it, so it is not skipped # automatically. Seeding one lets the suite record what actually happens. base: + name: test-model-$UNIQUE_NAME + catalog_name: main + schema_name: default aliases: - alias_name: champion version_num: 1 diff --git a/bundle/direct/autotest/testdata/fields/schemas.yml b/bundle/direct/autotest/testdata/fields/schemas.yml index 54669f64e85..873dd7bb155 100644 --- a/bundle/direct/autotest/testdata/fields/schemas.yml +++ b/bundle/direct/autotest/testdata/fields/schemas.yml @@ -2,6 +2,9 @@ # properties is a map the config does not declare, so its entries would be unreachable. base: + catalog_name: main + name: test-schema-$UNIQUE_NAME + comment: base comment properties: team: eng diff --git a/bundle/direct/autotest/testdata/fields/secret_scopes.yml b/bundle/direct/autotest/testdata/fields/secret_scopes.yml new file mode 100644 index 00000000000..df8dcc4ca02 --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/secret_scopes.yml @@ -0,0 +1,7 @@ + +base: + + + name: test-scope-$UNIQUE_NAME + backend_type: DATABRICKS + diff --git a/bundle/direct/autotest/testdata/fields/sql_warehouses.yml b/bundle/direct/autotest/testdata/fields/sql_warehouses.yml index 9cf8e365fb8..2ff21acc113 100644 --- a/bundle/direct/autotest/testdata/fields/sql_warehouses.yml +++ b/bundle/direct/autotest/testdata/fields/sql_warehouses.yml @@ -1,5 +1,13 @@ # Value library for SQL warehouses. See ../../README.md. +base: + name: test-warehouse-$UNIQUE_NAME + cluster_size: 2X-Small + auto_stop_mins: 10 + max_num_clusters: 1 + min_num_clusters: 1 + warehouse_type: CLASSIC + skip: # Must be an ARN of an instance profile registered with the workspace. instance_profile_arn: needs an instance profile registered with the workspace diff --git a/bundle/direct/autotest/testdata/fields/volumes.yml b/bundle/direct/autotest/testdata/fields/volumes.yml index 3708f98a02c..dd1e53874a6 100644 --- a/bundle/direct/autotest/testdata/fields/volumes.yml +++ b/bundle/direct/autotest/testdata/fields/volumes.yml @@ -1,4 +1,9 @@ # Value library for volumes. See ../../README.md. +base: + name: test-volume-$UNIQUE_NAME + catalog_name: main + schema_name: default + skip: # An external volume needs a storage location backed by a real external location and # cloud IAM, which this suite does not provision. diff --git a/bundle/direct/autotest/values_test.go b/bundle/direct/autotest/values_test.go index 95af13d13f5..78f289eb8b8 100644 --- a/bundle/direct/autotest/values_test.go +++ b/bundle/direct/autotest/values_test.go @@ -190,6 +190,12 @@ func loadFieldValues(resourceType string, vars map[string]string) (*fieldValues, var missing string expanded := os.Expand(string(data), func(key string) string { + // UNIQUE_NAME belongs to one deploy, and a library is read once for the whole run: the + // base is rendered into a bundle per harness, and a rebuild gets a new name. Passing it + // through keeps it for renderBundle to expand there. + if key == uniqueNameVar { + return "$" + uniqueNameVar + } value, ok := vars[key] if !ok { missing = key From 7739e2a3225fa0cbd9b3d64d4c08222bb167ed6d Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 23:12:57 +0200 Subject: [PATCH 081/118] Field catalog: fix two postgres fixtures the fake server was excusing Making postgres cloud-eligible exposed two values the fake server accepted and the real API does not, so both types failed their very first create there. pg_type is an enum -- PG_SPECIFIC_TYPE_HALFVEC, _VARCHAR, _VECTOR -- not a Postgres type name. The fixture said "bigint", and the API answers that with "Field 'synced_table.spec.type_overrides.pg_type' is required": outside the enum reads to it as unset. The fake stored the string verbatim, so all 18 type_overrides rows were measured against a spec the backend would have rejected. The fake now checks the value against the enum -- not against the zero value, which is what my first attempt did and why it never fired: the SDK keeps an unrecognised string rather than dropping it. A pg_setting cannot be sent on the create that carries it ("setting cannot be changed; setting: work_mem"), so seeding one made postgres_projects undeployable on cloud. Recorded as skipped with that reason. Nothing was lost: all 12 rows it bought were already engine errors, every transition of that map rejected for naming spec.default_endpoint_settings.pg_settings in update_mask without providing it. Co-authored-by: Isaac --- .../autotest/output/postgres_projects.txt | 20 ++++--------------- .../testdata/fields/postgres_projects.yml | 10 +++++++--- .../fields/postgres_synced_tables.yml | 5 ++++- .../testdata/fields/secret_scopes.yml | 1 - libs/testserver/postgres.go | 12 +++++++++++ 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/bundle/direct/autotest/output/postgres_projects.txt b/bundle/direct/autotest/output/postgres_projects.txt index bb14d49c82d..b55658cc137 100644 --- a/bundle/direct/autotest/output/postgres_projects.txt +++ b/bundle/direct/autotest/output/postgres_projects.txt @@ -26,20 +26,8 @@ default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true false BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings absent keys0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... -default_endpoint_settings.pg_settings absent keys1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... -default_endpoint_settings.pg_settings keys0 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings keys0 keys1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings keys1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.pg_... -default_endpoint_settings.pg_settings keys1 keys0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.pg_settings['work_mem'] y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... +default_endpoint_settings.no_suspension true false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... @@ -51,5 +39,5 @@ OK 27 OK_RECREATE 8 SUPPRESSED 4 BACKEND_ERROR 17 -SKIPPED 1 -BASE_ERROR 30 +SKIPPED 2 +BASE_ERROR 18 diff --git a/bundle/direct/autotest/testdata/fields/postgres_projects.yml b/bundle/direct/autotest/testdata/fields/postgres_projects.yml index d239a52800a..e3eb07466ed 100644 --- a/bundle/direct/autotest/testdata/fields/postgres_projects.yml +++ b/bundle/direct/autotest/testdata/fields/postgres_projects.yml @@ -12,9 +12,13 @@ base: custom_tags: - key: team value: eng - default_endpoint_settings: - pg_settings: - work_mem: 64MB + +skip: + # The real API refuses a pg_setting on the create it is sent with: "setting cannot be changed; + # setting: work_mem" (aws, 2026-08). Seeding one made the whole type undeployable there, and the + # 12 rows it bought were all engine errors already -- every transition of this map was rejected + # for naming spec.default_endpoint_settings.pg_settings in update_mask without providing it. + default_endpoint_settings.pg_settings: the API rejects a setting on the create that carries it fields: # project_id is part of the resource's hierarchical name, so a change recreates it. diff --git a/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml b/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml index 30702cf7775..4acf913b112 100644 --- a/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml +++ b/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml @@ -20,5 +20,8 @@ base: - column_name: ingested_at column_type: TIMESTAMP type_overrides: + # pg_type is an enum, not a Postgres type name: only PG_SPECIFIC_TYPE_HALFVEC, _VARCHAR and + # _VECTOR. "bigint" parsed to the unspecified zero value, which the real API rejects as a + # missing required field while the fake server stored it verbatim. - column_name: id - pg_type: bigint + pg_type: PG_SPECIFIC_TYPE_VARCHAR diff --git a/bundle/direct/autotest/testdata/fields/secret_scopes.yml b/bundle/direct/autotest/testdata/fields/secret_scopes.yml index df8dcc4ca02..44d4b03612b 100644 --- a/bundle/direct/autotest/testdata/fields/secret_scopes.yml +++ b/bundle/direct/autotest/testdata/fields/secret_scopes.yml @@ -4,4 +4,3 @@ base: name: test-scope-$UNIQUE_NAME backend_type: DATABRICKS - diff --git a/libs/testserver/postgres.go b/libs/testserver/postgres.go index ebc402fce07..3110e05e28b 100644 --- a/libs/testserver/postgres.go +++ b/libs/testserver/postgres.go @@ -1517,6 +1517,18 @@ func (s *FakeWorkspace) PostgresSyncedTableCreate(req Request, syncedTableID str } } + // pg_type is an enum, and the API rejects anything outside it as a missing required field + // rather than a bad value (aws, 2026-08): a Postgres type name like "bigint" is not one of + // PG_SPECIFIC_TYPE_HALFVEC / _VARCHAR / _VECTOR. Checked against the enum, not against the + // zero value: the SDK stores an unrecognised string verbatim rather than dropping it. + if table.Spec != nil { + for _, override := range table.Spec.TypeOverrides { + if !slices.Contains(override.PgType.Values(), override.PgType) { + return postgresErrorResponse(400, "INVALID_PARAMETER_VALUE", `Field 'synced_table.spec.type_overrides.pg_type' is required, expected non-default value (not "")!`) + } + } + } + name := "synced_tables/" + syncedTableID if _, exists := s.PostgresSyncedTables[name]; exists { From d696ec3cb44f19f474a1772f4715ff17b051aa01 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 23:22:45 +0200 Subject: [PATCH 082/118] Field catalog: address review findings An adversarial review (gpt-5.6-sol) found nine, all real: The baseline guard I added last commit is unreachable in the useful direction. Its key comes from relatedChangeKey, which only ever returns a key related to the field, so "key != path" means an ancestor -- exactly the case the original comment says must not excuse anything. Removing the guard entirely moves no row in any golden, so it went, and the fix is smaller than the one I wrote. OK_ID_FIELD_REQUIRED accepted any API error, including PERMISSION_DENIED and REQUEST_LIMIT_EXCEEDED, which are answers about the caller and the moment rather than about the request. Narrowed to the codes that say the request itself was malformed. sampledRows cut the field at the first space, so a quoted map key containing one -- tags['my team'] -- would have compared half a path. Read off by column width instead. Running it also turned up something the review did not: COLLATERAL_DRIFT is drift the plan attributes to another field, so a run that did not sample that field cannot reproduce the row. Dropped from both sides. renderBundle spliced re-indented YAML under the resource key, which has to reason about block scalars, where a blank line is content. It marshals one document now. The report's mutex guarded nothing -- one report per parallel type, everything within a type sequential -- and three comments described code that no longer exists, including one for a function deleted with the fixture fold. Verified with -race after removing the lock. Co-authored-by: Isaac --- bundle/direct/autotest/fields_test.go | 34 ++++++++-------- bundle/direct/autotest/harness_test.go | 24 ++++-------- bundle/direct/autotest/report_test.go | 54 +++++++++++++------------- bundle/direct/autotest/values_test.go | 12 +++--- 4 files changed, 56 insertions(+), 68 deletions(-) diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index 3670934a6f3..a7132ccc860 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -438,19 +438,6 @@ func baselineCovers(baseline map[string]bool, plan *deployplan.Plan, node, path return ok && baseline[key] } -// baselineCoversOther reports whether the drift this field would be blamed for was already -// there *and* belongs to something else. The planner records a change against the key it -// diffed at, which can be an ancestor several fields share, so existing drift at that ancestor -// says nothing about whether this field's write landed. -// -// Drift at the field's own key is a different matter: that is this field not holding its value, -// which is the very thing the transition set out to observe. Excusing it turned an ignored -// write into OK -- and silently, since a baseline re-measured after a rebuild is not reported. -func baselineCoversOther(baseline map[string]bool, plan *deployplan.Plan, node, path string) bool { - key, _, ok := relatedChangeKey(plan, node, path) - return ok && key != path && baseline[key] -} - // converged reports whether a plan no longer wants to change the field, at whatever // granularity the planner recorded it. func converged(plan *deployplan.Plan, node, path string) bool { @@ -593,7 +580,7 @@ func runTransition(t *testing.T, h *bundleHarness, path string, tr transition, s // One read cannot tell that apart from a read that was merely stale, so take a second // one. If the value has appeared by then the write did land and the first read was // behind; if it still has not, the field really was ignored. - if !baselineCoversOther(baseline, plan, h.node, path) && wasIgnored(plan, after, h.node, path) { + if wasIgnored(plan, after, h.node, path) { second, diags := h.readPlan() if diags.HasError() { res.verdict = verdictPlanError @@ -786,6 +773,13 @@ func firstError(diags diag.Diagnostics) string { // apiErrorCodes are the error codes that mean the backend rejected the request rather // than the CLI failing. Diagnostics carry a rendered message, not the error value, so // there is nothing to match with errors.As by the time we see them. +// invalidRequestCodes are the subset of apiErrorCodes that say the request itself was wrong, as +// opposed to the caller lacking permission or asking too often. +var invalidRequestCodes = []string{ + "INVALID_PARAMETER_VALUE", + "BAD_REQUEST", +} + var apiErrorCodes = []string{ "INVALID_PARAMETER_VALUE", "BAD_REQUEST", @@ -817,7 +811,11 @@ func isTimeout(diags diag.Diagnostics) bool { func idFieldRequired(value any, path string, decl declarations, diags diag.Diagnostics, otherwise verdict) verdict { // Same nil test valueLabel uses to print "absent", so the verdict and the row's own column // can never disagree about which value this was. - if value != nil || !isAPIError(diags) { + // + // Not any API error: a rejection is only evidence about the request when it says the request + // was malformed. PERMISSION_DENIED and REQUEST_LIMIT_EXCEEDED are answers about the caller + // and the moment, and would have come back whatever the field held. + if value != nil || !hasErrorCode(diags, invalidRequestCodes) { return otherwise } if _, isID := ruleReason(decl.idFields, path); !isID { @@ -827,8 +825,12 @@ func idFieldRequired(value any, path string, decl declarations, diags diag.Diagn } func isAPIError(diags diag.Diagnostics) bool { + return hasErrorCode(diags, apiErrorCodes) +} + +func hasErrorCode(diags diag.Diagnostics, codes []string) bool { for _, d := range diags { - for _, code := range apiErrorCodes { + for _, code := range codes { if strings.Contains(d.Summary, code) { return true } diff --git a/bundle/direct/autotest/harness_test.go b/bundle/direct/autotest/harness_test.go index 0b1a93f9b0c..565520255ce 100644 --- a/bundle/direct/autotest/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -265,11 +265,6 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC return harness, nil } -// cachedUser resolves the workspace user once per process: a harness is built per -// config and again on every rebuild, so on cloud this would otherwise be thousands of -// identical requests. Locally the name is pinned, which also keeps workspace paths -// stable between runs. - // opCtx returns a context with a fresh diagnostics sink. logdiag's error flag is sticky // -- FlushCollected empties the collected diagnostics but leaves HasError true -- and // CalculatePlan short-circuits to a bare "planning failed" whenever that flag is set. A @@ -429,24 +424,19 @@ const resourceKey = "foo" // $UNIQUE_NAME survives loadFieldValues unexpanded and is expanded here, because it belongs to // one deploy: a rebuild gets a new name, while a value library is read once for the whole run. func renderBundle(resourceType, uniqueName, userName string, base any) (string, error) { - body, err := yaml.Marshal(base) + // Marshalled as one document rather than spliced together as indented text: re-indenting + // someone else's YAML has to reason about block scalars, where a blank line is content. + document, err := yaml.Marshal(map[string]any{ + "bundle": map[string]any{"name": "test-bundle-$UNIQUE_NAME"}, + "resources": map[string]any{resourceType: map[string]any{resourceKey: base}}, + }) if err != nil { return "", err } - var sb strings.Builder - fmt.Fprintf(&sb, "bundle:\n name: test-bundle-$UNIQUE_NAME\n\nresources:\n %s:\n %s:\n", resourceType, resourceKey) - for line := range strings.SplitSeq(strings.TrimRight(string(body), "\n"), "\n") { - if line == "" { - sb.WriteString("\n") - continue - } - sb.WriteString(" " + line + "\n") - } - vars := templateVars(uniqueName, userName) var missing string - yml := os.Expand(sb.String(), func(key string) string { + yml := os.Expand(string(document), func(key string) string { value, ok := vars[key] if !ok { // Expanding an unknown variable to "" turns a required field into null, and the diff --git a/bundle/direct/autotest/report_test.go b/bundle/direct/autotest/report_test.go index adc29a0ae5d..84d685c1582 100644 --- a/bundle/direct/autotest/report_test.go +++ b/bundle/direct/autotest/report_test.go @@ -10,7 +10,6 @@ import ( "path/filepath" "slices" "strings" - "sync" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/testdiff" @@ -173,13 +172,12 @@ type result struct { evidence string } -// report accumulates results for one resource type. Resource types run in parallel but -// each has its own report, and a type's configs run in sequence, so the lock only guards -// against a future change to that. +// report accumulates results for one resource type. Resource types run in parallel, but each +// owns its report and everything within a type runs in sequence, so nothing here synchronizes. +// Parallelizing the field subtests would need that to change. type report struct { resourceType string - mu sync.Mutex results []result wildcard map[string]bool covered map[string]bool @@ -195,14 +193,16 @@ type report struct { // addSampled records that this field is one of the sampled ones. func (r *report) addSampled(path string) { - r.mu.Lock() - defer r.mu.Unlock() if r.sampled == nil { r.sampled = map[string]bool{} } r.sampled[path] = true } +// fieldColumnWidth is how wide the field column is in a rendered row. Named because sampledRows +// reads the field back out of the text by it. +const fieldColumnWidth = 44 + // legendKey identifies one size label of one field. type legendKey struct { field, label string @@ -217,8 +217,6 @@ func (r *report) addLegend(field, label string, value any) { } // Redacted like everything else in the report: a seeded value can name the workspace user or // carry this run's unique suffix, both of which differ between a fake server and a real one. - r.mu.Lock() - defer r.mu.Unlock() if r.legend == nil { r.legend = map[legendKey]string{} } @@ -226,8 +224,6 @@ func (r *report) addLegend(field, label string, value any) { } func (r *report) add(res result) { - r.mu.Lock() - defer r.mu.Unlock() r.results = append(r.results, res) } @@ -236,8 +232,6 @@ func (r *report) add(res result) { // resource declare different blocks, so a union of the per-config gaps would list fields // that are in fact tested -- jobs has eight configs and only some declare tasks. func (r *report) addCoverage(covered []field, uncovered []string) { - r.mu.Lock() - defer r.mu.Unlock() if r.wildcard == nil { r.wildcard = map[string]bool{} } @@ -297,9 +291,6 @@ func (r *report) uncoveredPaths() []string { // on either the fake server or a real workspace. The full form adds every row, the evidence // behind each finding, and what was not covered. func (r *report) render(problemsOnly bool) string { - r.mu.Lock() - defer r.mu.Unlock() - var sb strings.Builder slices.SortStableFunc(r.results, func(a, b result) int { @@ -320,7 +311,7 @@ func (r *report) render(problemsOnly bool) string { } used[legendKey{res.field, res.from}] = true used[legendKey{res.field, res.to}] = true - line := fmt.Sprintf("%-44s %-10s %-10s %s", res.field, res.from, res.to, res.verdict) + line := fmt.Sprintf("%-*s %-10s %-10s %s", fieldColumnWidth, res.field, res.from, res.to, res.verdict) if res.detail != "" { line += " " + res.detail } @@ -393,9 +384,6 @@ func (r *report) renderLegend(used map[legendKey]bool) string { return sb.String() } -// write compares the report against its golden file, or rewrites it under -update. -// Cloud runs go to a separate, uncommitted file: which values a real backend accepts -// depends on the workspace, so those results are for reading, not for diffing. // write compares the findings against the committed golden. There is one golden, and both // the fake server and a real workspace are held to it: a divergence means the fake server // does not model the API faithfully, which is worth failing over rather than filing away in @@ -421,8 +409,9 @@ func (r *report) write(t testutil.TestingT) { } // A sampled run knows about a few of the type's fields, so its report is held to those - // fields' rows and nothing else. Their verdicts must read exactly as the golden records - // them; the summary counts every field and has no meaningful subset, so both sides drop it. + // fields' rows and nothing else. The rows this run produced are filtered structurally, and + // the golden -- which is only text -- by the field column. The summary counts every field + // and has no meaningful subset, so both sides drop it. if r.sampled != nil { expected, err := os.ReadFile(name) if err != nil { @@ -444,20 +433,29 @@ func (r *report) write(t testutil.TestingT) { testdiff.AssertEqualTexts(t, name, name, testdiff.NormalizeNewlines(string(expected)), body) } -// sampledRows keeps the report rows belonging to the sampled fields, dropping section headers, -// blank lines and the summary. Rows the harness records against itself rather than a field -- -// "(base config)", "(rebuild)" -- are always kept: they mean the run for that type did not -// happen, which no sample should be allowed to hide. +// sampledRows keeps the report rows belonging to the sampled fields, dropping the summary and +// blank lines. Rows the harness records against itself rather than a field -- "(base config)", +// "(rebuild)", "(baseline)" -- are always kept: they mean the run for that type did not happen, +// which no sample should be allowed to hide. +// +// The field is read off by column width rather than by cutting at the first space: a map key can +// contain one ("tags['my team']"), and cutting there would compare half a path. func sampledRows(body string, sampled map[string]bool) string { var sb strings.Builder for line := range strings.SplitSeq(body, "\n") { if strings.HasPrefix(line, "=== summary") { break } - if line == "" || strings.HasPrefix(line, "=== ") { + if line == "" { + continue + } + // A verdict whose cause is another field cannot be reproduced by a run that did not + // sample that field: COLLATERAL_DRIFT is drift the plan attributes to something else, + // and its detail names what. Dropped from both sides rather than compared. + if strings.Contains(line, string(verdictCollateral)) { continue } - field, _, _ := strings.Cut(line, " ") + field := strings.TrimRight(line[:min(fieldColumnWidth, len(line))], " ") if sampled[field] || strings.HasPrefix(field, "(") { sb.WriteString(line + "\n") } diff --git a/bundle/direct/autotest/values_test.go b/bundle/direct/autotest/values_test.go index 78f289eb8b8..9bda30d8c2d 100644 --- a/bundle/direct/autotest/values_test.go +++ b/bundle/direct/autotest/values_test.go @@ -43,13 +43,11 @@ type fieldValues struct { // converted to the field's own Go type when the field is enumerated. fields map[string][]any - // base is merged into the resource before the first deploy. It makes a block - // reachable that the invariant config does not declare: a coherent git_source, say, - // whose fields cannot be tested one at a time from nothing because the API only - // accepts the set as a whole. Once seeded, each of its fields varies normally. - // - // Keep path-valued fields out of it: it is merged after the mutator pipeline has run, - // so nothing here goes through path translation. + // base is the resource itself, rendered into a one-resource databricks.yml and deployed + // before anything is measured. What it declares is what can be tested: a block it omits has + // no entry for its fields to live in, and a block the API only accepts whole -- a coherent + // git_source -- cannot be built up one field at a time. Once declared, each field varies + // normally. // base any From 75c9c793dcd07ebbe46255785b4de44672c04166 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 23:26:28 +0200 Subject: [PATCH 083/118] Field catalog: record the plan behind a baseline-drift row A field drifting straight after the create is usually one the backend did not apply, and only the plan's remote_state says which. The row recorded the symptom and no evidence, so diagnosing one meant re-running the type against a real workspace -- which is where all three of them are (jobs, pipelines, model serving endpoints). Evidence goes to the full report only, so no committed golden moves. Co-authored-by: Isaac --- bundle/direct/autotest/fields_test.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index a7132ccc860..50804332347 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -175,13 +175,17 @@ func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClie // echoes diffs forever. Measure that once, against the field that causes it, and leave // it out of every transition's drift -- otherwise it dirties every post-deploy plan and // gets blamed on whichever field happened to be under test. - baseline, err := baselineDrift(h) + baseline, basePlan, err := baselineDrift(h) if err != nil { rep.add(result{"(baseline)", "", "", verdictPlanError, oneLine(err.Error()), err.Error()}) return } for _, path := range slices.Sorted(maps.Keys(baseline)) { - rep.add(result{path, "", "", verdictBaselineDrift, "drifts with no config change", ""}) + // The plan is the whole diagnosis: a field drifting straight after the create is usually + // one the backend did not apply, and only its remote_state says so. Without it the row + // states the symptom and nothing else, which is not enough to tell a write the API + // ignored from a read that does not echo the field. + rep.add(result{path, "", "", verdictBaselineDrift, "drifts with no config change", withContext("plan right after the base deploy:", basePlan)}) } for _, f := range fields { @@ -415,7 +419,7 @@ func hasFieldChanges(plan *deployplan.Plan, node string) bool { // nothing, and says so: every verdict after it is measured against a baseline that may not be // this resource's. func remeasure(h *bundleHarness, previous map[string]bool, rep *report) map[string]bool { - measured, err := baselineDrift(h) + measured, _, err := baselineDrift(h) if err == nil { return measured } @@ -868,20 +872,20 @@ func checkDeclaredInert(res result, rules []dresources.FieldRule) result { // baselineDrift returns the fields the resource already wants to change with the config // exactly as deployed. -func baselineDrift(h *bundleHarness) (map[string]bool, error) { +func baselineDrift(h *bundleHarness) (map[string]bool, string, error) { plan, diags := h.readPlan() if diags.HasError() { // Returning no drift would leave every later verdict measured against an unknown // baseline: a field that was already drifting would be blamed on whichever // transition happened to run. - return nil, errors.New(firstError(diags)) + return nil, "", errors.New(firstError(diags)) } if plan == nil { - return nil, errors.New("no plan") + return nil, "", errors.New("no plan") } entry, ok := plan.Plan[h.node] if !ok { - return nil, nil + return nil, "", nil } out := map[string]bool{} for path, change := range entry.Changes { @@ -889,7 +893,7 @@ func baselineDrift(h *bundleHarness) (map[string]bool, error) { out[path] = true } } - return out, nil + return out, planJSON(plan), nil } // wasIgnored reports whether the write for one field was accepted and then had no effect: From 5faa3201264de9781a5a70f682d75158bd63e845 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 23:32:19 +0200 Subject: [PATCH 084/118] Field catalog: two API constraints the fake server was not applying The cloud run rejected creates and updates the fake accepted, so a dozen rows recorded outcomes the backend would never have produced. A SQL warehouse create needs a name, a cluster_size and max_num_clusters >= 1. The fake invented a name from the id and validated neither of the others, so clearing any of the three read as an ordinary update locally while the API refuses the create outright. Applying the same three checks here is safe because the CLI defaults max_num_clusters to 1 (resourcemutator's defaults table), so only a deliberate clear reaches 0. A pipeline with a storage location is not a Unity Catalog pipeline, and the backend will not convert one in place: "Cannot add catalog to an existing pipeline with defined storage location". The fake already models the storage default that triggers this, so the condition is the API's own -- a UC pipeline has no storage and can have its catalog edited. That second one is a finding about the engine, not just the fake: pipelines.catalog is not declared in recreate_on_changes, so the engine sends an update the API refuses. Declaring it immutable unconditionally would be wrong, since the constraint only applies to a pipeline that has storage, and recreating a pipeline is destructive -- so the golden records the API's own words and the decision is left open. Co-authored-by: Isaac --- bundle/direct/autotest/output/pipelines.txt | 10 ++++- .../direct/autotest/output/sql_warehouses.txt | 9 ++++- libs/testserver/pipelines.go | 15 +++++++ libs/testserver/sql_warehouses.go | 39 +++++++++++++++++-- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index b315a092f07..c4b3b9cdd25 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -1,11 +1,19 @@ +catalog absent x BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... +catalog absent y BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... +catalog x absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... +catalog x y BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... +catalog y absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... +catalog y x BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... dry_run absent true UPDATE_IGNORED dry_run false true UPDATE_IGNORED === summary -OK 742 +OK 736 OK_RECREATE 9 OK_INERT 12 SUPPRESSED 46 NOT_OBSERVABLE 29 UPDATE_IGNORED 2 +BACKEND_ERROR 2 SKIPPED 12 +BASE_ERROR 4 diff --git a/bundle/direct/autotest/output/sql_warehouses.txt b/bundle/direct/autotest/output/sql_warehouses.txt index d72746daaef..a93d1dabfd5 100644 --- a/bundle/direct/autotest/output/sql_warehouses.txt +++ b/bundle/direct/autotest/output/sql_warehouses.txt @@ -2,16 +2,23 @@ auto_stop_mins 10 absent UPDATE_IGNORE auto_stop_mins 20 absent UPDATE_IGNORED cluster_size 2X-Small absent UPDATE_IGNORED cluster_size X-Small absent UPDATE_IGNORED +cluster_size absent 2X-Small START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) +cluster_size absent X-Small START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) enable_photon true absent UPDATE_IGNORED max_num_clusters 2 absent UPDATE_IGNORED max_num_clusters 3 absent UPDATE_IGNORED +max_num_clusters absent 2 START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. The value must be greater than or equal to 1, and l... +max_num_clusters absent 3 START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. The value must be greater than or equal to 1, and l... +name absent x-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) +name absent y-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) name x-UNIQUE absent UPDATE_IGNORED name y-UNIQUE absent UPDATE_IGNORED spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED spot_instance_policy RELIABILITY_OPTIMIZED absent UPDATE_IGNORED === summary -OK 42 +OK 36 SUPPRESSED 3 UPDATE_IGNORED 11 SKIPPED 5 +START_NOT_REACHED 6 diff --git a/libs/testserver/pipelines.go b/libs/testserver/pipelines.go index 0aedc44c5d3..e42686189d7 100644 --- a/libs/testserver/pipelines.go +++ b/libs/testserver/pipelines.go @@ -117,6 +117,21 @@ func (s *FakeWorkspace) PipelineUpdate(req Request, pipelineId string) Response } } + // A pipeline that has a storage location is not a Unity Catalog pipeline, and the backend + // will not convert one into the other in place (aws, 2026-08). Only the fake server having + // allowed it hid the fact that the engine neither recreates nor rejects the change: it sends + // the update and the API refuses it. The condition is the API's own -- a UC pipeline, which + // has no storage, can have its catalog edited. + if item.Spec != nil && item.Spec.Storage != "" && spec.Catalog != "" && spec.Catalog != item.Spec.Catalog { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "Cannot add catalog to an existing pipeline with defined storage location, if you want to migrate to Unity Catalog, please use the migration API.", + }, + } + } + // parameters is on EditPipeline, not PipelineSpec; round-trip it like // PipelineCreate does. var edit pipelines.EditPipeline diff --git a/libs/testserver/sql_warehouses.go b/libs/testserver/sql_warehouses.go index e13d3baffe7..c226d67357a 100644 --- a/libs/testserver/sql_warehouses.go +++ b/libs/testserver/sql_warehouses.go @@ -8,6 +8,21 @@ import ( "github.com/databricks/databricks-sdk-go/service/sql" ) +// maxWarehouseClusters is the ceiling the API puts on max_num_clusters. +const maxWarehouseClusters = 40 + +// sqlWarehouseError returns a rejection in the API's shape: a 400 whose message the suite +// records verbatim. +func sqlWarehouseError(message string) Response { + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": message, + }, + } +} + func sqlWarehouseFixUps(warehouse *sql.GetWarehouseResponse, userName string) { if warehouse.CreatorName == "" { warehouse.CreatorName = userName @@ -35,7 +50,8 @@ func (s *FakeWorkspace) SqlWarehousesUpsert(req Request, warehouseId string) Res defer s.LockUnlock()() - if warehouseId != "" { + isCreate := warehouseId == "" + if !isCreate { existing, ok := s.SqlWarehouses[warehouseId] if !ok { return Response{ @@ -58,10 +74,25 @@ func (s *FakeWorkspace) SqlWarehousesUpsert(req Request, warehouseId string) Res } else { warehouseId = nextUUID() } - warehouse.Id = warehouseId - if warehouse.Name == "" { - warehouse.Name = warehouseId + + // The create validations the real API applies, in the words it uses (observed on aws, + // 2026-08). An edit is exempt: it merges onto a stored warehouse that already passed them, + // and the body carries only what changed. + if isCreate { + if warehouse.Name == "" { + return sqlWarehouseError("Invalid value for SQL Endpoint name, it cannot be empty.") + } + if warehouse.ClusterSize == "" { + return sqlWarehouseError("Required field 'cluster_size' is missing.") + } + // The CLI defaults this to 1 (see resourcemutator's defaults table), so a bundle only + // reaches 0 by clearing it deliberately. + if warehouse.MaxNumClusters < 1 || warehouse.MaxNumClusters > maxWarehouseClusters { + return sqlWarehouseError(fmt.Sprintf("%d is not a valid value for max_num_clusters. The value must be greater than or equal to 1, and less than or equal to %d.", warehouse.MaxNumClusters, maxWarehouseClusters)) + } } + + warehouse.Id = warehouseId warehouse.State = sql.StateRunning sqlWarehouseFixUps(&warehouse, s.CurrentUser().UserName) s.SqlWarehouses[warehouseId] = warehouse From de9f63c36c8f881eedb9fe6feaff4752e1e4d2b1 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 31 Aug 2026 23:36:15 +0200 Subject: [PATCH 085/118] Field catalog: the two postgres types cannot be confirmed on a real workspace Making them cloud-eligible was the right move -- it produced evidence -- but the evidence says both belong back in local_only, now with reasons that name what actually blocks them rather than "the service is AWS-only". A postgres project holds its name after deletion until the soft delete is purged: "A soft-deleted project currently has this resource name." A recreate reuses the name, so every recreating field fails the create half on a real workspace. Same shape as instance_pools, whose delete a workspace refuses outright. A synced table's branch names a project this fixture does not create, and one resource per bundle means it never will, so the create fails with "Project ... not found". The fake server does not require the parent to exist, which is why the local report stands on its own. Also skips default_branch, which the API wants as 'projects//branches/' -- the generic x and y were only ever accepted by the fake server, so those rows measured nothing. Co-authored-by: Isaac --- .../direct/autotest/output/postgres_projects.txt | 8 +++----- .../testdata/fields/postgres_projects.yml | 16 +++++++++++----- .../testdata/fields/postgres_synced_tables.yml | 10 +++++----- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/bundle/direct/autotest/output/postgres_projects.txt b/bundle/direct/autotest/output/postgres_projects.txt index b55658cc137..2bf0dd05cfe 100644 --- a/bundle/direct/autotest/output/postgres_projects.txt +++ b/bundle/direct/autotest/output/postgres_projects.txt @@ -18,8 +18,6 @@ custom_tags[0].value x absent BASE_ERROR c custom_tags[0].value x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].value y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... custom_tags[0].value y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_branch x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... -default_branch y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_branch' is in update_... default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... @@ -35,9 +33,9 @@ display_name y absent BACKEND_ERROR enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... === summary -OK 27 +OK 23 OK_RECREATE 8 SUPPRESSED 4 -BACKEND_ERROR 17 -SKIPPED 2 +BACKEND_ERROR 15 +SKIPPED 3 BASE_ERROR 18 diff --git a/bundle/direct/autotest/testdata/fields/postgres_projects.yml b/bundle/direct/autotest/testdata/fields/postgres_projects.yml index e3eb07466ed..bbdac04e78d 100644 --- a/bundle/direct/autotest/testdata/fields/postgres_projects.yml +++ b/bundle/direct/autotest/testdata/fields/postgres_projects.yml @@ -1,10 +1,11 @@ # Value library for postgres projects. See ../../README.md. -# Postgres is an AWS-only service, so an aws workspace is the one place the cloud run can -# confirm this type. The invariant suite drops these configs from every cloud run rather than -# just the ones without the service; acceptance/bundle/resources/postgres_*/test.toml is the -# closer precedent, disabling gcp and azure by name. -clouds: [aws] +# A recreate reuses the project name, and a deleted project holds its name until the soft delete +# is purged: "A soft-deleted project currently has this resource name." So on a real workspace +# every recreating field fails the create half, the same shape as instance_pools. A plain deploy +# and destroy works -- the invariant suite does that on aws -- but a run of recreates does not, +# so the catalog for this type is the local one. +local_only: a recreate cannot reuse the project name until the soft delete is purged base: project_id: test-pg-project-$UNIQUE_NAME display_name: Test Postgres Project @@ -14,6 +15,11 @@ base: value: eng skip: + # A branch reference, not a name: the API answers a bare string with "Field 'default_branch' + # expects 'projects//branches/'". Naming one needs a branch that exists, and + # this fixture creates only the project. + default_branch: names a branch resource the fixture does not create + # The real API refuses a pg_setting on the create it is sent with: "setting cannot be changed; # setting: work_mem" (aws, 2026-08). Seeding one made the whole type undeployable there, and the # 12 rows it bought were all engine errors already -- every transition of this map was rejected diff --git a/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml b/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml index 4acf913b112..8008258a837 100644 --- a/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml +++ b/bundle/direct/autotest/testdata/fields/postgres_synced_tables.yml @@ -1,10 +1,10 @@ # Value library for postgres synced tables. See ../../README.md. -# Postgres is an AWS-only service, so an aws workspace is the one place the cloud run can -# confirm this type. The invariant suite drops these configs from every cloud run rather than -# just the ones without the service; acceptance/bundle/resources/postgres_*/test.toml is the -# closer precedent, disabling gcp and azure by name. -clouds: [aws] +# The branch below names a project this fixture does not create -- one resource per bundle -- so +# on a real workspace there is no parent to attach to and the create fails with "Project +# test-pg-project-... not found". The fake server does not require the parent to exist, so the +# local report stands on its own; confirming it needs a project and branch provisioned first. +local_only: needs a postgres project and branch the suite does not provision base: synced_table_id: lakebase_$UNIQUE_NAME.public.trips_synced source_table_full_name: main.raw.trips From f973da3650881c2fe689ce815e9523d4db9cf27a Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 08:31:41 +0200 Subject: [PATCH 086/118] Field catalog: keep the error's cause in a row's detail A row's detail is one line, cut at 140 characters from the front, so a long URL in the middle pushed out the part that says what happened. A registered_models cloud failure read PLAN_ERROR cannot plan ...: reading id="...": Get "https://dbc-61ef35eb-01e7.cloud.databr... which is the question, not the answer: whether that was a timeout, a reset or a 403 decides whether it is a finding at all, and it had been truncated away. The URL identifies nothing a reader can use -- the resource is the row's own field and the id is redacted -- so it is elided, and what remains is cut in the middle rather than at the end, keeping both which operation failed and what answered. The committed goldens gain the error code they had been losing: every backend rejection now ends in its own "(400 INVALID_PARAMETER_VALUE)" instead of stopping mid-sentence. Co-authored-by: Isaac --- bundle/direct/autotest/fields_test.go | 14 +++- bundle/direct/autotest/output/pipelines.txt | 12 ++-- .../autotest/output/postgres_projects.txt | 66 +++++++++---------- .../direct/autotest/output/secret_scopes.txt | 4 +- .../direct/autotest/output/sql_warehouses.txt | 4 +- bundle/direct/autotest/values_unit_test.go | 13 ++++ 6 files changed, 69 insertions(+), 44 deletions(-) diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index 50804332347..98e621031fd 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -1069,8 +1069,20 @@ func oneLine(s string) string { s = redactIDs(s) s = strings.ReplaceAll(s, "\n", " ") s = strings.Join(strings.Fields(s), " ") + // A long URL in the middle pushes out the part that says what went wrong -- a request that + // never completed reads as `Get "https://…": read tcp …: operation timed out`, and truncating + // at 140 characters kept the host and dropped the cause. The URL identifies nothing a reader + // of the report can use: the resource is already the row's own field, and the id is redacted. + s = urlPattern.ReplaceAllString(s, `"…"`) if len(s) > 140 { - s = s[:140] + "..." + // Elided in the middle, keeping both ends: the front says which resource and operation, + // the back says what the backend or the network answered. Cutting at the front dropped + // the answer, which is the half a reader needs. + s = s[:90] + "..." + s[len(s)-47:] } return s } + +// urlPattern matches a quoted http(s) URL, which carries a workspace host and a long path and +// crowds out the error that follows it. +var urlPattern = regexp.MustCompile(`"https?://[^"]*"`) diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index c4b3b9cdd25..997e07a513e 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -1,9 +1,9 @@ -catalog absent x BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... -catalog absent y BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... -catalog x absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... -catalog x y BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... -catalog y absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... -catalog y x BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing pipeline with defined storage location, if you wa... +catalog absent x BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) +catalog absent y BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) +catalog x absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) +catalog x y BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) +catalog y absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) +catalog y x BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) dry_run absent true UPDATE_IGNORED dry_run false true UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/postgres_projects.txt b/bundle/direct/autotest/output/postgres_projects.txt index 2bf0dd05cfe..aa8b5b82bfd 100644 --- a/bundle/direct/autotest/output/postgres_projects.txt +++ b/bundle/direct/autotest/output/postgres_projects.txt @@ -1,36 +1,36 @@ -budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.budget_policy_id' is in updat... -custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... -custom_tags absent len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... -custom_tags len0 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... -custom_tags len0 len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... -custom_tags len1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... -custom_tags len1 len0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.custom_tags' is in update_mas... -custom_tags[0].key absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].key y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -custom_tags[0].value y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.default_endpoint_settings.aut... -default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -default_endpoint_settings.no_suspension true false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Unknown field path in update_mask: 'spec.... -display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.display_name' is in update_ma... -enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQUE_NAME]: Field 'spec.enable_pg_native_login' is in... +budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags absent len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags len0 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags len0 len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags len1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags len1 len0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags[0].key absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...stom_tags[0].key' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].key absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...stom_tags[0].key' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].key x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...stom_tags[0].key' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].key x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...stom_tags[0].key' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].key y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...stom_tags[0].key' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].key y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...stom_tags[0].key' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].value absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...om_tags[0].value' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].value absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...om_tags[0].value' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].value x absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...om_tags[0].value' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].value x y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...om_tags[0].value' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].value y absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...om_tags[0].value' (400 INVALID_PARAMETER_VALUE) +custom_tags[0].value y x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...om_tags[0].value' (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.no_suspension absent true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...gs.no_suspension' (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...gs.no_suspension' (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...gs.no_suspension' (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.no_suspension true false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...gs.no_suspension' (400 INVALID_PARAMETER_VALUE) +display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) === summary OK 23 diff --git a/bundle/direct/autotest/output/secret_scopes.txt b/bundle/direct/autotest/output/secret_scopes.txt index e4fa3ffc993..bb9f6335fda 100644 --- a/bundle/direct/autotest/output/secret_scopes.txt +++ b/bundle/direct/autotest/output/secret_scopes.txt @@ -1,5 +1,5 @@ -backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... -backend_type AZURE_KEYVAULT absent BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! (400 INVALID_PARA... +backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyV...Metadata defined! (400 INVALID_PARAMETER_VALUE) +backend_type AZURE_KEYVAULT absent BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyV...Metadata defined! (400 INVALID_PARAMETER_VALUE) === summary NOT_OBSERVABLE 10 diff --git a/bundle/direct/autotest/output/sql_warehouses.txt b/bundle/direct/autotest/output/sql_warehouses.txt index a93d1dabfd5..4cf3b4e4c1a 100644 --- a/bundle/direct/autotest/output/sql_warehouses.txt +++ b/bundle/direct/autotest/output/sql_warehouses.txt @@ -7,8 +7,8 @@ cluster_size absent X-Small START_NOT_REA enable_photon true absent UPDATE_IGNORED max_num_clusters 2 absent UPDATE_IGNORED max_num_clusters 3 absent UPDATE_IGNORED -max_num_clusters absent 2 START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. The value must be greater than or equal to 1, and l... -max_num_clusters absent 3 START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. The value must be greater than or equal to 1, and l... +max_num_clusters absent 2 START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) +max_num_clusters absent 3 START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) name absent x-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) name absent y-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) name x-UNIQUE absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/values_unit_test.go b/bundle/direct/autotest/values_unit_test.go index f49a1921058..5b987307ad3 100644 --- a/bundle/direct/autotest/values_unit_test.go +++ b/bundle/direct/autotest/values_unit_test.go @@ -365,3 +365,16 @@ func paths(fields []field) []string { slices.Sort(out) return out } + +// A row's detail is one line, and a long URL in the middle of an error pushes out the part that +// says what went wrong. +func TestOneLineKeepsTheCause(t *testing.T) { + err := `cannot plan resources.registered_models.foo: reading id="main.default.test-model-abc": ` + + `Get "https://dbc-61ef35eb-01e7.cloud.databricks.com/api/2.1/unity-catalog/models/main.default.test-model-abc?": ` + + `read tcp 10.0.0.1:1234->3.4.5.6:443: read: operation timed out` + + line := oneLine(err) + assert.Contains(t, line, "operation timed out") + assert.NotContains(t, line, "dbc-61ef35eb") + assert.LessOrEqual(t, len(line), 143) +} From 5d5110941ffc647a3de321be215188fc0e5978d8 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 08:34:19 +0200 Subject: [PATCH 087/118] Field catalog: the genie divergence is fixed, not tolerated One of the three known cloud divergences was a bug: the fake server named an untitled genie space "" where the backend names it "New Agent", and that missing default was masking a real defect in this suite. Removing it from the list, and saying so, since it is the shape these entries usually have. Co-authored-by: Isaac --- bundle/direct/autotest/README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bundle/direct/autotest/README.md b/bundle/direct/autotest/README.md index b7b4dfc9b35..8451a1e1dec 100644 --- a/bundle/direct/autotest/README.md +++ b/bundle/direct/autotest/README.md @@ -241,16 +241,19 @@ faithfully, which is worth failing over. The full report goes to - resource types with no value library yet, listed in `output/configs.txt` - fields under a slice or map, listed at the end of each report -Three divergences from a real workspace are known and left as findings rather than modelled, +Two divergences from a real workspace are known and left as findings rather than modelled, because each needs a behavioural change the acceptance suite currently asserts otherwise: - A SQL warehouse reads back `STARTING`, not `RUNNING`, right after create, so a config asking for `started: false` is already satisfied there. Modelling it means the fake server also has to move the warehouse to `RUNNING` on a later read, which the engine's waiter depends on. -- A model serving endpoint's `email_notifications` do not take effect from a create; they are - configured through their own endpoint. -- A Genie space's title cannot be cleared, and the chain then reaches a different value than it - does locally in one transition. +- A model serving endpoint's `email_notifications` do not take effect from a create, so the field + drifts from the moment the resource exists — recorded as `BASELINE_DRIFT`, with the plan behind + it in the full report. + +A third, a Genie space's title, is fixed: the fake server named an untitled space `""` where the +backend names it `New Agent`, and the missing default was masking a bug in this suite. That is the +shape these entries usually have, which is why they are worth chasing rather than tolerating. Two types cannot be driven against the workspace this was verified on, for reasons that are its capacity rather than the engine's behaviour: a cluster never reaches `RUNNING` because the From b51d91a52ad66906374c6be961926331518cd38e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 10:16:42 +0200 Subject: [PATCH 088/118] Two acceptance fixtures asserted behaviour the API does not have Both were Cloud = false, so nothing had ever checked them against a real workspace, and each broke when the fake server started applying a constraint the API actually applies. bind/pipelines/update pre-created a pipeline with neither storage nor catalog and then bound a config asking for catalog: main. Verified directly (aws): such a pipeline is given a default storage location, and a later PUT adding a catalog is refused -- so backend-assigned storage counts, not just storage the user wrote. The test is about binding, so the pre-created pipeline is now a UC pipeline and the bind is again the only thing under test. The fake also carries the API's own message, which names the existing location and the requested catalog. create-scope asserted that an Azure KeyVault scope can be created without the metadata naming the vault. It cannot ("Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined!"), so that case is a musterr now and the test documents the constraint. Also two SQL warehouse defaults the backend applies and the fake did not: enable_photon and spot_instance_policy. A create with only a name, cluster_size and max_num_clusters reads back with both set (verified on aws), where the fake stored the zero value -- so a config asking for the non-default value looked like a no-op, state and remote already agreeing. That was the last divergence in the sql_warehouses report apart from the known STARTING-vs-RUNNING one. Co-authored-by: Isaac --- .../pipelines/update/out.bind-fail.direct.txt | 1 - .../update/out.bind-fail.terraform.txt | 1 - .../update/out.deploy.requests.terraform.json | 3 +-- .../bind/pipelines/update/pipeline.json | 1 + .../cmd/workspace/create-scope/output.txt | 8 ++------ acceptance/cmd/workspace/create-scope/script | 4 +++- libs/testserver/pipelines.go | 15 +++++++++------ libs/testserver/sql_warehouses.go | 19 +++++++++++++++++++ 8 files changed, 35 insertions(+), 17 deletions(-) diff --git a/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.direct.txt b/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.direct.txt index a5e4ca80074..607db25e88c 100644 --- a/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.direct.txt +++ b/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.direct.txt @@ -3,7 +3,6 @@ Plan: update resources.pipelines.foo Changes detected: - ~ catalog: null -> "main" ~ channel: null -> "CURRENT" ~ deployment: null -> {"kind":"BUNDLE","metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/test-pipeline-recreate/default/state/metadata.json"} ~ edition: null -> "ADVANCED" diff --git a/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.terraform.txt b/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.terraform.txt index deacd3b1a20..7e846f456ee 100644 --- a/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.terraform.txt +++ b/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.terraform.txt @@ -10,7 +10,6 @@ Terraform will perform the following actions: # databricks_pipeline.foo will be updated in-place ~ resource "databricks_pipeline" "foo" { - + catalog = "main" + channel = "CURRENT" + edition = "ADVANCED" id = "[NEW_PIPELINE_ID]" diff --git a/acceptance/bundle/deployment/bind/pipelines/update/out.deploy.requests.terraform.json b/acceptance/bundle/deployment/bind/pipelines/update/out.deploy.requests.terraform.json index 3c8d1946b83..cb62d4d01c9 100644 --- a/acceptance/bundle/deployment/bind/pipelines/update/out.deploy.requests.terraform.json +++ b/acceptance/bundle/deployment/bind/pipelines/update/out.deploy.requests.terraform.json @@ -31,7 +31,6 @@ } } ], - "name": "test-pipeline", - "storage": "dbfs:/pipelines/[NEW_PIPELINE_ID]" + "name": "test-pipeline" } } diff --git a/acceptance/bundle/deployment/bind/pipelines/update/pipeline.json b/acceptance/bundle/deployment/bind/pipelines/update/pipeline.json index 0254d7b549f..fca73e374c0 100644 --- a/acceptance/bundle/deployment/bind/pipelines/update/pipeline.json +++ b/acceptance/bundle/deployment/bind/pipelines/update/pipeline.json @@ -1,5 +1,6 @@ { "name": "lakeflow-pipeline", + "catalog": "main", "libraries": [ { "glob": { diff --git a/acceptance/cmd/workspace/create-scope/output.txt b/acceptance/cmd/workspace/create-scope/output.txt index 4487d2945cc..f818405d634 100644 --- a/acceptance/cmd/workspace/create-scope/output.txt +++ b/acceptance/cmd/workspace/create-scope/output.txt @@ -16,10 +16,6 @@ Error: expected JSON object, received sequence Warning: unknown field: keyvault_metadata in (inline):4:4 +Error: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! ->>> [CLI] secrets delete-scope xxxxxxxx-xxx - ->>> [CLI] secrets create-scope --scope-backend-type AZURE_KEYVAULT --json { - "scope": "xxxxxxxx-xxx", - "scope_backend_type": "AZURE_KEYVAULT" -} +Exit code: 1 diff --git a/acceptance/cmd/workspace/create-scope/script b/acceptance/cmd/workspace/create-scope/script index 240af51bc41..6ee4dfb5e6a 100644 --- a/acceptance/cmd/workspace/create-scope/script +++ b/acceptance/cmd/workspace/create-scope/script @@ -13,7 +13,9 @@ trace $CLI secrets create-scope --scope-backend-type AZURE_KEYVAULT --json '{ trace $CLI secrets delete-scope xxxxxxxx-xxx -trace $CLI secrets create-scope --scope-backend-type AZURE_KEYVAULT --json '{ +# The API requires the metadata that names the vault: "Scope with Azure KeyVault must have +# AzureKeyVaultSecretScopeMetadata defined!" +trace musterr $CLI secrets create-scope --scope-backend-type AZURE_KEYVAULT --json '{ "scope": "xxxxxxxx-xxx", "scope_backend_type": "AZURE_KEYVAULT" }' diff --git a/libs/testserver/pipelines.go b/libs/testserver/pipelines.go index e42686189d7..d75ec98b811 100644 --- a/libs/testserver/pipelines.go +++ b/libs/testserver/pipelines.go @@ -117,17 +117,20 @@ func (s *FakeWorkspace) PipelineUpdate(req Request, pipelineId string) Response } } - // A pipeline that has a storage location is not a Unity Catalog pipeline, and the backend - // will not convert one into the other in place (aws, 2026-08). Only the fake server having - // allowed it hid the fact that the engine neither recreates nor rejects the change: it sends - // the update and the API refuses it. The condition is the API's own -- a UC pipeline, which - // has no storage, can have its catalog edited. + // A pipeline that has a storage location is not a Unity Catalog pipeline, and the backend will + // not convert one into the other in place. Verified directly (aws, 2026-09): a pipeline created + // with neither storage nor catalog is given a default storage location, and a later PUT adding + // a catalog is refused -- so a backend-assigned storage counts, not just one the user wrote. + // Only the fake server having allowed it hid the fact that the engine neither recreates nor + // rejects the change: it sends the update and the API refuses it. if item.Spec != nil && item.Spec.Storage != "" && spec.Catalog != "" && spec.Catalog != item.Spec.Catalog { return Response{ StatusCode: 400, Body: map[string]string{ "error_code": "INVALID_PARAMETER_VALUE", - "message": "Cannot add catalog to an existing pipeline with defined storage location, if you want to migrate to Unity Catalog, please use the migration API.", + "message": fmt.Sprintf("Cannot add catalog to an existing pipeline with defined storage location, "+ + "if you want to use UC create a new pipeline and set catalog.\nExisting storage location: %q\nRequested catalog: %q", + item.Spec.Storage, spec.Catalog), }, } } diff --git a/libs/testserver/sql_warehouses.go b/libs/testserver/sql_warehouses.go index c226d67357a..24b391f0e1c 100644 --- a/libs/testserver/sql_warehouses.go +++ b/libs/testserver/sql_warehouses.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "slices" "github.com/databricks/databricks-sdk-go/service/sql" ) @@ -36,6 +37,24 @@ func sqlWarehouseFixUps(warehouse *sql.GetWarehouseResponse, userName string) { if warehouse.WarehouseType == "" { warehouse.WarehouseType = sql.GetWarehouseResponseWarehouseTypeClassic } + + // Defaults the backend applies to a create that omits them (observed on aws, 2026-09: a + // create with only a name, cluster_size and max_num_clusters reads back with both set). + // Storing the zero value instead made a config asking for the non-default value look like a + // no-op, since state and remote already agreed on it. + if !warehouse.EnablePhoton && !forceSent(warehouse.ForceSendFields, "EnablePhoton") { + warehouse.EnablePhoton = true + warehouse.ForceSendFields = append(warehouse.ForceSendFields, "EnablePhoton") + } + if warehouse.SpotInstancePolicy == "" { + warehouse.SpotInstancePolicy = sql.SpotInstancePolicyCostOptimized + } +} + +// forceSent reports whether a field was explicitly present in the request body, as opposed to +// omitted: an explicit false and an absent one are the same zero value otherwise. +func forceSent(fields []string, name string) bool { + return slices.Contains(fields, name) } func (s *FakeWorkspace) SqlWarehousesUpsert(req Request, warehouseId string) Response { From a82e1e883b776fc6bac1762f37ba9e4d088857ab Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 10:21:46 +0200 Subject: [PATCH 089/118] Field catalog: three app fields the cloud run says are not testable here git_repository's url and provider are validated as a set -- "Git repository requires both URL and provider to be defined" -- so moving either on its own leaves the other unset and the API refuses. Neither is testable until a fixture declares a coherent block for them to vary inside, which is the same shape as a job's git_source. Both spellings are skipped: the app has a top-level git_repository and one under git_source. compute_max_instances and compute_min_instances need a workspace feature: "Manual instance count configuration is not enabled in this workspace." The fake server accepts them, so the local rows for those two were recording the fake and nothing else. Six OK rows and four UPDATE_IGNORED become four honest SKIPPED rows. That is the trade this catalog keeps making, and the right one: an OK that only the fake server has ever agreed to is worth less than a gap with the API's own reason next to it. Co-authored-by: Isaac --- bundle/direct/autotest/output/apps.txt | 18 ++++-------------- bundle/direct/autotest/output/pipelines.txt | 12 ++++++------ .../direct/autotest/testdata/fields/apps.yml | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/bundle/direct/autotest/output/apps.txt b/bundle/direct/autotest/output/apps.txt index 2020abfb9de..4619820810c 100644 --- a/bundle/direct/autotest/output/apps.txt +++ b/bundle/direct/autotest/output/apps.txt @@ -1,7 +1,3 @@ -compute_max_instances 1 absent UPDATE_IGNORED -compute_max_instances 2 absent UPDATE_IGNORED -compute_min_instances 1 absent UPDATE_IGNORED -compute_min_instances 2 absent UPDATE_IGNORED config.command absent len1 START_NOT_REACHED no active deployment config.command absent len2 START_NOT_REACHED no active deployment config.command len1 absent START_NOT_REACHED no active deployment @@ -69,10 +65,6 @@ git_source.git_repository.caller_credential_id 2 1 START_NOT_R git_source.git_repository.caller_credential_id 2 absent START_NOT_REACHED no active deployment git_source.git_repository.caller_credential_id absent 1 SUPPRESSED no active deployment git_source.git_repository.caller_credential_id absent 2 SUPPRESSED no active deployment -git_source.git_repository.provider x y START_NOT_REACHED no active deployment -git_source.git_repository.provider y x START_NOT_REACHED no active deployment -git_source.git_repository.url x y START_NOT_REACHED no active deployment -git_source.git_repository.url y x START_NOT_REACHED no active deployment git_source.resolved_commit absent x SUPPRESSED no active deployment git_source.resolved_commit absent y SUPPRESSED no active deployment git_source.resolved_commit x absent START_NOT_REACHED no active deployment @@ -91,7 +83,6 @@ git_source.tag x absent START_NOT_REA git_source.tag x y START_NOT_REACHED no active deployment git_source.tag y absent START_NOT_REACHED no active deployment git_source.tag y x START_NOT_REACHED no active deployment -lifecycle.started false true COLLATERAL_DRIFT git_source.git_repository source_code_path absent x-UNIQUE SUPPRESSED no active deployment source_code_path absent y-UNIQUE SUPPRESSED no active deployment source_code_path x-UNIQUE absent START_NOT_REACHED no active deployment @@ -104,12 +95,11 @@ user_api_scopes len2 absent UPDATE_IGNORE user_api_scopes len2 len0 UPDATE_IGNORED === summary -OK 46 +OK 35 OK_RECREATE 2 SUPPRESSED 23 NOT_OBSERVABLE 3 -UPDATE_IGNORED 13 -COLLATERAL_DRIFT 1 +UPDATE_IGNORED 9 UNSETTABLE 2 -SKIPPED 84 -START_NOT_REACHED 72 +SKIPPED 90 +START_NOT_REACHED 68 diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index 997e07a513e..d33cb411b04 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -1,9 +1,9 @@ -catalog absent x BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) -catalog absent y BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) -catalog x absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) -catalog x y BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) -catalog y absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) -catalog y x BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...he migration API. (400 INVALID_PARAMETER_VALUE) +catalog absent x BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) +catalog absent y BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) +catalog x absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) +catalog x y BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) +catalog y absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) +catalog y x BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) dry_run absent true UPDATE_IGNORED dry_run false true UPDATE_IGNORED diff --git a/bundle/direct/autotest/testdata/fields/apps.yml b/bundle/direct/autotest/testdata/fields/apps.yml index 49f742b052c..ee2c7b29116 100644 --- a/bundle/direct/autotest/testdata/fields/apps.yml +++ b/bundle/direct/autotest/testdata/fields/apps.yml @@ -26,3 +26,18 @@ skip: # test workspace has none to reference, and creating one is an account-level operation. budget_policy_id: needs an account-level budget policy to exist usage_policy_id: needs an account-level usage policy to exist + + # The API validates the block as a set: "Git repository requires both URL and provider to be + # defined." Moving either leaf on its own leaves the other unset, so neither is testable until + # the fixture declares a coherent block for them to vary inside -- the same shape as a job's + # git_source, which base seeds for exactly that reason. + git_source.git_repository.url: validated as a set with the provider, which the fixture does not declare + git_source.git_repository.provider: validated as a set with the url, which the fixture does not declare + git_repository.url: validated as a set with the provider, which the fixture does not declare + git_repository.provider: validated as a set with the url, which the fixture does not declare + + # "Manual instance count configuration is not enabled in this workspace." A workspace-level + # feature, so the local report for these two records what the fake server allows, not what a + # workspace does. + compute_max_instances: needs manual instance count enabled on the workspace + compute_min_instances: needs manual instance count enabled on the workspace From 9484e47070541523375f87919506204835262830 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 10:32:28 +0200 Subject: [PATCH 090/118] Field catalog: a serving endpoint's create does not apply email_notifications The last of the three README divergences, and it turned out to be checkable: the plan the cloud run now records as evidence shows remote_state with no email_notifications at all, straight after a create that asked for them. An update does apply them -- those rows are OK on cloud -- so the fake server now drops them at create and keeps applying them on update, and the endpoint drifts from the moment it exists, exactly as it does on a real workspace. This sharpens the acceptance test that already carried the note "There a bug in TF where it does not actually update the email notifications for a serving endpoint": with the create no longer storing them, the direct engine still lands user2@example.com and terraform now lands nothing at all. The fake having stored the create's value was hiding half of that bug. Co-authored-by: Isaac --- ...out.get_email_notifications.terraform.json | 6 +---- .../email-notifications/out.plan.direct.json | 26 ++++++++++--------- .../update/email-notifications/output.txt | 6 +---- .../output/model_serving_endpoints.txt | 2 ++ libs/testserver/serving_endpoints.go | 18 ++++++++----- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.get_email_notifications.terraform.json b/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.get_email_notifications.terraform.json index 76b5ef08c2c..19765bd501b 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.get_email_notifications.terraform.json +++ b/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.get_email_notifications.terraform.json @@ -1,5 +1 @@ -{ - "on_update_success": [ - "user1@example.com" - ] -} +null diff --git a/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.plan.direct.json b/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.plan.direct.json index cf8c8b3f0ca..4529fa116f0 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.plan.direct.json +++ b/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.plan.direct.json @@ -60,11 +60,6 @@ "creation_timestamp": [UNIX_TIME_MILLIS][0], "creator": "[USERNAME]", "description": "", - "email_notifications": { - "on_update_success": [ - "user1@example.com" - ] - }, "id": "[UUID]", "last_updated_timestamp": [UNIX_TIME_MILLIS][0], "name": "[ENDPOINT_ID]", @@ -100,11 +95,6 @@ ] } }, - "email_notifications": { - "on_update_success": [ - "user1@example.com" - ] - }, "name": "[ENDPOINT_ID]" }, "changes": { @@ -126,11 +116,23 @@ "reason": "empty", "remote": "" }, + "email_notifications": { + "action": "update", + "old": { + "on_update_success": [ + "user1@example.com" + ] + }, + "new": { + "on_update_success": [ + "user2@example.com" + ] + } + }, "email_notifications.on_update_success[0]": { "action": "update", "old": "user1@example.com", - "new": "user2@example.com", - "remote": "user1@example.com" + "new": "user2@example.com" }, "route_optimized": { "action": "skip", diff --git a/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/output.txt b/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/output.txt index 077c27ab917..fcb206aad5a 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/output.txt +++ b/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/output.txt @@ -6,11 +6,7 @@ Files: 5 uploaded, 0 deleted Resources: 1 created, 0 changed, 0 deleted, 0 unchanged >>> [CLI] serving-endpoints get [ENDPOINT_ID] -{ - "on_update_success": [ - "user1@example.com" - ] -} +null >>> update_file.py databricks.yml user1@example.com user2@example.com diff --git a/bundle/direct/autotest/output/model_serving_endpoints.txt b/bundle/direct/autotest/output/model_serving_endpoints.txt index c44169eaec3..03f45917f21 100644 --- a/bundle/direct/autotest/output/model_serving_endpoints.txt +++ b/bundle/direct/autotest/output/model_serving_endpoints.txt @@ -1,3 +1,4 @@ +email_notifications BASELINE_DRIFT drifts with no config change === summary OK 32 @@ -5,4 +6,5 @@ OK_RECREATE 12 OK_INERT 6 SUPPRESSED 5 NOT_OBSERVABLE 3 +BASELINE_DRIFT 1 SKIPPED 4 diff --git a/libs/testserver/serving_endpoints.go b/libs/testserver/serving_endpoints.go index 12899f53531..aa6af369cc3 100644 --- a/libs/testserver/serving_endpoints.go +++ b/libs/testserver/serving_endpoints.go @@ -273,13 +273,17 @@ func (s *FakeWorkspace) ServingEndpointCreate(req Request) Response { now := nowMilli() endpoint := serving.ServingEndpointDetailed{ - AiGateway: createReq.AiGateway, - BudgetPolicyId: createReq.BudgetPolicyId, - Config: config, - CreationTimestamp: now, - Creator: s.CurrentUser().UserName, - Description: createReq.Description, - EmailNotifications: createReq.EmailNotifications, + AiGateway: createReq.AiGateway, + BudgetPolicyId: createReq.BudgetPolicyId, + Config: config, + CreationTimestamp: now, + Creator: s.CurrentUser().UserName, + Description: createReq.Description, + // Not carried over from the create: a real workspace does not echo the notifications a + // create asked for (aws, 2026-09 -- the field is absent from a GET straight afterwards), + // so the endpoint drifts from the moment it exists. An update does apply them, which is + // why they are set in the update handler below and not here. + EmailNotifications: nil, Id: nextUUID(), LastUpdatedTimestamp: now, Name: createReq.Name, From 043cadf78607b13cb8ebcc7fd2cc0d386adc6f5b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 10:38:01 +0200 Subject: [PATCH 091/118] Field catalog: three more from the re-review Reading a row's field out of a fixed-width column was wrong: a pipeline's ingestion_definition paths reach 130 characters, so the column held a truncated path matching nothing, and the row vanished from both sides of a sampled comparison -- the one thing that comparison must never do. Matched by prefix now, which cannot truncate. Dropping every COLLATERAL_DRIFT row from a sampled comparison was too broad. The row is dropped because its cause is a field this run may not have sampled, so now only those are: a sampled field turning from OK into COLLATERAL_DRIFT is a regression like any other. oneLine truncated by byte index, and the ellipsis it inserts for an elided URL is multi-byte, so a cut could land inside a rune and put invalid UTF-8 in a golden. By rune now, with a test that fails on the old code. Two findings are declined, with reasons. The fake server is still weaker than the warehouse API in ways the catalog does not exercise -- a whitespace-only name, min_num_clusters above max, an edit setting max to 41 -- and modelling those would be speculative: the three that are modelled are the three a cloud run actually rejected. And the reviewer would rather the postgres types were made to work on cloud than declared local_only; that means provisioning a project and branch per run and modelling a soft delete whose whole effect is to make the local run fail too. The report says what blocks them in the API's own words, which is the honest state. A sampled run still cannot see a field move between two passing verdicts, since the compared golden holds only findings. That is documented in the README rather than fixed: catching it needs the full report committed, which is the thing this design deliberately does not do. Co-authored-by: Isaac --- bundle/direct/autotest/report_test.go | 48 +++++++++++++++++++--- bundle/direct/autotest/values_unit_test.go | 11 +++++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/bundle/direct/autotest/report_test.go b/bundle/direct/autotest/report_test.go index 84d685c1582..1b7b008b7a4 100644 --- a/bundle/direct/autotest/report_test.go +++ b/bundle/direct/autotest/report_test.go @@ -433,6 +433,42 @@ func (r *report) write(t testutil.TestingT) { testdiff.AssertEqualTexts(t, name, name, testdiff.NormalizeNewlines(string(expected)), body) } +// collateralCause returns the field a COLLATERAL_DRIFT row blames, which the renderer puts in the +// detail column. Reports false for any other row. +func collateralCause(line string) (string, bool) { + marker := " " + string(verdictCollateral) + " " + _, after, ok := strings.Cut(line, marker) + if !ok { + return "", false + } + // The detail is a comma-separated list of drifting paths; the first is enough to decide + // whether this run had any business reproducing the row. + detail := strings.TrimSpace(after) + cause, _, _ := strings.Cut(detail, ",") + return cause, true +} + +// rowField reports whether a rendered row belongs to a sampled field, or to the harness itself. +// +// Matched by prefix rather than by reading a fixed-width column: a field path can be far longer +// than the column (a pipeline's ingestion_definition reaches 130 characters), and the column then +// holds a truncated path that matches nothing -- silently dropping the row from both sides, which +// is the one thing this comparison must not do. +func rowField(line string, sampled map[string]bool) bool { + // A row the harness records against itself -- "(base config)", "(rebuild)", "(baseline)" -- + // says the run for that type did not happen, so no sample may hide it. + if strings.HasPrefix(line, "(") { + return true + } + for field := range sampled { + // The separator matters: without it "tags" would also claim "tags_extra". + if strings.HasPrefix(line, field+" ") { + return true + } + } + return false +} + // sampledRows keeps the report rows belonging to the sampled fields, dropping the summary and // blank lines. Rows the harness records against itself rather than a field -- "(base config)", // "(rebuild)", "(baseline)" -- are always kept: they mean the run for that type did not happen, @@ -449,14 +485,14 @@ func sampledRows(body string, sampled map[string]bool) string { if line == "" { continue } - // A verdict whose cause is another field cannot be reproduced by a run that did not - // sample that field: COLLATERAL_DRIFT is drift the plan attributes to something else, - // and its detail names what. Dropped from both sides rather than compared. - if strings.Contains(line, string(verdictCollateral)) { + // COLLATERAL_DRIFT is drift the plan attributes to another field, which its detail names. + // A run that did not sample that other field cannot reproduce the row, so it is dropped -- + // but only then. Dropping every one of them would hide a sampled field turning from OK + // into COLLATERAL_DRIFT, which is a regression like any other. + if cause, ok := collateralCause(line); ok && !sampled[cause] { continue } - field := strings.TrimRight(line[:min(fieldColumnWidth, len(line))], " ") - if sampled[field] || strings.HasPrefix(field, "(") { + if rowField(line, sampled) { sb.WriteString(line + "\n") } } diff --git a/bundle/direct/autotest/values_unit_test.go b/bundle/direct/autotest/values_unit_test.go index 5b987307ad3..2fe3cd1b1c1 100644 --- a/bundle/direct/autotest/values_unit_test.go +++ b/bundle/direct/autotest/values_unit_test.go @@ -6,6 +6,7 @@ import ( "slices" "strings" "testing" + "unicode/utf8" "github.com/databricks/cli/libs/structs/structpath" @@ -378,3 +379,13 @@ func TestOneLineKeepsTheCause(t *testing.T) { assert.NotContains(t, line, "dbc-61ef35eb") assert.LessOrEqual(t, len(line), 143) } + +// Truncation is by rune, since an API message can carry any character and the ellipsis oneLine +// inserts for a URL is itself multi-byte: a byte index can land inside one. +func TestOneLineKeepsValidUTF8(t *testing.T) { + // Long enough to truncate, and multi-byte throughout so every cut lands inside a rune if the + // truncation is done by byte. + line := oneLine(strings.Repeat("héllo wörld ", 20)) + assert.True(t, utf8.ValidString(line), "%q is not valid UTF-8", line) + assert.Contains(t, line, "...") +} From d605303cb2f8db90bc0032d30cecca02268ea699 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 11:09:44 +0200 Subject: [PATCH 092/118] Field catalog: a cluster's autotermination has a minimum The generic integer values 1 and 2 are both refused: "The cluster autotermination time cannot be less than 10 minutes." The fake server stored them, so all six rows for the field were measuring the fake and nothing else. Values are 10 and 20 now, and the fake applies the range on create and on edit, so the constraint holds locally too -- verified by putting 1 back and watching it fail. Zero is also accepted and means never, but the CLI defaults the field to 60, so 0 is what a user writes to turn autotermination off rather than a second ordinary value. Every acceptance config already uses 30 or 60, so nothing there changes. The type's remaining cloud failures are this workspace's capacity -- INSTANCE_GROUP_MAX_CAPACITY_REACHED from the instance pool the fixture references -- which is why clusters is still not marked local_only: one workspace running out of instances is not a property of the suite. Co-authored-by: Isaac --- .../autotest/testdata/fields/clusters.yml | 6 ++++ libs/testserver/clusters.go | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/bundle/direct/autotest/testdata/fields/clusters.yml b/bundle/direct/autotest/testdata/fields/clusters.yml index a52cbe87f94..722380e65e5 100644 --- a/bundle/direct/autotest/testdata/fields/clusters.yml +++ b/bundle/direct/autotest/testdata/fields/clusters.yml @@ -22,6 +22,12 @@ base: - workspace: destination: /Users/$CURRENT_USER_NAME/init.sh +fields: + # The API refuses anything between 1 and 9: "The cluster autotermination time cannot be less than + # 10 minutes." 0 disables it and is accepted too, but the CLI defaults the field to 60, so 0 is + # what a user writes to turn it off rather than a second ordinary value. + autotermination_minutes: [10, 20] + skip: # "The field 'worker_node_type_flexibility' cannot be supplied when an instance pool ID is # provided", and the corpus config provides one. The alternates would also have to name a diff --git a/libs/testserver/clusters.go b/libs/testserver/clusters.go index 80991b0091a..ea2042c1beb 100644 --- a/libs/testserver/clusters.go +++ b/libs/testserver/clusters.go @@ -20,6 +20,10 @@ func (s *FakeWorkspace) ClustersCreate(req Request) any { defer s.LockUnlock()() + if response, ok := rejectAutotermination(request.AutoterminationMinutes); !ok { + return response + } + clusterId := nextUUID() request.ClusterId = clusterId // Clusters start in PENDING state when created; ClustersGet transitions them to RUNNING. @@ -65,6 +69,26 @@ func specSnapshot(body []byte) *compute.ClusterSpec { } } +// minAutoterminationMinutes is the shortest autotermination the API accepts. Zero is also accepted +// and means never. +const minAutoterminationMinutes = 10 + +// rejectAutotermination applies the API's own range check (aws, 2026-09), which the fake server did +// not: a value of 1 was stored happily here and refused there, so the catalog's rows for the field +// were measuring nothing. +func rejectAutotermination(minutes int) (Response, bool) { + if minutes == 0 || minutes >= minAutoterminationMinutes { + return Response{}, true + } + return Response{ + StatusCode: 400, + Body: map[string]string{ + "error_code": "INVALID_PARAMETER_VALUE", + "message": "The cluster autotermination time cannot be less than 10 minutes.", + }, + }, false +} + func (s *FakeWorkspace) ClustersResize(req Request) any { var request compute.ResizeCluster if err := json.Unmarshal(req.Body, &request); err != nil { @@ -110,6 +134,10 @@ func (s *FakeWorkspace) ClustersEdit(req Request) any { return Response{StatusCode: 404} } + if response, ok := rejectAutotermination(request.AutoterminationMinutes); !ok { + return response + } + // Preserve runtime-only fields that the Edit API request doesn't include. request.State = existing.State request.ClusterId = existing.ClusterId From d7d1fed8d6e1f99d4f688ed816120207357e8ee1 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 11:28:48 +0200 Subject: [PATCH 093/118] Field catalog: nine job blocks the API validates as a set The jobs cloud run found the same shape nine times over, each in the API's own words: "Invalid trigger settings. One of 'schedule', 'trigger' or 'continuous' can be set." "Field 'new_settings.trigger.table_update.table_names' is required, expected non-empty collection!" "Missing required field: new_settings.trigger.model.condition" "dependencies and java_dependencies cannot be provided at the same time" "Sparse checkout pattern must a valid file path" "Node type x is not supported." "S3 cluster log destination is provided without region" trigger is a choice -- file_arrival, table_update, model, periodic -- and base seeds periodic, so the other three members cannot be reached by moving a leaf inside them; covering those needs a second fixture per member, which is a coverage change rather than a fix. The rest are blocks the API accepts only whole, or that hold cloud-specific node type ids the suite has one variable for. 136 of the 377 diverging rows were these, all of them recording what the fake server allows rather than what a workspace does. They are SKIPPED with the reason now. Co-authored-by: Isaac --- bundle/direct/autotest/output/jobs.txt | 8 +++--- .../direct/autotest/testdata/fields/jobs.yml | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/bundle/direct/autotest/output/jobs.txt b/bundle/direct/autotest/output/jobs.txt index da8f019bc59..67532dc8338 100644 --- a/bundle/direct/autotest/output/jobs.txt +++ b/bundle/direct/autotest/output/jobs.txt @@ -6,9 +6,9 @@ parent_path y absent UPDATE_IGNORE parent_path y x UPDATE_IGNORED === summary -OK 693 +OK 561 OK_INERT 6 -SUPPRESSED 53 -NOT_OBSERVABLE 40 +SUPPRESSED 47 +NOT_OBSERVABLE 34 UPDATE_IGNORED 6 -SKIPPED 46 +SKIPPED 54 diff --git a/bundle/direct/autotest/testdata/fields/jobs.yml b/bundle/direct/autotest/testdata/fields/jobs.yml index ccabeac5816..550f2aff88f 100644 --- a/bundle/direct/autotest/testdata/fields/jobs.yml +++ b/bundle/direct/autotest/testdata/fields/jobs.yml @@ -131,6 +131,33 @@ skip: # A trigger, a schedule and a continuous block are three arms of one choice, so a schedule # field cannot move on a job the corpus config gives no schedule. schedule: one of schedule, trigger or continuous, and the config declares none + # trigger is a choice between file_arrival, table_update, model and periodic, and base seeds + # periodic. Each other member is rejected as a set, in its own words: + # "Invalid trigger settings. One of 'schedule', 'trigger' or 'continuous' can be set." + # "Field 'new_settings.trigger.table_update.table_names' is required, expected non-empty collection!" + # "Missing required field: new_settings.trigger.model.condition" + # Covering them needs a second fixture per member rather than a value for a leaf inside one. + trigger.file_arrival: a trigger member the seeded periodic trigger excludes + trigger.table_update: a trigger member the seeded periodic trigger excludes + trigger.model: a trigger member the seeded periodic trigger excludes + + # "dependencies and java_dependencies cannot be provided at the same time for environment key + # seeded", and base seeds dependencies. + environments[*].spec.java_dependencies: cannot be set alongside the seeded dependencies + + # "Sparse checkout pattern must a valid file path and cannot exceed 2048 characters" -- the + # generic x and y are not paths. + git_source.sparse_checkout: needs a valid file path, which no generic string value is + + # Both hold node type ids, and a node type is cloud-specific: "Node type x is not supported." + # The suite has one node type variable, so there is no second value to move to. + job_clusters[*].new_cluster.worker_node_type_flexibility: cloud-specific node type ids + job_clusters[*].new_cluster.driver_node_type_flexibility: cloud-specific node type ids + + # "S3 cluster log destination is provided without region" -- the block is validated as a set, + # and a region is cloud-specific. + job_clusters[*].new_cluster.cluster_log_conf.s3: validated as a set with a cloud-specific region + # Only one of the three cloud attribute blocks applies, decided by the workspace's cloud. job_clusters[*].new_cluster.azure_attributes: cloud-specific; only one attribute block applies job_clusters[*].new_cluster.gcp_attributes: cloud-specific; only one attribute block applies From 646c4a80f7000c705e8a1b915defbd2517aded2f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 11:44:05 +0200 Subject: [PATCH 094/118] Field catalog: an init script's other members are not reachable from the seeded one An init script entry carries exactly one of workspace, volumes, s3, abfss, gcs, dbfs or file, and both the pipelines and clusters fixtures seed the workspace member -- their own comments say so. The suite was then setting leaves inside the sibling arms, which leaves an arm with no destination of its own. The backend accepts that and ignores it; the fake server stored it and echoed it back, so 80 rows across the two types read OK where a workspace ignores the write entirely. Skipped with that reason, which also subsumes the narrower s3.kms_key skip each fixture carried. Covering an arm needs a fixture that seeds it, the same conclusion as the job trigger members. clusters is the type this could not have been found on directly -- its cloud run dies on the workspace's instance pool capacity -- so the pipelines run is what exposed it. Both fixtures carry the same cluster spec, which is why fixing one without the other would have left the same latent gap. Co-authored-by: Isaac --- bundle/direct/autotest/output/clusters.txt | 6 +++--- bundle/direct/autotest/output/pipelines.txt | 6 +++--- bundle/direct/autotest/testdata/fields/clusters.yml | 12 ++++++++++++ .../direct/autotest/testdata/fields/pipelines.yml | 13 ++++++++++++- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/bundle/direct/autotest/output/clusters.txt b/bundle/direct/autotest/output/clusters.txt index 97e620e06b5..c6c93ce793c 100644 --- a/bundle/direct/autotest/output/clusters.txt +++ b/bundle/direct/autotest/output/clusters.txt @@ -1,6 +1,6 @@ === summary -OK 429 -SUPPRESSED 22 +OK 383 +SUPPRESSED 20 NOT_OBSERVABLE 9 -SKIPPED 4 +SKIPPED 10 diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index d33cb411b04..0ae900d0f8a 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -8,12 +8,12 @@ dry_run absent true UPDATE_IGNORE dry_run false true UPDATE_IGNORED === summary -OK 736 +OK 696 OK_RECREATE 9 OK_INERT 12 -SUPPRESSED 46 +SUPPRESSED 44 NOT_OBSERVABLE 29 UPDATE_IGNORED 2 BACKEND_ERROR 2 -SKIPPED 12 +SKIPPED 17 BASE_ERROR 4 diff --git a/bundle/direct/autotest/testdata/fields/clusters.yml b/bundle/direct/autotest/testdata/fields/clusters.yml index 722380e65e5..56032ae8073 100644 --- a/bundle/direct/autotest/testdata/fields/clusters.yml +++ b/bundle/direct/autotest/testdata/fields/clusters.yml @@ -29,6 +29,18 @@ fields: autotermination_minutes: [10, 20] skip: + # An init script entry carries exactly one of workspace, volumes, s3, abfss, gcs, dbfs or file, + # and base seeds the workspace member. Setting a leaf inside another arm leaves that arm without + # its own destination, which the backend accepts and then ignores -- every such row was recording + # the fake server storing a value the API drops. Covering an arm needs a fixture that seeds it, + # not a value for a leaf inside one that is not there. + init_scripts[*].abfss: an init script member the seeded workspace member excludes + init_scripts[*].dbfs: an init script member the seeded workspace member excludes + init_scripts[*].file: an init script member the seeded workspace member excludes + init_scripts[*].gcs: an init script member the seeded workspace member excludes + init_scripts[*].s3: an init script member the seeded workspace member excludes + init_scripts[*].volumes: an init script member the seeded workspace member excludes + # "The field 'worker_node_type_flexibility' cannot be supplied when an instance pool ID is # provided", and the corpus config provides one. The alternates would also have to name a # second node type, which differs per cloud. diff --git a/bundle/direct/autotest/testdata/fields/pipelines.yml b/bundle/direct/autotest/testdata/fields/pipelines.yml index 6af40411318..ae97530a019 100644 --- a/bundle/direct/autotest/testdata/fields/pipelines.yml +++ b/bundle/direct/autotest/testdata/fields/pipelines.yml @@ -54,7 +54,6 @@ skip: clusters[*].instance_pool_id: needs a second instance pool to exist clusters[*].driver_instance_pool_id: needs a second instance pool to exist clusters[*].policy_id: needs a cluster policy to exist - clusters[*].init_scripts[*].s3.kms_key: needs a KMS key registered for the workspace gateway_definition: needs a UC connection to a source database ingestion_definition.connection_name: needs a UC connection to a source database ingestion_definition.ingestion_gateway_id: needs an ingestion gateway pipeline to exist @@ -66,6 +65,18 @@ skip: # there is none to move it with. trigger.cron: validated as a pair with the cron expression, which the config does not declare + # An init script entry carries exactly one of workspace, volumes, s3, abfss, gcs, dbfs or file, + # and base seeds the workspace member. Setting a leaf inside another arm leaves that arm without + # its own destination, which the backend accepts and then ignores -- every such row was recording + # the fake server storing a value the API drops. Covering an arm needs a fixture that seeds it, + # not a value for a leaf inside one that is not there. + clusters[*].init_scripts[*].abfss: an init script member the seeded workspace member excludes + clusters[*].init_scripts[*].dbfs: an init script member the seeded workspace member excludes + clusters[*].init_scripts[*].file: an init script member the seeded workspace member excludes + clusters[*].init_scripts[*].gcs: an init script member the seeded workspace member excludes + clusters[*].init_scripts[*].s3: an init script member the seeded workspace member excludes + clusters[*].init_scripts[*].volumes: an init script member the seeded workspace member excludes + fields: # Constrained by the backend but typed as a plain string in the SDK, so nothing derives their # values: the generic "x" is rejected on a real workspace. From d2ced65481c0e1f5165535ce939c4ce3ecbe8fec Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 12:36:17 +0200 Subject: [PATCH 095/118] Field catalog: cover every resource type the engine supports The suite drove the 22 types that happened to have a value library and listed the other 12 in a coverage report. Now it iterates SupportedResources itself: a type the engine supports with no library is a failure, not a line in a report, so adding a resource type and forgetting the catalog cannot pass. configs.txt is gone with the report it existed for. The 12 that were missing are mostly children -- a postgres branch needs a project, a database catalog needs an instance, a vector search index needs an endpoint -- so a fixture can now declare `deps`, resources of other types deployed alongside the one under test and referenced from base the way a bundle normally does. No field of a dep is ever tested; it exists so the resource under test can. Their definitions come from the invariant configs, which already prove them deployable. Two mechanics had to give way for that. A fixture may declare `variables`, because a secret's value has to be a variable reference -- the config format refuses a literal, to keep secrets out of config files -- and the value is passed through BUNDLE_VAR_ the way a user would, since a default in the config resolves before that validation runs. And dollar-expansion no longer eats the bundle's own interpolation: a dotted name is a bundle reference and is restored verbatim, an undotted one is a variable the suite provides. That collision is why none of these types could be folded before. 687 new observations. Eight of the twelve are local_only with a reason -- a job run would start a real job per transition, a quality monitor needs a table with data, the postgres chain cannot rebuild a soft-deleted project name. Co-authored-by: Isaac --- bundle/direct/autotest/fields_test.go | 6 +- bundle/direct/autotest/fixtures_test.go | 56 ++++++++-------- bundle/direct/autotest/harness_test.go | 66 +++++++++++++++---- bundle/direct/autotest/output/configs.txt | 37 ----------- .../autotest/output/database_catalogs.txt | 9 +++ bundle/direct/autotest/output/job_runs.txt | 6 ++ .../autotest/output/postgres_branches.txt | 19 ++++++ .../autotest/output/postgres_catalogs.txt | 6 ++ .../autotest/output/postgres_databases.txt | 9 +++ .../autotest/output/postgres_endpoints.txt | 31 +++++++++ .../direct/autotest/output/postgres_roles.txt | 11 ++++ .../autotest/output/quality_monitors.txt | 12 ++++ bundle/direct/autotest/output/secrets.txt | 17 +++++ .../output/synced_database_tables.txt | 14 ++++ .../output/vector_search_endpoints.txt | 5 ++ .../autotest/output/vector_search_indexes.txt | 11 ++++ bundle/direct/autotest/report_test.go | 33 ---------- .../testdata/fields/database_catalogs.yml | 15 +++++ .../autotest/testdata/fields/job_runs.yml | 22 +++++++ .../testdata/fields/postgres_branches.yml | 15 +++++ .../testdata/fields/postgres_catalogs.yml | 14 ++++ .../testdata/fields/postgres_databases.yml | 22 +++++++ .../testdata/fields/postgres_endpoints.yml | 18 +++++ .../testdata/fields/postgres_roles.yml | 19 ++++++ .../testdata/fields/quality_monitors.yml | 16 +++++ .../autotest/testdata/fields/secrets.yml | 16 +++++ .../fields/synced_database_tables.yml | 24 +++++++ .../fields/vector_search_endpoints.yml | 6 ++ .../testdata/fields/vector_search_indexes.yml | 17 +++++ bundle/direct/autotest/values_test.go | 33 ++++++++-- 30 files changed, 465 insertions(+), 120 deletions(-) delete mode 100644 bundle/direct/autotest/output/configs.txt create mode 100644 bundle/direct/autotest/output/database_catalogs.txt create mode 100644 bundle/direct/autotest/output/job_runs.txt create mode 100644 bundle/direct/autotest/output/postgres_branches.txt create mode 100644 bundle/direct/autotest/output/postgres_catalogs.txt create mode 100644 bundle/direct/autotest/output/postgres_databases.txt create mode 100644 bundle/direct/autotest/output/postgres_endpoints.txt create mode 100644 bundle/direct/autotest/output/postgres_roles.txt create mode 100644 bundle/direct/autotest/output/quality_monitors.txt create mode 100644 bundle/direct/autotest/output/secrets.txt create mode 100644 bundle/direct/autotest/output/synced_database_tables.txt create mode 100644 bundle/direct/autotest/output/vector_search_endpoints.txt create mode 100644 bundle/direct/autotest/output/vector_search_indexes.txt create mode 100644 bundle/direct/autotest/testdata/fields/database_catalogs.yml create mode 100644 bundle/direct/autotest/testdata/fields/job_runs.yml create mode 100644 bundle/direct/autotest/testdata/fields/postgres_branches.yml create mode 100644 bundle/direct/autotest/testdata/fields/postgres_catalogs.yml create mode 100644 bundle/direct/autotest/testdata/fields/postgres_databases.yml create mode 100644 bundle/direct/autotest/testdata/fields/postgres_endpoints.yml create mode 100644 bundle/direct/autotest/testdata/fields/postgres_roles.yml create mode 100644 bundle/direct/autotest/testdata/fields/quality_monitors.yml create mode 100644 bundle/direct/autotest/testdata/fields/secrets.yml create mode 100644 bundle/direct/autotest/testdata/fields/synced_database_tables.yml create mode 100644 bundle/direct/autotest/testdata/fields/vector_search_endpoints.yml create mode 100644 bundle/direct/autotest/testdata/fields/vector_search_indexes.yml diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index 98e621031fd..1d441639e41 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -63,7 +63,7 @@ import ( ) func TestFields(t *testing.T) { - driven, undriven := discoverFixtures(t) + driven := drivenTypes(t) // -update writes the report as the whole truth for a type, so it cannot come from a run // that tested a few fields: the missing ones would read as removed. @@ -82,8 +82,6 @@ func TestFields(t *testing.T) { t.Logf("testing %d fields per resource type, seeded from HEAD (%d)", *sampleSize, sampleFields) } - writeCoverageReport(t, driven, undriven) - for _, resourceType := range driven { t.Run(resourceType, func(t *testing.T) { // One resource type per goroutine: the types are independent, and the @@ -647,7 +645,7 @@ func runTransition(t *testing.T, h *bundleHarness, path string, tr transition, s // the subtest would destroy the resource as soon as that transition ended, and every // transition after it would silently be creating a new one. func newBaseline(owner *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, resourceType string, fv *fieldValues, presets ...preset) (*bundleHarness, error) { - h, err := newHarness(owner, ctx, client, user, resourceType, uniqueName(), fv.base) + h, err := newHarness(owner, ctx, client, user, resourceType, uniqueName(), fv.base, fv.deps, fv.variables) if err != nil { return nil, err } diff --git a/bundle/direct/autotest/fixtures_test.go b/bundle/direct/autotest/fixtures_test.go index 4a63f8c8f8b..b86d4959f2d 100644 --- a/bundle/direct/autotest/fixtures_test.go +++ b/bundle/direct/autotest/fixtures_test.go @@ -2,6 +2,7 @@ package autotest import ( "fmt" + "os" "path/filepath" "slices" "strings" @@ -18,50 +19,51 @@ import ( // themselves live in testdata/fields and belong to this suite alone. const dataDir = "../../../acceptance/bundle/invariant/data" -// discoverFixtures returns the resource types this suite drives -- one value library each -- -// and the supported types that have none, so the report accounts for every type the direct -// engine knows rather than only the ones covered. +// drivenTypes returns every resource type the direct engine supports, which is what this suite +// covers: a type with no value library is a failure rather than a line in a report, so adding a +// resource type to the engine and forgetting the catalog cannot go unnoticed. // -// Sub-resources are not among either: permissions and grants are separate plan nodes whose -// fields describe an ACL rather than the resource, they need a parent to attach to, and the -// suite strips them from every fixture. Listing them as gaps would suggest fixtures are missing. -func discoverFixtures(t *testing.T) (driven, undriven []string) { +// Permissions and grants are excluded. They are separate plan nodes whose fields describe an ACL +// rather than the resource, they need a parent to attach to, and the suite strips them from every +// fixture. +func drivenTypes(t *testing.T) []string { + var driven []string + for resourceType := range dresources.SupportedResources { + if strings.Contains(resourceType, ".") { + continue + } + path := filepath.Join(fieldsDir, resourceType+".yml") + _, err := os.Stat(path) + require.NoError(t, err, "%s is supported by the engine but has no value library", resourceType) + driven = append(driven, resourceType) + } + + // A library naming no supported type is a leftover -- a rename that missed one side. entries, err := filepath.Glob(filepath.Join(fieldsDir, "*.yml")) require.NoError(t, err) - require.NotEmpty(t, entries) - - has := map[string]bool{} for _, path := range entries { resourceType := strings.TrimSuffix(filepath.Base(path), ".yml") - // A library for a type the engine does not support is a leftover, and silently - // ignoring it would let a rename go unnoticed. _, supported := dresources.SupportedResources[resourceType] require.True(t, supported, "%s names no supported resource type", path) - has[resourceType] = true - driven = append(driven, resourceType) - } - - for resourceType := range dresources.SupportedResources { - if !has[resourceType] && !strings.Contains(resourceType, ".") { - undriven = append(undriven, resourceType) - } } slices.Sort(driven) - slices.Sort(undriven) - return driven, undriven + return driven } -// soleResourceNode returns the single resource node of an initialized config. -func soleResourceNode(root *bundleconfig.Root) (string, error) { +// resourceNode returns the node of the resource under test, which a fixture declares under +// resourceKey. A fixture may declare dependencies of other types alongside it, so this is not +// simply the only node in the config. +func resourceNode(root *bundleconfig.Root, resourceType string) (string, error) { + want := "resources." + resourceType + "." + resourceKey nodes, err := initializedNodes(root) if err != nil { return "", err } - if len(nodes) != 1 { - return "", fmt.Errorf("expected exactly one resource node, got %v", nodes) + if !slices.Contains(nodes, want) { + return "", fmt.Errorf("%s is not among the declared resources %v", want, nodes) } - return nodes[0], nil + return want, nil } func initializedNodes(root *bundleconfig.Root) ([]string, error) { diff --git a/bundle/direct/autotest/harness_test.go b/bundle/direct/autotest/harness_test.go index 565520255ce..b7384daa736 100644 --- a/bundle/direct/autotest/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -164,14 +164,14 @@ func maxWaitSeconds() string { // built is usually the environment (an expired token, a workspace that rejects the // config), and the run is more useful if that lands in the report as one bad verdict // than if it aborts thousands of pending observations. -func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, resourceType, uniqueName string, base any) (*bundleHarness, error) { +func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, resourceType, uniqueName string, base any, deps map[string]any, variables map[string]string) (*bundleHarness, error) { dir := t.TempDir() if err := copyDir(dataDir, dir); err != nil { return nil, err } rememberWorkspaceUser(user.UserName) - yml, err := renderBundle(resourceType, uniqueName, user.UserName, base) + yml, err := renderBundle(resourceType, uniqueName, user.UserName, base, deps, variables) if err != nil { return nil, err } @@ -187,6 +187,9 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC // in the cleanup case. Values (dbr, logdiag, cmdio, env) still propagate; the test // binary's own -timeout remains the backstop. ctx = dbr.MockRuntime(context.WithoutCancel(ctx), dbr.Environment{}) //exhaustruct:ignore + for name, value := range variables { + ctx = env.Set(ctx, "BUNDLE_VAR_"+name, value) + } // Thousands of deploys run through here, so cap how long any one of them waits for // a resource to become ready. Without a cap a resource that never reaches its // terminal state stalls the whole suite instead of showing up as one bad verdict. @@ -237,7 +240,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC return nil, err } - node, err := soleResourceNode(&b.Config) + node, err := resourceNode(&b.Config, resourceType) if err != nil { return nil, err } @@ -413,6 +416,12 @@ func (h *bundleHarness) resource() (any, error) { return structaccess.GetByString(&h.bundle.Config, h.node) } +// depKey is the bundle key a dependency is declared under: its own resource type, singularised +// only in the sense that it is unique per type, so base references read ${resources.....}. +func depKey(depType string) string { + return depType +} + // resourceKey is the name every fixture's resource is declared under. One resource per bundle, // so the name carries nothing and only has to be stable. const resourceKey = "foo" @@ -423,27 +432,56 @@ const resourceKey = "foo" // // $UNIQUE_NAME survives loadFieldValues unexpanded and is expanded here, because it belongs to // one deploy: a rebuild gets a new name, while a value library is read once for the whole run. -func renderBundle(resourceType, uniqueName, userName string, base any) (string, error) { +func renderBundle(resourceType, uniqueName, userName string, base any, deps map[string]any, variables map[string]string) (string, error) { // Marshalled as one document rather than spliced together as indented text: re-indenting // someone else's YAML has to reason about block scalars, where a blank line is content. - document, err := yaml.Marshal(map[string]any{ + resources := map[string]any{resourceType: map[string]any{resourceKey: base}} + // A dependency is declared under its own type with its own key, so base can reference it as + // ${resources...} the way a bundle normally would. The key is the type's + // own name, which keeps a reference readable and cannot collide with the resource under test. + for depType, body := range deps { + if depType == resourceType { + return "", fmt.Errorf("dep %s is the resource type under test", depType) + } + resources[depType] = map[string]any{depKey(depType): body} + } + + document := map[string]any{ "bundle": map[string]any{"name": "test-bundle-$UNIQUE_NAME"}, - "resources": map[string]any{resourceType: map[string]any{resourceKey: base}}, - }) + "resources": resources, + } + // Declared without a value: the value comes from the environment below, since a default here + // would be resolved before the config-format validations run. + if len(variables) > 0 { + declared := map[string]any{} + for name := range variables { + declared[name] = map[string]any{"description": "set by the field catalog"} + } + document["variables"] = declared + } + + body, err := yaml.Marshal(document) if err != nil { return "", err } vars := templateVars(uniqueName, userName) var missing string - yml := os.Expand(string(document), func(key string) string { - value, ok := vars[key] - if !ok { - // Expanding an unknown variable to "" turns a required field into null, and the - // failure then surfaces as a confusing validation error much later. - missing = key + yml := os.Expand(string(body), func(key string) string { + if value, ok := vars[key]; ok { + return value } - return value + // A bundle's own interpolation shares this syntax -- ${var.secret_value}, + // ${resources.postgres_projects.x.name} -- and belongs to the config, not to the suite. + // Told apart by the dot: every variable the suite provides is a bare upper-case name, and + // every bundle reference is dotted. Restored verbatim so the config still carries it. + if strings.Contains(key, ".") { + return "${" + key + "}" + } + // Expanding an unknown variable to "" turns a required field into null, and the failure + // then surfaces as a confusing validation error much later. + missing = key + return "" }) if missing != "" { return "", fmt.Errorf("%s.yml uses $%s, which this suite does not provide", resourceType, missing) diff --git a/bundle/direct/autotest/output/configs.txt b/bundle/direct/autotest/output/configs.txt deleted file mode 100644 index 35129310071..00000000000 --- a/bundle/direct/autotest/output/configs.txt +++ /dev/null @@ -1,37 +0,0 @@ -=== covered -alerts -apps -catalogs -cluster_policies -clusters -dashboards -database_instances -experiments -external_locations -genie_spaces -instance_pools -jobs -model_serving_endpoints -models -pipelines -postgres_projects -postgres_synced_tables -registered_models -schemas -secret_scopes -sql_warehouses -volumes - -=== not covered: no value library in testdata/fields -database_catalogs -job_runs -postgres_branches -postgres_catalogs -postgres_databases -postgres_endpoints -postgres_roles -quality_monitors -secrets -synced_database_tables -vector_search_endpoints -vector_search_indexes diff --git a/bundle/direct/autotest/output/database_catalogs.txt b/bundle/direct/autotest/output/database_catalogs.txt new file mode 100644 index 00000000000..02442d614ce --- /dev/null +++ b/bundle/direct/autotest/output/database_catalogs.txt @@ -0,0 +1,9 @@ +database_instance_name x y BASE_ERROR cannot recreate resources.database_catalogs.foo: database instance with name 'x' not found (404 UNKNOWN) +database_instance_name y x BASE_ERROR cannot recreate resources.database_catalogs.foo: database instance with name 'y' not found (404 UNKNOWN) + +=== summary +OK_RECREATE 8 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +SKIPPED 2 +BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/job_runs.txt b/bundle/direct/autotest/output/job_runs.txt new file mode 100644 index 00000000000..b0d1dbf87a4 --- /dev/null +++ b/bundle/direct/autotest/output/job_runs.txt @@ -0,0 +1,6 @@ + +=== summary +OK_RECREATE 109 +SUPPRESSED 9 +NOT_OBSERVABLE 12 +SKIPPED 1 diff --git a/bundle/direct/autotest/output/postgres_branches.txt b/bundle/direct/autotest/output/postgres_branches.txt new file mode 100644 index 00000000000..d1cd81e2c79 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_branches.txt @@ -0,0 +1,19 @@ +is_protected true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +no_expiry absent false BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) +no_expiry absent true BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) +no_expiry false absent BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) +no_expiry false true BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) +no_expiry true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) +no_expiry true false BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) +parent x y BASE_ERROR cannot recreate resources.postgres_branches.foo: No stub found for pattern: POST /api/2.0/postgres/x/branches (501 ) +parent y x BASE_ERROR cannot recreate resources.postgres_branches.foo: No stub found for pattern: POST /api/2.0/postgres/y/branches (501 ) + +=== summary +OK 7 +OK_RECREATE 14 +OK_INERT 6 +SUPPRESSED 3 +NOT_OBSERVABLE 1 +BACKEND_ERROR 3 +SKIPPED 1 +BASE_ERROR 6 diff --git a/bundle/direct/autotest/output/postgres_catalogs.txt b/bundle/direct/autotest/output/postgres_catalogs.txt new file mode 100644 index 00000000000..1bd2a026bd8 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_catalogs.txt @@ -0,0 +1,6 @@ + +=== summary +OK_RECREATE 14 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +SKIPPED 1 diff --git a/bundle/direct/autotest/output/postgres_databases.txt b/bundle/direct/autotest/output/postgres_databases.txt new file mode 100644 index 00000000000..f8112aa8e12 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_databases.txt @@ -0,0 +1,9 @@ +parent x y BASE_ERROR cannot recreate resources.postgres_databases.foo: No stub found for pattern: POST /api/2.0/postgres/x/databases (501 ) +parent y x BASE_ERROR cannot recreate resources.postgres_databases.foo: No stub found for pattern: POST /api/2.0/postgres/y/databases (501 ) + +=== summary +OK 8 +OK_RECREATE 2 +OK_INERT 6 +SKIPPED 1 +BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/postgres_endpoints.txt b/bundle/direct/autotest/output/postgres_endpoints.txt new file mode 100644 index 00000000000..30d4db10720 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_endpoints.txt @@ -0,0 +1,31 @@ +autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +disabled true absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries absent false BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries absent true BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries false absent BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries false true BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries true absent BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries true false BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.max 1 2 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.max 2 1 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.min 1 2 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.min 2 1 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +no_suspension absent true BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ec.no_suspension' (400 INVALID_PARAMETER_VALUE) +no_suspension false true BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ec.no_suspension' (400 INVALID_PARAMETER_VALUE) +no_suspension true absent BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ec.no_suspension' (400 INVALID_PARAMETER_VALUE) +no_suspension true false BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ec.no_suspension' (400 INVALID_PARAMETER_VALUE) +parent x y BASE_ERROR cannot recreate resources.postgres_endpoints.foo: No stub found for pattern: POST /api/2.0/postgres/x/endpoints (501 ) +parent y x BASE_ERROR cannot recreate resources.postgres_endpoints.foo: No stub found for pattern: POST /api/2.0/postgres/y/endpoints (501 ) + +=== summary +OK 11 +OK_RECREATE 4 +OK_INERT 6 +SUPPRESSED 2 +NOT_OBSERVABLE 2 +BACKEND_ERROR 9 +SKIPPED 1 +BASE_ERROR 12 diff --git a/bundle/direct/autotest/output/postgres_roles.txt b/bundle/direct/autotest/output/postgres_roles.txt new file mode 100644 index 00000000000..66dedd17dd9 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_roles.txt @@ -0,0 +1,11 @@ +parent x y BASE_ERROR cannot recreate resources.postgres_roles.foo: No stub found for pattern: POST /api/2.0/postgres/x/roles (501 ) +parent y x BASE_ERROR cannot recreate resources.postgres_roles.foo: No stub found for pattern: POST /api/2.0/postgres/y/roles (501 ) + +=== summary +OK 22 +OK_RECREATE 20 +OK_INERT 6 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +SKIPPED 1 +BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/quality_monitors.txt b/bundle/direct/autotest/output/quality_monitors.txt new file mode 100644 index 00000000000..7683b7d5785 --- /dev/null +++ b/bundle/direct/autotest/output/quality_monitors.txt @@ -0,0 +1,12 @@ +latest_monitor_failure_msg absent x POST_DEPLOY_DRIFT latest_monitor_failure_msg +latest_monitor_failure_msg absent y POST_DEPLOY_DRIFT latest_monitor_failure_msg +latest_monitor_failure_msg x y POST_DEPLOY_DRIFT latest_monitor_failure_msg +latest_monitor_failure_msg y x POST_DEPLOY_DRIFT latest_monitor_failure_msg + +=== summary +OK 100 +OK_RECREATE 4 +SUPPRESSED 6 +NOT_OBSERVABLE 4 +POST_DEPLOY_DRIFT 4 +SKIPPED 1 diff --git a/bundle/direct/autotest/output/secrets.txt b/bundle/direct/autotest/output/secrets.txt new file mode 100644 index 00000000000..909c5ede7ad --- /dev/null +++ b/bundle/direct/autotest/output/secrets.txt @@ -0,0 +1,17 @@ +comment x absent UPDATE_IGNORED +comment y absent UPDATE_IGNORED +owner absent x SUPPRESSED spec:input_only +owner absent y SUPPRESSED spec:input_only +owner x absent SUPPRESSED spec:input_only +owner x y SUPPRESSED spec:input_only +owner y absent SUPPRESSED spec:input_only +owner y x SUPPRESSED spec:input_only +value BASELINE_DRIFT drifts with no config change + +=== summary +OK 4 +OK_RECREATE 6 +SUPPRESSED 6 +BASELINE_DRIFT 1 +UPDATE_IGNORED 2 +SKIPPED 8 diff --git a/bundle/direct/autotest/output/synced_database_tables.txt b/bundle/direct/autotest/output/synced_database_tables.txt new file mode 100644 index 00000000000..cc1a2da8947 --- /dev/null +++ b/bundle/direct/autotest/output/synced_database_tables.txt @@ -0,0 +1,14 @@ +database_instance_name absent x DEPLOY_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) +database_instance_name absent y DEPLOY_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) +database_instance_name x absent BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) +database_instance_name x y BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) +database_instance_name y absent BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) +database_instance_name y x BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) + +=== summary +OK_RECREATE 68 +SUPPRESSED 4 +NOT_OBSERVABLE 2 +DEPLOY_ERROR 2 +SKIPPED 35 +BASE_ERROR 4 diff --git a/bundle/direct/autotest/output/vector_search_endpoints.txt b/bundle/direct/autotest/output/vector_search_endpoints.txt new file mode 100644 index 00000000000..55398a08452 --- /dev/null +++ b/bundle/direct/autotest/output/vector_search_endpoints.txt @@ -0,0 +1,5 @@ + +=== summary +OK 18 +OK_RECREATE 4 +SKIPPED 1 diff --git a/bundle/direct/autotest/output/vector_search_indexes.txt b/bundle/direct/autotest/output/vector_search_indexes.txt new file mode 100644 index 00000000000..4867308ae1c --- /dev/null +++ b/bundle/direct/autotest/output/vector_search_indexes.txt @@ -0,0 +1,11 @@ +endpoint_name x y BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint x not found (404 RESOURCE_DOES_NOT_EXIST) +endpoint_name y x BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint y not found (404 RESOURCE_DOES_NOT_EXIST) +name x-UNIQUE y-UNIQUE BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Invalid index name. Must specify the ...ores are allowed. (400 INVALID_PARAMETER_VALUE) +name y-UNIQUE x-UNIQUE BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Invalid index name. Must specify the ...ores are allowed. (400 INVALID_PARAMETER_VALUE) + +=== summary +OK_RECREATE 69 +SUPPRESSED 4 +NOT_OBSERVABLE 3 +SKIPPED 1 +BASE_ERROR 4 diff --git a/bundle/direct/autotest/report_test.go b/bundle/direct/autotest/report_test.go index 1b7b008b7a4..d3bd64a41e8 100644 --- a/bundle/direct/autotest/report_test.go +++ b/bundle/direct/autotest/report_test.go @@ -3,9 +3,7 @@ package autotest import ( "cmp" "encoding/json" - "errors" "fmt" - "io/fs" "os" "path/filepath" "slices" @@ -509,37 +507,6 @@ func writeReport(t testutil.TestingT, name, body string) { } } -// writeCoverageReport records which resource types the suite drives and which supported types -// it does not, so the catalog accounts for everything the direct engine knows rather than only -// what it happens to cover. -func writeCoverageReport(t testutil.TestingT, driven, undriven []string) { - var sb strings.Builder - sb.WriteString("=== covered\n") - for _, resourceType := range driven { - fmt.Fprintf(&sb, "%s\n", resourceType) - } - sb.WriteString("\n=== not covered: no value library in testdata/fields\n") - for _, resourceType := range undriven { - fmt.Fprintf(&sb, "%s\n", resourceType) - } - - name := reportPath("configs.txt") - body := sb.String() - if isCloud() { - return - } - if testdiff.OverwriteMode { - writeReport(t, name, body) - return - } - expected, err := os.ReadFile(name) - if err != nil && !errors.Is(err, fs.ErrNotExist) { - t.Errorf("reading %s: %s", name, err) - return - } - testdiff.AssertEqualTexts(t, name, name, testdiff.NormalizeNewlines(string(expected)), body) -} - // outputDir holds the committed reports. Kept out of the package directory so the Go // files stay readable next to two dozen goldens. const outputDir = "output" diff --git a/bundle/direct/autotest/testdata/fields/database_catalogs.yml b/bundle/direct/autotest/testdata/fields/database_catalogs.yml new file mode 100644 index 00000000000..96d8af042f6 --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/database_catalogs.yml @@ -0,0 +1,15 @@ +# Value library for database catalogs. See ../../README.md. + +# Lakebase v1 is not available in every cloud, same as the instance this catalog needs. +local_only: Lakebase v1 is not available in every cloud + +deps: + database_instances: + name: test-db-instance-$UNIQUE_NAME + capacity: CU_1 + +base: + database_instance_name: ${resources.database_instances.database_instances.name} + database_name: test_db + name: test-catalog-$UNIQUE_NAME + create_database_if_not_exists: true diff --git a/bundle/direct/autotest/testdata/fields/job_runs.yml b/bundle/direct/autotest/testdata/fields/job_runs.yml new file mode 100644 index 00000000000..32214831823 --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/job_runs.yml @@ -0,0 +1,22 @@ +# Value library for job runs. See ../../README.md. + +# Deploying a job run actually runs the job, and the wait needs a task a workspace will execute -- +# serverless keeps that to about a minute. The catalog moves a field hundreds of times, so this type +# is driven against the fake server only; a real workspace would spend a run per transition. +local_only: every transition would start a real job run + +deps: + jobs: + name: test-job-$UNIQUE_NAME + tasks: + - task_key: only_task + spark_python_task: + python_file: ./job_run.py + environment_key: default + environments: + - environment_key: default + spec: + environment_version: "2" + +base: + job_id: ${resources.jobs.jobs.id} diff --git a/bundle/direct/autotest/testdata/fields/postgres_branches.yml b/bundle/direct/autotest/testdata/fields/postgres_branches.yml new file mode 100644 index 00000000000..20d45e6676a --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/postgres_branches.yml @@ -0,0 +1,15 @@ +# Value library for postgres branches. See ../../README.md. + +# The parent project holds its name after deletion until the soft delete is purged, so the chain +# cannot be rebuilt on a real workspace. See postgres_projects.yml. +local_only: a recreate cannot reuse the project name until the soft delete is purged + +deps: + postgres_projects: + project_id: test-pg-project-$UNIQUE_NAME + display_name: Test Postgres Project + +base: + parent: ${resources.postgres_projects.postgres_projects.name} + branch_id: test-branch-$UNIQUE_NAME + no_expiry: true diff --git a/bundle/direct/autotest/testdata/fields/postgres_catalogs.yml b/bundle/direct/autotest/testdata/fields/postgres_catalogs.yml new file mode 100644 index 00000000000..bbb8fcc8fb4 --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/postgres_catalogs.yml @@ -0,0 +1,14 @@ +# Value library for postgres catalogs. See ../../README.md. + +local_only: a recreate cannot reuse the project name until the soft delete is purged + +deps: + postgres_projects: + project_id: test-pg-project-$UNIQUE_NAME + display_name: Test Postgres Project + +base: + catalog_id: test_pg_catalog_$UNIQUE_NAME + branch: ${resources.postgres_projects.postgres_projects.name}/branches/production + postgres_database: appdb + create_database_if_missing: true diff --git a/bundle/direct/autotest/testdata/fields/postgres_databases.yml b/bundle/direct/autotest/testdata/fields/postgres_databases.yml new file mode 100644 index 00000000000..ebc2576d0ce --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/postgres_databases.yml @@ -0,0 +1,22 @@ +# Value library for postgres databases. See ../../README.md. + +local_only: a recreate cannot reuse the project name until the soft delete is purged + +deps: + postgres_projects: + project_id: test-pg-project-$UNIQUE_NAME + display_name: Test Postgres Project + postgres_branches: + parent: ${resources.postgres_projects.postgres_projects.name} + branch_id: test-branch-$UNIQUE_NAME + no_expiry: true + postgres_roles: + parent: ${resources.postgres_branches.postgres_branches.name} + role_id: test-role-$UNIQUE_NAME + postgres_role: app_role + +base: + parent: ${resources.postgres_branches.postgres_branches.name} + database_id: test-database-$UNIQUE_NAME + postgres_database: app_db + role: ${resources.postgres_roles.postgres_roles.name} diff --git a/bundle/direct/autotest/testdata/fields/postgres_endpoints.yml b/bundle/direct/autotest/testdata/fields/postgres_endpoints.yml new file mode 100644 index 00000000000..26392bccaa8 --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/postgres_endpoints.yml @@ -0,0 +1,18 @@ +# Value library for postgres endpoints. See ../../README.md. + +local_only: a recreate cannot reuse the project name until the soft delete is purged + +deps: + postgres_projects: + project_id: test-pg-project-$UNIQUE_NAME + display_name: Test Postgres Project + postgres_branches: + parent: ${resources.postgres_projects.postgres_projects.name} + branch_id: test-branch-$UNIQUE_NAME + no_expiry: true + +base: + parent: ${resources.postgres_branches.postgres_branches.name} + endpoint_id: test-endpoint-$UNIQUE_NAME + endpoint_type: ENDPOINT_TYPE_READ_WRITE + suspend_timeout_duration: 300s diff --git a/bundle/direct/autotest/testdata/fields/postgres_roles.yml b/bundle/direct/autotest/testdata/fields/postgres_roles.yml new file mode 100644 index 00000000000..5e9460447fc --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/postgres_roles.yml @@ -0,0 +1,19 @@ +# Value library for postgres roles. See ../../README.md. + +# A recreate reuses the project name, and a deleted project holds it until the soft delete is +# purged, so the parent chain cannot be rebuilt on a real workspace. See postgres_projects.yml. +local_only: a recreate cannot reuse the project name until the soft delete is purged + +deps: + postgres_projects: + project_id: test-pg-project-$UNIQUE_NAME + display_name: Test Postgres Project + postgres_branches: + parent: ${resources.postgres_projects.postgres_projects.name} + branch_id: test-branch-$UNIQUE_NAME + no_expiry: true + +base: + parent: ${resources.postgres_branches.postgres_branches.name} + role_id: test-role-$UNIQUE_NAME + postgres_role: app_role diff --git a/bundle/direct/autotest/testdata/fields/quality_monitors.yml b/bundle/direct/autotest/testdata/fields/quality_monitors.yml new file mode 100644 index 00000000000..720521c56da --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/quality_monitors.yml @@ -0,0 +1,16 @@ +# Value library for quality monitors. See ../../README.md. + +# A monitor attaches to a table that already holds data, and its own name is that table's -- the +# suite provisions no table, so a real workspace has nothing to monitor. +local_only: needs an existing table with data to monitor + +base: + table_name: main.default.test_monitor_$UNIQUE_NAME + assets_dir: /Shared/databricks_monitoring/main.default.test_monitor_$UNIQUE_NAME + output_schema_name: main.default + inference_log: + granularities: ["1 day"] + timestamp_col: timestamp + prediction_col: prediction + model_id_col: model_id + problem_type: PROBLEM_TYPE_REGRESSION diff --git a/bundle/direct/autotest/testdata/fields/secrets.yml b/bundle/direct/autotest/testdata/fields/secrets.yml new file mode 100644 index 00000000000..42aec0796d9 --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/secrets.yml @@ -0,0 +1,16 @@ +# Value library for secrets. See ../../README.md. + +variables: + secret_value: seeded-value + +base: + catalog_name: main + schema_name: default + name: test-secret-$UNIQUE_NAME + value: ${var.secret_value} + +skip: + # The config format refuses a literal here, to keep secrets out of config files: "Secret value + # must be a variable reference". No value this suite could move the field to is a legal bundle, + # so the only reachable shape is the reference base declares. + value: the config format requires a variable reference, which no literal value is diff --git a/bundle/direct/autotest/testdata/fields/synced_database_tables.yml b/bundle/direct/autotest/testdata/fields/synced_database_tables.yml new file mode 100644 index 00000000000..8489565c6bb --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/synced_database_tables.yml @@ -0,0 +1,24 @@ +# Value library for synced database tables. See ../../README.md. + +# Lakebase v1 is not available in every cloud, and the source table below is not provisioned. +local_only: Lakebase v1 is not available in every cloud + +deps: + database_instances: + name: test-db-instance-$UNIQUE_NAME + capacity: CU_1 + database_catalogs: + database_instance_name: ${resources.database_instances.database_instances.name} + database_name: test_db + name: test-catalog-$UNIQUE_NAME + create_database_if_not_exists: true + +base: + name: ${resources.database_catalogs.database_catalogs.name}.${resources.database_catalogs.database_catalogs.database_name}.test_table + database_instance_name: ${resources.database_instances.database_instances.name} + logical_database_name: ${resources.database_catalogs.database_catalogs.database_name} + spec: + source_table_full_name: main.test_synced_$UNIQUE_NAME.trips_source + scheduling_policy: SNAPSHOT + primary_key_columns: + - tpep_pickup_datetime diff --git a/bundle/direct/autotest/testdata/fields/vector_search_endpoints.yml b/bundle/direct/autotest/testdata/fields/vector_search_endpoints.yml new file mode 100644 index 00000000000..9e1e8a1a258 --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/vector_search_endpoints.yml @@ -0,0 +1,6 @@ +# Value library for vector search endpoints. See ../../README.md. + +base: + # Endpoint names must be under 50 characters. + name: test-vse-$UNIQUE_NAME + endpoint_type: STANDARD diff --git a/bundle/direct/autotest/testdata/fields/vector_search_indexes.yml b/bundle/direct/autotest/testdata/fields/vector_search_indexes.yml new file mode 100644 index 00000000000..c1e3572372a --- /dev/null +++ b/bundle/direct/autotest/testdata/fields/vector_search_indexes.yml @@ -0,0 +1,17 @@ +# Value library for vector search indexes. See ../../README.md. + +deps: + vector_search_endpoints: + name: test-vse-$UNIQUE_NAME + endpoint_type: STANDARD + +base: + name: main.default.test_index_$UNIQUE_NAME + endpoint_name: ${resources.vector_search_endpoints.vector_search_endpoints.name} + primary_key: id + index_type: DIRECT_ACCESS + direct_access_index_spec: + schema_json: '{"id":"integer","vector":"array"}' + embedding_vector_columns: + - name: vector + embedding_dimension: 768 diff --git a/bundle/direct/autotest/values_test.go b/bundle/direct/autotest/values_test.go index 9bda30d8c2d..8d0ba2fb210 100644 --- a/bundle/direct/autotest/values_test.go +++ b/bundle/direct/autotest/values_test.go @@ -60,6 +60,19 @@ type fieldValues struct { // cloud run neither confirms nor contradicts it. localOnly string + // variables are bundle variables the fixture's own values reference, with the value to give + // each. A secret's value has to be a ${var...} reference -- the config format refuses a + // literal, to keep secrets out of config files -- so the fixture needs somewhere to declare + // one. The value is passed the way a user would, through BUNDLE_VAR_: a default in the + // config would be resolved before that validation runs, and then fail it. + variables map[string]string + + // deps are resources the one under test needs in order to exist, keyed by resource type: a + // postgres branch needs a project, a secret needs nothing but a catalog and schema that + // already exist. They are deployed in the same bundle and referenced from base with + // ${resources...}, and no field of one is ever tested -- only the resource under test is. + deps map[string]any + // clouds names the clouds whose workspaces can host this resource type, for a service that // exists on some and not others. Empty means every cloud. Unlike localOnly this does not // give up on the cloud run: the type is verified where the service exists and skipped @@ -174,7 +187,7 @@ var cliManagedFields = map[string]string{ // corpus configs use so a value can name the workspace's own user rather than a placeholder // only the fake server knows. func loadFieldValues(resourceType string, vars map[string]string) (*fieldValues, error) { - fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, base: nil, localOnly: "", clouds: nil} + fv := &fieldValues{skip: map[string]string{}, fields: map[string][]any{}, base: nil, localOnly: "", clouds: nil, deps: nil, variables: nil} maps.Copy(fv.skip, cliManagedFields) path := filepath.Join(fieldsDir, resourceType+".yml") @@ -194,11 +207,17 @@ func loadFieldValues(resourceType string, vars map[string]string) (*fieldValues, if key == uniqueNameVar { return "$" + uniqueNameVar } - value, ok := vars[key] - if !ok { - missing = key + if value, ok := vars[key]; ok { + return value + } + // A bundle's own interpolation shares this syntax and belongs to the config: base declares + // ${var.secret_value} and a dep reference like ${resources.postgres_projects.x.name}. Told + // apart by the dot, as in renderBundle, which is where they are finally emitted. + if strings.Contains(key, ".") { + return "${" + key + "}" } - return value + missing = key + return "" }) if missing != "" { return nil, fmt.Errorf("%s uses $%s, which this suite does not provide here", path, missing) @@ -209,6 +228,8 @@ func loadFieldValues(resourceType string, vars map[string]string) (*fieldValues, Fields map[string][]any `yaml:"fields"` Base any `yaml:"base"` LocalOnly string `yaml:"local_only"` + Deps map[string]any `yaml:"deps"` + Variables map[string]string `yaml:"variables"` Clouds []string `yaml:"clouds"` } if err := yaml.Unmarshal([]byte(expanded), &file); err != nil { @@ -218,6 +239,8 @@ func loadFieldValues(resourceType string, vars map[string]string) (*fieldValues, maps.Copy(fv.fields, file.Fields) fv.base = file.Base fv.localOnly = file.LocalOnly + fv.deps = file.Deps + fv.variables = file.Variables fv.clouds = file.Clouds return fv, nil From cd94d03b2d35afefaf236efbf95e62cbe51a0c7c Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 12:51:25 +0200 Subject: [PATCH 096/118] Field catalog: give an unwieldy value a short alias A subtest named after a truncated email address with a digest on the end is unreadable, and no easier to retype than absent_to_v1. A value whose own label is not already a clean short token now gets an alias -- v1, v2 -- numbered by sorted label so it does not move when the run order does, and the legend at the end of the full report says which value each one is. The report rows use the same alias, so a row and its subtest always agree. Indexed paths lose their brackets in the subtest name: tasks_0, not tasks[0]. Brackets are a character class to the test filter regex, so a filter copied out of the test output quietly meant something else -- the old name matched with the index read as a one-character class. Co-authored-by: Isaac --- bundle/direct/autotest/README.md | 5 ++ bundle/direct/autotest/fields_test.go | 48 ++++++++++++++----- bundle/direct/autotest/harness_test.go | 2 +- .../autotest/output/cluster_policies.txt | 8 ++-- bundle/direct/autotest/values_test.go | 48 ++++++++++++++++++- 5 files changed, 91 insertions(+), 20 deletions(-) diff --git a/bundle/direct/autotest/README.md b/bundle/direct/autotest/README.md index 8451a1e1dec..59495a5a529 100644 --- a/bundle/direct/autotest/README.md +++ b/bundle/direct/autotest/README.md @@ -202,6 +202,11 @@ longer deploys at all. The summary counts every field and has no meaningful subs not compared -- a change between two *passing* verdicts shows up only in the full run. `-sample` refuses to run with `-update`, which would truncate the goldens to the sample. +A value whose own label would be unreadable gets a short alias -- `v1`, `v2` -- which the legend at +the end of the full report maps back, so a subtest is `absent_to_v1` rather than a truncated email +address with a digest on the end. An index is written `tasks_0`, not `tasks[0]`: brackets are a +character class to the `-run` regex, so a filter copied from the output would mean something else. + One transition is addressable on its own, and the subtest names carry no shell metacharacters so no quoting is needed. `-v` prints the post-deploy plan behind any problem verdict: diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index 1d441639e41..441966e6e79 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -141,13 +141,13 @@ func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClie rep.addCoverage(fields, uncovered) fields = sample(fields, sampleSeedValue, rep) - // A container's label says only how many entries it has, so record what each one stands for. + // A label that does not say what the value is needs the legend to: a container's says only how + // many entries it has, and an alias says nothing at all. for _, f := range fields { - if !isContainer(f.kind) { - continue - } for _, value := range f.values { - rep.addLegend(f.path, valueLabel(value), value) + if label := f.label(value); isContainer(f.kind) || label != valueLabel(value) { + rep.addLegend(f.path, label, value) + } } } @@ -192,7 +192,7 @@ func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClie // every later field, so they are recorded as such instead of run. var broken error - t.Run(f.path, func(t *testing.T) { + t.Run(subtestName(f.path), func(t *testing.T) { // What the field is currently deployed as, when that is known. Transitions are // generated in runs that share a starting value, so this skips about half of // the setup deploys -- the single biggest cost in the suite. @@ -201,7 +201,7 @@ func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClie for _, tr := range f.transitions() { t.Run(tr.label(), func(t *testing.T) { if broken != nil { - rep.add(result{f.path, valueLabel(tr.from), valueLabel(tr.to), verdictBaseError, oneLine(broken.Error()), broken.Error()}) + rep.add(result{f.path, tr.fromLabel, tr.toLabel, verdictBaseError, oneLine(broken.Error()), broken.Error()}) return } @@ -489,7 +489,7 @@ type declarations struct { func runTransition(t *testing.T, h *bundleHarness, path string, tr transition, startsDeployed bool, baseline map[string]bool, decl declarations) result { from, to := tr.from, tr.to - res := result{field: path, from: valueLabel(tr.from), to: valueLabel(tr.to)} //exhaustruct:ignore + res := result{field: path, from: tr.fromLabel, to: tr.toLabel} //exhaustruct:ignore // Reach the starting value. if err := h.setField(path, from); err != nil { @@ -730,6 +730,16 @@ func valueLabel(v any) string { } } +// subtestName turns a field path into a subtest name. An index is written "tasks_0", not +// "tasks[0]": brackets are a character class to the -run regex, so a filter copied from the test +// output would quietly mean something else. +func subtestName(path string) string { + return indexPattern.ReplaceAllString(path, "_$1") +} + +// indexPattern matches a slice index in a field path. +var indexPattern = regexp.MustCompile(`\[([0-9]+)\]`) + // labelSafe matches the characters a label keeps verbatim: everything else would need // quoting to pass a label back to `go test -run`. var labelSafe = regexp.MustCompile(`[^A-Za-z0-9._@/:=-]+`) @@ -739,16 +749,28 @@ var labelSafe = regexp.MustCompile(`[^A-Za-z0-9._@/:=-]+`) // cluster policy definition is a whole JSON document -- is trimmed and given a short digest, // so two values that share a prefix still get different labels. func shortLabel(s string) string { - const maxLabel = 24 - - clean := labelSafe.ReplaceAllString(s, "_") - if clean == s && len(clean) <= maxLabel { + if clean := cleanLabel(s); clean != "" { return clean } + // No alias was assigned for this value -- it is not one of a field's declared values, so + // nothing holds a legend entry for it. A digest keeps the label short and unique. digest := fnv.New32a() _, _ = digest.Write([]byte(s)) - return strings.Trim(clean[:min(len(clean), maxLabel-5)], "_") + "~" + strconv.FormatUint(uint64(digest.Sum32())%0x10000, 16) + safe := labelSafe.ReplaceAllString(s, "_") + return strings.Trim(safe[:min(len(safe), maxLabelLength-5)], "_") + "~" + strconv.FormatUint(uint64(digest.Sum32())%0x10000, 16) +} + +// maxLabelLength is how long a value's own label may be before it is aliased instead. +const maxLabelLength = 24 + +// cleanLabel returns the value unchanged when it reads as a label already -- short, and free of +// characters that would need quoting in a test filter -- and "" when it does not. +func cleanLabel(s string) string { + if len(s) <= maxLabelLength && labelSafe.ReplaceAllString(s, "_") == s { + return s + } + return "" } // allErrors returns every error diagnostic in full -- status, code, message, endpoint -- diff --git a/bundle/direct/autotest/harness_test.go b/bundle/direct/autotest/harness_test.go index b7384daa736..8949261e603 100644 --- a/bundle/direct/autotest/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -188,7 +188,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC // binary's own -timeout remains the backstop. ctx = dbr.MockRuntime(context.WithoutCancel(ctx), dbr.Environment{}) //exhaustruct:ignore for name, value := range variables { - ctx = env.Set(ctx, "BUNDLE_VAR_"+name, value) + ctx := env.Set(ctx, "BUNDLE_VAR_"+name, value) } // Thousands of deploys run through here, so cap how long any one of them waits for // a resource to become ready. Without a cap a resource that never reaches its diff --git a/bundle/direct/autotest/output/cluster_policies.txt b/bundle/direct/autotest/output/cluster_policies.txt index fe9d5760087..3436746c9ca 100644 --- a/bundle/direct/autotest/output/cluster_policies.txt +++ b/bundle/direct/autotest/output/cluster_policies.txt @@ -1,7 +1,7 @@ -definition absent spark_version_:_ty~69b3 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -definition absent spark_version_:_ty~cbc0 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -definition spark_version_:_ty~69b3 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -definition spark_version_:_ty~cbc0 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition absent v1 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition absent v2 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition v1 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition v2 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) libraries[0].jar absent dbfs:/FileStore/a.jar BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) libraries[0].jar absent dbfs:/FileStore/b.jar BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) libraries[0].jar dbfs:/FileStore/a.jar absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/values_test.go b/bundle/direct/autotest/values_test.go index 8d0ba2fb210..d9f0055c3b8 100644 --- a/bundle/direct/autotest/values_test.go +++ b/bundle/direct/autotest/values_test.go @@ -371,6 +371,10 @@ type field struct { kind reflect.Kind values []any + // aliases maps a value's own label to the short alias the report shows instead, for values + // whose label would otherwise be an unreadable truncation. Only populated for those. + aliases map[string]string + // required fields do not get an "absent" transition: a config missing one is // rejected by bundle validate, so removing it is not something a user can deploy. required bool @@ -379,6 +383,9 @@ type field struct { // transition is one move of a field from one value to another. type transition struct { from, to any + // The labels the report and the subtest name use, which for an unwieldy value is a short + // alias the legend explains rather than the value itself. + fromLabel, toLabel string } // label names the transition for the subtest. Kept free of shell metacharacters so a @@ -386,7 +393,39 @@ type transition struct { // // go test ./bundle/direct/autotest -run TestFields/pipelines/.*/dry_run/absent_to_true -v func (t transition) label() string { - return valueLabel(t.from) + "_to_" + valueLabel(t.to) + return t.fromLabel + "_to_" + t.toLabel +} + +// label names one of the field's values for the report and the subtest. An unwieldy value gets a +// short alias -- v1, v2 -- which the legend maps back, because the alternative is a subtest called +// "absent_to_alerts@databricks.i~4539": unreadable, and no easier to retype than v1. +func (f field) label(value any) string { + own := valueLabel(value) + if alias, ok := f.aliases[own]; ok { + return alias + } + return own +} + +// assignAliases gives every value whose own label is not a clean short token an alias. Numbered by +// sorted label rather than by position, so the alias for a value does not move when the run order +// does. +func assignAliases(values []any) map[string]string { + var unwieldy []string + for _, value := range values { + if own := valueLabel(value); own != cleanLabel(own) { + unwieldy = append(unwieldy, own) + } + } + if len(unwieldy) == 0 { + return nil + } + slices.Sort(unwieldy) + aliases := make(map[string]string, len(unwieldy)) + for i, own := range slices.Compact(unwieldy) { + aliases[own] = "v" + strconv.Itoa(i+1) + } + return aliases } // transitions returns every ordered pair of the field's values, ordered as a single walk: @@ -445,7 +484,11 @@ func (f field) transitions() []transition { out := make([]transition, 0, len(circuit)-1) for i := 1; i < len(circuit); i++ { - out = append(out, transition{from: values[circuit[i-1]], to: values[circuit[i]]}) + from, to := values[circuit[i-1]], values[circuit[i]] + out = append(out, transition{ + from: from, to: to, + fromLabel: f.label(from), toLabel: f.label(to), + }) } return out } @@ -547,6 +590,7 @@ func enumerateFields(resourceType string, inputType reflect.Type, fv *fieldValue path: path, kind: kind, values: values, + aliases: assignAliases(values), required: isRequired(resourceType, path), } // A field with nothing to walk is a gap, not something to drop on the floor: no From fa7429cc8519bc2b9e5c977ac69abd581b9d1faa Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 12:59:38 +0200 Subject: [PATCH 097/118] Field catalog: say which value could not be created, and actually pass the variables "cannot create the resource holding it" left the reader to work out what "it" was: the starting value the transition needed. The row's own from column names the value, so the detail just has to say that is what failed. Also fixes the variables loop from the previous commit, where the context was shadowed inside it, so BUNDLE_VAR_ never reached the deploy. The secrets fixture passed anyway -- the validation that needs the reference runs before resolution -- which is exactly how a silent one of these survives. Co-authored-by: Isaac --- bundle/direct/autotest/fields_test.go | 2 +- bundle/direct/autotest/harness_test.go | 2 ++ bundle/direct/autotest/output/dashboards.txt | 4 ++-- bundle/direct/autotest/output/sql_warehouses.txt | 12 ++++++------ 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index 441966e6e79..779ef5ea148 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -223,7 +223,7 @@ func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClie // answer for this transition: a dashboard has to have a display name, // so nothing starts from it being absent. Recorded, and a plain // rebuild follows so the remaining transitions have a resource. - res.detail = "cannot create the resource holding it: " + oneLine(err.Error()) + res.detail = "cannot create the resource with this starting value: " + oneLine(err.Error()) res.evidence = withContext("error creating the resource with the starting value:", err.Error()) rebuilt, err = rebuild(owner, ctx, client, user, resourceType, fv, h) } diff --git a/bundle/direct/autotest/harness_test.go b/bundle/direct/autotest/harness_test.go index 8949261e603..6df70bcfe5d 100644 --- a/bundle/direct/autotest/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -187,6 +187,8 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC // in the cleanup case. Values (dbr, logdiag, cmdio, env) still propagate; the test // binary's own -timeout remains the backstop. ctx = dbr.MockRuntime(context.WithoutCancel(ctx), dbr.Environment{}) //exhaustruct:ignore + // A fixture's variables are passed the way a user passes them, so the config keeps the + // ${var...} reference the format requires rather than a resolved literal. for name, value := range variables { ctx := env.Set(ctx, "BUNDLE_VAR_"+name, value) } diff --git a/bundle/direct/autotest/output/dashboards.txt b/bundle/direct/autotest/output/dashboards.txt index 76830d53ab4..c1d370a198e 100644 --- a/bundle/direct/autotest/output/dashboards.txt +++ b/bundle/direct/autotest/output/dashboards.txt @@ -1,5 +1,5 @@ -display_name absent x-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) -display_name absent y-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) +display_name absent x-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) +display_name absent y-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) display_name x-UNIQUE absent UPDATE_IGNORED display_name y-UNIQUE absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/sql_warehouses.txt b/bundle/direct/autotest/output/sql_warehouses.txt index 4cf3b4e4c1a..ce4a674a677 100644 --- a/bundle/direct/autotest/output/sql_warehouses.txt +++ b/bundle/direct/autotest/output/sql_warehouses.txt @@ -2,15 +2,15 @@ auto_stop_mins 10 absent UPDATE_IGNORE auto_stop_mins 20 absent UPDATE_IGNORED cluster_size 2X-Small absent UPDATE_IGNORED cluster_size X-Small absent UPDATE_IGNORED -cluster_size absent 2X-Small START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) -cluster_size absent X-Small START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) +cluster_size absent 2X-Small START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) +cluster_size absent X-Small START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) enable_photon true absent UPDATE_IGNORED max_num_clusters 2 absent UPDATE_IGNORED max_num_clusters 3 absent UPDATE_IGNORED -max_num_clusters absent 2 START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) -max_num_clusters absent 3 START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) -name absent x-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) -name absent y-UNIQUE START_NOT_REACHED cannot create the resource holding it: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) +max_num_clusters absent 2 START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) +max_num_clusters absent 3 START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) +name absent x-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) +name absent y-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) name x-UNIQUE absent UPDATE_IGNORED name y-UNIQUE absent UPDATE_IGNORED spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED From d84d8ca4cce438833eaa2fbef1cbaaef73e02147 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 13:21:49 +0200 Subject: [PATCH 098/118] Drop the invariant field-mutation targets, superseded by the Go catalog apply_update and apply_remote_update, apply_mutation.py, state_snapshot.py, the "# ACTION:" annotations they read, and verify_no_drift.py's --expect-change: all of it existed to move one field per config through the acceptance harness, which is what bundle/direct/autotest does now, exhaustively and without a golden per case. Keeping both would mean maintaining two answers to the same question. The invariant suite keeps everything else, no_drift included, and still passes. The configs keep the fields the annotations happened to sit on -- postgres_endpoint's suspend_timeout_duration is there because an SDK duration used to look like an empty struct to isEmptyStruct (#6377), which is worth saying whether or not anything mutates it. Co-authored-by: Isaac --- .agents/rules/testing.md | 4 +- acceptance/bin/apply_mutation.py | 179 ------------------ acceptance/bin/state_snapshot.py | 81 -------- acceptance/bin/verify_no_drift.py | 102 ++-------- acceptance/bundle/invariant/README.md | 39 ---- .../apply_remote_update/out.test.toml | 3 - .../invariant/apply_remote_update/output.txt | 1 - .../invariant/apply_remote_update/script | 34 ---- .../invariant/apply_remote_update/test.toml | 19 -- .../invariant/apply_update/out.test.toml | 3 - .../bundle/invariant/apply_update/output.txt | 1 - .../bundle/invariant/apply_update/script | 28 --- .../bundle/invariant/apply_update/test.toml | 19 -- .../bundle/invariant/configs/app.yml.tmpl | 5 - .../bundle/invariant/configs/job.yml.tmpl | 3 - .../configs/postgres_endpoint.yml.tmpl | 15 +- .../bundle/invariant/configs/schema.yml.tmpl | 8 +- acceptance/bundle/invariant/script.prepare | 76 -------- bundle/direct/autotest/harness_test.go | 19 +- 19 files changed, 33 insertions(+), 606 deletions(-) delete mode 100755 acceptance/bin/apply_mutation.py delete mode 100755 acceptance/bin/state_snapshot.py delete mode 100644 acceptance/bundle/invariant/apply_remote_update/out.test.toml delete mode 100644 acceptance/bundle/invariant/apply_remote_update/output.txt delete mode 100644 acceptance/bundle/invariant/apply_remote_update/script delete mode 100644 acceptance/bundle/invariant/apply_remote_update/test.toml delete mode 100644 acceptance/bundle/invariant/apply_update/out.test.toml delete mode 100644 acceptance/bundle/invariant/apply_update/output.txt delete mode 100644 acceptance/bundle/invariant/apply_update/script delete mode 100644 acceptance/bundle/invariant/apply_update/test.toml diff --git a/.agents/rules/testing.md b/.agents/rules/testing.md index 96faad1677f..d09b44674d2 100644 --- a/.agents/rules/testing.md +++ b/.agents/rules/testing.md @@ -184,10 +184,8 @@ Available on `PATH` during test execution (from `acceptance/bin/`): - `find.py REGEX [--expect N]`: find files matching regex in current directory. `--expect N` asserts an exact count. - `diff.py DIR1 DIR2` or `diff.py FILE1 FILE2`: recursive diff with test replacements applied. - `print_state.py [-t TARGET] [--backup]`: print deployment state (terraform or direct). -- `state_snapshot.py save|restore [-t TARGET]`: snapshot the deployment state file and restore it later, so a change the CLI made itself looks like out-of-band remote drift. Restore re-stamps the serial past the current one, otherwise `statemgmt.readStates` prefers the newer copy pushed to the workspace and the restore is a no-op. -- `apply_mutation.py FILE [NAME|--list]`: list or apply the `# ACTION:` field mutations declared in an invariant config. See `acceptance/bundle/invariant/README.md`. - `edit_resource.py TYPE ID < script.py`: fetch resource by ID, execute Python on it (resource in `r`), then update it. TYPE is `jobs` or `pipelines`. -- `verify_no_drift.py PLAN.json [--expect-change FIELD --shape local|remote]`: assert every action in a JSON plan is `skip`, or that one field has a planned change with the given shape (`local`: config moved away from state; `remote`: only the remote moved). +- `verify_no_drift.py PLAN.json`: assert every action in a JSON plan is `skip`. - `gron.py`: flatten JSON into greppable discrete assignments (simpler than `jq` for searching JSON). - `jq` is also available for JSON processing. diff --git a/acceptance/bin/apply_mutation.py b/acceptance/bin/apply_mutation.py deleted file mode 100755 index 6f37e3eb6c1..00000000000 --- a/acceptance/bin/apply_mutation.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env python3 -""" -Apply a single field mutation to a rendered databricks.yml. - -Mutations are declared in the config as trailing comments, so they are inert YAML -and do not affect the invariant targets that read the same config without mutating -it: - - description: hello # ACTION: REMOVE - #user_api_scopes: [iam.access] # ACTION: UNCOMMENT - #compute_size: LARGE # ACTION: SET XLARGE - -Each mutation is named ":", e.g. "description:remove". - -REMOVE and UNCOMMENT only work for a field the resource's update path can clear: -both targets return the remote to the base config, and for those two actions that -means clearing the field. A resource whose update request drops empty fields (an -omitempty payload without ForceSendFields, e.g. UpdateSchema) never converges, so -use SET there. - -EXPECT names the plan action the mutation should produce, defaulting to -"non-skip". A field the engine deliberately ignores expects "skip" instead, and -which of the two targets sees that depends on why it is ignored -- an -ignore_local_changes field is skipped when the config changes, an -ignore_remote_changes field when the remote drifts. So the expectation can be set -for both targets at once or for one of them: - - EXPECT: skip both targets - EXPECT_UPDATE: skip apply_update only - EXPECT_REMOTE_UPDATE: skip apply_remote_update only - -Usage: - apply_mutation.py FILE --list [--expect-key KEY] print " " per mutation - apply_mutation.py FILE NAME apply mutation NAME in place -""" - -import argparse -import re -import sys - -MARKER = "# ACTION:" -DEFAULT_EXPECT = "non-skip" -GENERIC_EXPECT_KEY = "EXPECT" -EXPECT_KEYS = (GENERIC_EXPECT_KEY, "EXPECT_UPDATE", "EXPECT_REMOTE_UPDATE") - -# Splits "SET XLARGE EXPECT_UPDATE: skip" into the action part and the key/value pairs. -EXPECT_RE = re.compile(r"\b(" + "|".join(EXPECT_KEYS) + r"):\s*(\S+)") - -REMOVE = "REMOVE" -UNCOMMENT = "UNCOMMENT" -SET = "SET" -ACTIONS = (REMOVE, UNCOMMENT, SET) - - -class Mutation: - def __init__(self, lineno, indent, body, key, action, arg, expects): - self.lineno = lineno - self.indent = indent - self.body = body - self.key = key - self.action = action - self.arg = arg - self.expects = expects - - @property - def name(self): - return f"{self.key}:{self.action.lower()}" - - def expect(self, expect_key): - for candidate in (expect_key, GENERIC_EXPECT_KEY): - if candidate in self.expects: - return self.expects[candidate] - return DEFAULT_EXPECT - - def render(self): - """Return the replacement line, or None when the line is dropped.""" - if self.action == REMOVE: - return None - if self.action == UNCOMMENT: - return self.indent + self.body - return f"{self.indent}{self.key}: {self.arg}" - - -def parse_line(lineno, line): - if MARKER not in line: - return None - - head, _, spec = line.partition(MARKER) - - expects = dict(EXPECT_RE.findall(spec)) - spec = EXPECT_RE.sub("", spec) - - action, _, arg = spec.strip().partition(" ") - action = action.upper() - arg = arg.strip() - - indent = head[: len(head) - len(head.lstrip())] - body = head.strip() - commented = body.startswith("#") - if commented: - body = body[1:].lstrip() - - key = body.partition(":")[0].strip() - - def fail(message): - sys.exit(f"{lineno}: {message}: {line.strip()!r}") - - if action not in ACTIONS: - fail(f"unknown action {action!r}, expected one of {', '.join(ACTIONS)}") - if not key: - fail("cannot determine field name") - if action == REMOVE and commented: - fail("REMOVE on a commented-out line is a no-op; use SET or drop the annotation") - if action == UNCOMMENT and not commented: - fail("UNCOMMENT on an active line is a no-op; use REMOVE or comment the line out") - if action == SET and not arg: - fail("SET requires a value") - - return Mutation(lineno, indent, body, key, action, arg, expects) - - -def parse(filename): - with open(filename) as fobj: - lines = fobj.read().splitlines(keepends=True) - - mutations = [] - seen = {} - for lineno, line in enumerate(lines, start=1): - mutation = parse_line(lineno, line) - if mutation is None: - continue - if mutation.name in seen: - sys.exit(f"{lineno}: duplicate mutation {mutation.name!r}, first seen on line {seen[mutation.name]}") - seen[mutation.name] = lineno - mutations.append(mutation) - - return lines, mutations - - -def apply(filename, lines, mutation): - replacement = mutation.render() - index = mutation.lineno - 1 - if replacement is None: - del lines[index] - else: - lines[index] = replacement + "\n" - with open(filename, "w") as fobj: - fobj.write("".join(lines)) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("filename") - parser.add_argument("name", nargs="?") - parser.add_argument("--list", action="store_true") - parser.add_argument("--expect-key", default=GENERIC_EXPECT_KEY, choices=EXPECT_KEYS) - args = parser.parse_args() - - lines, mutations = parse(args.filename) - - if args.list: - for mutation in mutations: - print(mutation.name, mutation.expect(args.expect_key)) - return - - if not args.name: - parser.error("either NAME or --list is required") - - for mutation in mutations: - if mutation.name == args.name: - apply(args.filename, lines, mutation) - return - - available = ", ".join(m.name for m in mutations) or "(none)" - sys.exit(f"mutation {args.name!r} not found in {args.filename}; available: {available}") - - -if __name__ == "__main__": - main() diff --git a/acceptance/bin/state_snapshot.py b/acceptance/bin/state_snapshot.py deleted file mode 100755 index 18c66a2e491..00000000000 --- a/acceptance/bin/state_snapshot.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -""" -Snapshot and restore the deployment state file. - -Restoring a snapshot makes an update the CLI performed itself look like an -out-of-band remote change: state says one thing, the remote says another. - -The engine is not hardcoded here -- print_state.get_state_file() resolves the -target directory and picks resources.json (direct) or terraform.tfstate -(terraform), so both engines go through the same helper. -""" - -import argparse -import json -import os -import shutil -import sys - -sys.path.insert(0, os.path.dirname(__file__)) -import print_state - -# Written during a deploy and merged into the state file at the end of it. A WAL -# left over from a crashed deploy would be replayed on top of a restored -# snapshot, undoing the restore, so drop it alongside. -WAL_SUFFIX = ".wal" - - -def snapshot_path(state_path): - return state_path + ".snapshot" - - -def save(target): - state_path = print_state.get_state_file(target, backup=False) - if not os.path.exists(state_path): - sys.exit(f"cannot snapshot {state_path}: file does not exist") - shutil.copyfile(state_path, snapshot_path(state_path)) - - -def read_serial(path): - with open(path) as fobj: - return json.load(fobj)["serial"] - - -def restore(target): - state_path = print_state.get_state_file(target, backup=False) - snapshot = snapshot_path(state_path) - if not os.path.exists(snapshot): - sys.exit(f"cannot restore {state_path}: {snapshot} does not exist") - - with open(snapshot) as fobj: - data = json.load(fobj) - - # The deploy that ran since the snapshot bumped the serial and pushed the state to - # the workspace. statemgmt.readStates picks whichever copy has the highest serial, so - # a snapshot restored at its original serial loses to that remote copy and the - # restore silently does nothing. Re-stamping it past the current serial makes the - # restored state authoritative again. - data["serial"] = read_serial(state_path) + 1 - - with open(state_path, "w") as fobj: - json.dump(data, fobj) - - wal = state_path + WAL_SUFFIX - if os.path.exists(wal): - os.remove(wal) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("action", choices=["save", "restore"]) - parser.add_argument("-t", "--target") - args = parser.parse_args() - - if args.action == "save": - save(args.target) - else: - restore(args.target) - - -if __name__ == "__main__": - main() diff --git a/acceptance/bin/verify_no_drift.py b/acceptance/bin/verify_no_drift.py index dba22e39d6d..9b272c1ce79 100755 --- a/acceptance/bin/verify_no_drift.py +++ b/acceptance/bin/verify_no_drift.py @@ -1,113 +1,37 @@ #!/usr/bin/env python3 """ -Check the actions in a JSON plan. - -By default every action must be "skip" -- i.e. a deploy left no drift behind. - ---expect-change instead requires a planned change for one field, with the shape the -caller created. "local" means the config moved away from state and remote -(old != new); "remote" means only the remote moved (old == new, remote differs). -Checking the shape and not just the action matters: reverting a config produces a -local change that looks like drift detection from the action alone. +Check that all actions in plan are "skip". """ -import argparse import json import sys -SKIP = "skip" -LOCAL = "local" -REMOTE = "remote" - - -class Missing: - """Stands in for an omitted old/new/remote value; the plan omits empty ones.""" - - def __repr__(self): - return "absent" - - -MISSING = Missing() - -def load_plan(path): +def check_plan(path): with open(path) as fobj: raw = fobj.read() + + changes_detected = 0 + try: - return raw, json.loads(raw)["plan"] + data = json.loads(raw) + for key, value in data["plan"].items(): + action = value.get("action") + if action != "skip": + print(f"Unexpected {action=} for {key}") + changes_detected += 1 except Exception: print(raw, flush=True) raise - -def fail(raw, message): - print(message) - print(raw, flush=True) - sys.exit(10) - - -def check_no_drift(path): - raw, plan = load_plan(path) - - changes_detected = 0 - for key, value in plan.items(): - action = value.get("action") - if action != SKIP: - print(f"Unexpected {action=} for {key}") - changes_detected += 1 - if changes_detected: print(raw, flush=True) sys.exit(10) -def find_change(plan, field): - for key, value in plan.items(): - change = value.get("changes", {}).get(field) - if change is not None: - return key, change - return None, None - - -def check_expected_change(path, field, shape): - raw, plan = load_plan(path) - - key, change = find_change(plan, field) - if change is None: - fail(raw, f"No planned change for field {field!r}") - - action = change.get("action") - if action == SKIP: - fail(raw, f"Change for {key}.{field} was skipped ({change.get('reason')!r})") - - old = change.get("old", MISSING) - new = change.get("new", MISSING) - remote = change.get("remote", MISSING) - - if shape == LOCAL and old == new: - fail(raw, f"Change for {key}.{field} is not a local change: old == new == {new!r}") - if shape == REMOTE: - if old != new: - fail(raw, f"Change for {key}.{field} is not remote-only: {old=} != {new=}") - if remote == new: - fail(raw, f"Change for {key}.{field} has no remote drift: remote == new == {new!r}") - - def main(): - parser = argparse.ArgumentParser() - parser.add_argument("filenames", nargs="+") - parser.add_argument("--expect-change", metavar="FIELD") - parser.add_argument("--shape", choices=[LOCAL, REMOTE]) - args = parser.parse_args() - - if bool(args.expect_change) != bool(args.shape): - parser.error("--expect-change and --shape must be given together") - - for path in args.filenames: - if args.expect_change: - check_expected_change(path, args.expect_change, args.shape) - else: - check_no_drift(path) + for path in sys.argv[1:]: + check_plan(path) if __name__ == "__main__": diff --git a/acceptance/bundle/invariant/README.md b/acceptance/bundle/invariant/README.md index d253c1974e9..184d3f541c4 100644 --- a/acceptance/bundle/invariant/README.md +++ b/acceptance/bundle/invariant/README.md @@ -4,42 +4,3 @@ no_drift test checks that there are no actions planned after successful deploy. test will dump full JSON plan to the output. In order to add a new test, add a config to configs/ and include it in test.toml. - -## Field mutations - -`apply_update` and `apply_remote_update` check that a single field change is planned, -applied, and converges. They read the change to make from the config itself, as a -trailing comment, so the same config still works for every other target: - -```yaml - comment: base comment # ACTION: SET "changed comment" - #description: test description # ACTION: UNCOMMENT - user_api_scopes: [iam.access] # ACTION: REMOVE -``` - -`apply_update` edits the config, so the plan must show a *local* change for that -field. `apply_remote_update` deploys the edit and then restores the pre-deploy state -snapshot, leaving the remote ahead of both state and config -- so the plan must show -a *remote* change for that field. Checking which of the two it is matters: reverting -a config also produces a change, so the action alone proves nothing. - -Both targets return the remote to the base config afterwards, which for REMOVE and -UNCOMMENT means clearing the field. A resource whose update request drops empty -fields never converges, so only SET is usable there (see `schema.yml.tmpl`). - -A field the engine deliberately ignores expects `skip` instead, and which target -sees that depends on why it is ignored -- `ignore_local_changes` is skipped when the -config changes, `ignore_remote_changes` when the remote drifts: - -```yaml - #compute_size: LARGE # ACTION: UNCOMMENT EXPECT_REMOTE_UPDATE: skip -``` - -Run one mutation on its own with `$MUTATION`: - -``` -MUTATION=comment:set go test ./acceptance \ - -run 'TestAccept/bundle/invariant/apply_update/.*INPUT_CONFIG=schema.yml.tmpl' -``` - -See `acceptance/bin/apply_mutation.py` for the full annotation reference. diff --git a/acceptance/bundle/invariant/apply_remote_update/out.test.toml b/acceptance/bundle/invariant/apply_remote_update/out.test.toml deleted file mode 100644 index 4ed4a6d98e8..00000000000 --- a/acceptance/bundle/invariant/apply_remote_update/out.test.toml +++ /dev/null @@ -1,3 +0,0 @@ -Cloud = true -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] -EnvMatrix.INPUT_CONFIG = ["app.yml.tmpl", "job.yml.tmpl", "schema.yml.tmpl"] diff --git a/acceptance/bundle/invariant/apply_remote_update/output.txt b/acceptance/bundle/invariant/apply_remote_update/output.txt deleted file mode 100644 index 7a28cb73a58..00000000000 --- a/acceptance/bundle/invariant/apply_remote_update/output.txt +++ /dev/null @@ -1 +0,0 @@ -INPUT_CONFIG_OK diff --git a/acceptance/bundle/invariant/apply_remote_update/script b/acceptance/bundle/invariant/apply_remote_update/script deleted file mode 100644 index 238a2000393..00000000000 --- a/acceptance/bundle/invariant/apply_remote_update/script +++ /dev/null @@ -1,34 +0,0 @@ -# Invariant to test: drift the CLI itself introduced is detected once state no -# longer records it. Deploying a mutation and then restoring the pre-deploy state -# snapshot leaves remote ahead of both state and config -- exactly the shape of an -# out-of-band change made by someone else. - -EXPECT_KEY=EXPECT_REMOTE_UPDATE -SHAPE=remote - -invariant_setup - -invariant_deploy LOG.deploy $CLI bundle deploy -invariant_verify_no_drift - -run_mutation() { - local name="$1" - local expect="$2" - - state_snapshot.py save - - invariant_render_config "$name" - invariant_deploy_quiet "LOG.deploy.$name" - - # The CLI made this change, so drop the state that records it and put the - # config back: remote now differs from both. - state_snapshot.py restore - invariant_render_config - - invariant_verify_mutation "$name" "$expect" - - invariant_deploy_quiet "LOG.deploy.$name.converge" - invariant_verify_no_drift -} - -invariant_for_each_mutation run_mutation diff --git a/acceptance/bundle/invariant/apply_remote_update/test.toml b/acceptance/bundle/invariant/apply_remote_update/test.toml deleted file mode 100644 index 6b79f65b10a..00000000000 --- a/acceptance/bundle/invariant/apply_remote_update/test.toml +++ /dev/null @@ -1,19 +0,0 @@ -# Both targets assert on the plan's per-field "changes" map, which only the direct -# engine emits -- a terraform plan carries a resource-level action and nothing else. -# Pinned here rather than inherited so re-enabling terraform in the parent matrix -# does not break these two targets. -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] - -# Only configs carrying "# ACTION:" annotations are worth running here; without any -# the target degenerates into a second copy of no_drift. Overrides the parent list. -# -# postgres_endpoint is left out: its annotated field is a oneof member, and the engine puts -# the member name in update_mask where the API only accepts the group name -# ("spec.suspension"), so the deploy is rejected. Both targets require the mutation to -# deploy, so neither can express that -- the annotation stays as the mutation to run once -# the mask is fixed. -EnvMatrix.INPUT_CONFIG = [ - "app.yml.tmpl", - "job.yml.tmpl", - "schema.yml.tmpl", -] diff --git a/acceptance/bundle/invariant/apply_update/out.test.toml b/acceptance/bundle/invariant/apply_update/out.test.toml deleted file mode 100644 index 4ed4a6d98e8..00000000000 --- a/acceptance/bundle/invariant/apply_update/out.test.toml +++ /dev/null @@ -1,3 +0,0 @@ -Cloud = true -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] -EnvMatrix.INPUT_CONFIG = ["app.yml.tmpl", "job.yml.tmpl", "schema.yml.tmpl"] diff --git a/acceptance/bundle/invariant/apply_update/output.txt b/acceptance/bundle/invariant/apply_update/output.txt deleted file mode 100644 index 7a28cb73a58..00000000000 --- a/acceptance/bundle/invariant/apply_update/output.txt +++ /dev/null @@ -1 +0,0 @@ -INPUT_CONFIG_OK diff --git a/acceptance/bundle/invariant/apply_update/script b/acceptance/bundle/invariant/apply_update/script deleted file mode 100644 index cc167ff892a..00000000000 --- a/acceptance/bundle/invariant/apply_update/script +++ /dev/null @@ -1,28 +0,0 @@ -# Invariant to test: a config change is planned, applied, and leaves no drift. -# Each "# ACTION:" annotation in the config is one mutation; all of them run -# against a single deployed resource, converging back to base in between. - -EXPECT_KEY=EXPECT_UPDATE -SHAPE=local - -invariant_setup - -invariant_deploy LOG.deploy $CLI bundle deploy -invariant_verify_no_drift - -run_mutation() { - local name="$1" - local expect="$2" - - invariant_render_config "$name" - invariant_verify_mutation "$name" "$expect" - invariant_deploy_quiet "LOG.deploy.$name" - invariant_verify_no_drift - - # Back to base so the next mutation starts from the same remote state. - invariant_render_config - invariant_deploy_quiet "LOG.deploy.$name.revert" - invariant_verify_no_drift -} - -invariant_for_each_mutation run_mutation diff --git a/acceptance/bundle/invariant/apply_update/test.toml b/acceptance/bundle/invariant/apply_update/test.toml deleted file mode 100644 index 6b79f65b10a..00000000000 --- a/acceptance/bundle/invariant/apply_update/test.toml +++ /dev/null @@ -1,19 +0,0 @@ -# Both targets assert on the plan's per-field "changes" map, which only the direct -# engine emits -- a terraform plan carries a resource-level action and nothing else. -# Pinned here rather than inherited so re-enabling terraform in the parent matrix -# does not break these two targets. -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] - -# Only configs carrying "# ACTION:" annotations are worth running here; without any -# the target degenerates into a second copy of no_drift. Overrides the parent list. -# -# postgres_endpoint is left out: its annotated field is a oneof member, and the engine puts -# the member name in update_mask where the API only accepts the group name -# ("spec.suspension"), so the deploy is rejected. Both targets require the mutation to -# deploy, so neither can express that -- the annotation stays as the mutation to run once -# the mask is fixed. -EnvMatrix.INPUT_CONFIG = [ - "app.yml.tmpl", - "job.yml.tmpl", - "schema.yml.tmpl", -] diff --git a/acceptance/bundle/invariant/configs/app.yml.tmpl b/acceptance/bundle/invariant/configs/app.yml.tmpl index 6e152e648c9..153b04e9b56 100644 --- a/acceptance/bundle/invariant/configs/app.yml.tmpl +++ b/acceptance/bundle/invariant/configs/app.yml.tmpl @@ -6,11 +6,6 @@ resources: foo: name: app-$UNIQUE_NAME source_code_path: ./app - # Mutations for apply_update / apply_remote_update; see acceptance/bin/apply_mutation.py. - #description: test description # ACTION: UNCOMMENT - # compute_size is a backend_default: with no value in config the backend's own - # value must be accepted (skip), but an explicit value must still be applied. - #compute_size: LARGE # ACTION: UNCOMMENT EXPECT_REMOTE_UPDATE: skip permissions: - level: CAN_USE group_name: users diff --git a/acceptance/bundle/invariant/configs/job.yml.tmpl b/acceptance/bundle/invariant/configs/job.yml.tmpl index 6e5dd89df3b..696483648b5 100644 --- a/acceptance/bundle/invariant/configs/job.yml.tmpl +++ b/acceptance/bundle/invariant/configs/job.yml.tmpl @@ -5,9 +5,6 @@ resources: jobs: foo: name: test-job-$UNIQUE_NAME - # Mutations for apply_update / apply_remote_update; see acceptance/bin/apply_mutation.py. - #description: test description # ACTION: UNCOMMENT - #max_concurrent_runs: 2 # ACTION: UNCOMMENT permissions: - level: CAN_VIEW group_name: users diff --git a/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl b/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl index b9096d552cf..da86c817365 100644 --- a/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl +++ b/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl @@ -18,14 +18,7 @@ resources: parent: ${resources.postgres_branches.branch.name} endpoint_id: test-endpoint-$UNIQUE_NAME endpoint_type: ENDPOINT_TYPE_READ_WRITE - # Mutations for apply_update / apply_remote_update; see acceptance/bin/apply_mutation.py. - # The mutation apply_update and apply_remote_update would run, if they could: the - # engine puts this field's own name in update_mask, but it is a oneof member and the - # API only accepts the group name ("spec.suspension"), so the update is rejected with - # "Unknown field path in update_mask". Both targets therefore exclude this config; see - # their test.toml. - # - # The planning half does work: an SDK duration used to look like an empty struct to - # isEmptyStruct, so every change to one was suppressed with reason "empty", until #6377. - # A remote change is still dropped on purpose, the field being declared spec:input_only. - suspend_timeout_duration: 300s # ACTION: SET 600s EXPECT_REMOTE_UPDATE: skip + # An SDK duration used to look like an empty struct to isEmptyStruct, so every change to one + # was suppressed with reason "empty", until #6377. A remote change is still dropped on + # purpose, the field being declared spec:input_only. + suspend_timeout_duration: 300s diff --git a/acceptance/bundle/invariant/configs/schema.yml.tmpl b/acceptance/bundle/invariant/configs/schema.yml.tmpl index 934c3928236..3cea5b06adb 100644 --- a/acceptance/bundle/invariant/configs/schema.yml.tmpl +++ b/acceptance/bundle/invariant/configs/schema.yml.tmpl @@ -6,13 +6,7 @@ resources: foo: catalog_name: main name: test-schema-$UNIQUE_NAME - # Mutations for apply_update / apply_remote_update; see acceptance/bin/apply_mutation.py. - # Only SET is usable here. Every field of UpdateSchema is omitempty and DoUpdate - # carries just the config's own ForceSendFields, so clearing a field drops it from - # the PATCH and the remote keeps its old value. Both targets have to return the - # remote to base, which for REMOVE/UNCOMMENT means clearing -- so those never - # converge for this resource. Applies to comment and properties alike. - comment: base comment # ACTION: SET "changed comment" + comment: base comment grants: - principal: account users privileges: diff --git a/acceptance/bundle/invariant/script.prepare b/acceptance/bundle/invariant/script.prepare index 5723191f916..54f2a3dbf25 100644 --- a/acceptance/bundle/invariant/script.prepare +++ b/acceptance/bundle/invariant/script.prepare @@ -52,79 +52,3 @@ invariant_verify_no_drift() { cat LOG.planjson.err | contains.py '!panic:' '!internal error' > /dev/null verify_no_drift.py LOG.planjson } - -# Asserts the plan reflects one mutation. expect=skip means the engine deliberately -# ignores this field, which is the strict "nothing planned at all" check. Otherwise the -# change must be planned for the mutated field, with the shape this target creates: -# SHAPE=local for a config change, SHAPE=remote for out-of-band remote drift. Checking -# the shape is what makes the two targets distinguishable -- reverting a config also -# produces a change, so the action alone proves nothing. -invariant_verify_mutation() { - local name="$1" - local expect="$2" - - $CLI bundle plan -o json > LOG.planjson 2>LOG.planjson.err - cat LOG.planjson.err | contains.py '!panic:' '!internal error' > /dev/null - - if [ "$expect" = "skip" ]; then - verify_no_drift.py LOG.planjson - else - verify_no_drift.py LOG.planjson --expect-change "${name%:*}" --shape "$SHAPE" - fi -} - -# Deploys without adding anything to output.txt. Unlike invariant_deploy this -# prints no INPUT_CONFIG_OK marker: config validity is already established by the -# base deploy that runs before any mutation. -invariant_deploy_quiet() { - local logfile="$1" - # Not just no panic: a deploy that fails here would otherwise pass silently and surface - # later as unexplained drift, since the remote never moved. On failure the log is the - # only record of why, so print it. - if ! trace $CLI bundle deploy &> "$logfile"; then - echo "deploy failed:" - cat "$logfile" - return 1 - fi - cat "$logfile" | contains.py '!panic:' '!internal error' > /dev/null -} - -# Calls HANDLER once per "# ACTION:" annotation in the config. -# EXPECT_KEY selects which expectation the annotations are read for, since an -# ignored field is skipped by only one of the two targets. -# -# On success nothing is printed: mutation names differ per INPUT_CONFIG, and every -# EnvMatrix variant has to produce the same output.txt. The name is recorded in -# LOG.mutations and in the per-mutation LOG files, and printed on failure so a -# broken run says which mutation broke. -# -# $MUTATION pins a single mutation for debugging: -# MUTATION=description:remove go test ./acceptance -run '...' -invariant_for_each_mutation() { - local handler="$1" - - apply_mutation.py databricks.yml --list --expect-key "$EXPECT_KEY" > LOG.mutations - - while read -r name expect; do - if [ -n "${MUTATION:-}" ] && [ "${MUTATION:-}" != "$name" ]; then - continue - fi - "$handler" "$name" "$expect" || { - echo "FAILED mutation=$name expect=$expect" - exit 1 - } - done < LOG.mutations -} - -# Re-renders databricks.yml from the template, optionally applying one mutation. -# Re-rendering is what returns the config to base, so a mutation never sees a -# previous one's edit. Only the envsubst step is repeated: the data files and the -# per-config init script are already in place from invariant_setup. -invariant_render_config() { - local name="${1:-}" - envsubst < "$TESTDIR/../configs/$INPUT_CONFIG" > databricks.yml - if [ -n "$name" ]; then - apply_mutation.py databricks.yml "$name" - fi - cp databricks.yml "LOG.config.${name:-base}" -} diff --git a/bundle/direct/autotest/harness_test.go b/bundle/direct/autotest/harness_test.go index 6df70bcfe5d..3865dd38581 100644 --- a/bundle/direct/autotest/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -187,11 +187,7 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC // in the cleanup case. Values (dbr, logdiag, cmdio, env) still propagate; the test // binary's own -timeout remains the backstop. ctx = dbr.MockRuntime(context.WithoutCancel(ctx), dbr.Environment{}) //exhaustruct:ignore - // A fixture's variables are passed the way a user passes them, so the config keeps the - // ${var...} reference the format requires rather than a resolved literal. - for name, value := range variables { - ctx := env.Set(ctx, "BUNDLE_VAR_"+name, value) - } + ctx = withBundleVars(ctx, variables) // Thousands of deploys run through here, so cap how long any one of them waits for // a resource to become ready. Without a cap a resource that never reaches its // terminal state stalls the whole suite instead of showing up as one bad verdict. @@ -424,6 +420,19 @@ func depKey(depType string) string { return depType } +// withBundleVars passes a fixture's variables the way a user does, so the config keeps the +// ${var...} reference its format requires instead of a literal resolved before validation. +func withBundleVars(ctx context.Context, variables map[string]string) context.Context { + // fatcontext objects to building a context in a loop, which is about a context that grows per + // request; this one is built once per harness and holds at most a handful of variables. Its + // autofix turns the assignment into a declaration, which drops every variable. + //nolint:fatcontext + for name, value := range variables { + ctx = env.Set(ctx, "BUNDLE_VAR_"+name, value) + } + return ctx +} + // resourceKey is the name every fixture's resource is declared under. One resource per bundle, // so the name carries nothing and only has to be stable. const resourceKey = "foo" From 9d01a08eef55addd46f507f4949ed1ef11219c5e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 13:31:08 +0200 Subject: [PATCH 099/118] Field catalog: attribute the CLI's log output to the test that produced it "Phase: load" under an unrelated transition was the puzzle. The phases run once per harness -- one occurrence for clusters' 412 transitions -- but libs/log's default handler writes to stderr, which belongs to no subtest, so a harness built by one resource type appeared beneath whichever parallel type had printed last. Routed through t.Log: the framework attributes it, and a passing run prints nothing at all instead of a hundred stray INFO lines. Nothing is lost -- the same lines appear under -v, and under the test that made them. Co-authored-by: Isaac --- bundle/direct/autotest/harness_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/bundle/direct/autotest/harness_test.go b/bundle/direct/autotest/harness_test.go index 3865dd38581..23ce0b4b186 100644 --- a/bundle/direct/autotest/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "math" "os" "path/filepath" @@ -26,6 +27,7 @@ import ( "github.com/databricks/cli/libs/dbr" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/env" + "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/cli/libs/structs/structaccess" "github.com/databricks/cli/libs/structs/structpath" @@ -187,6 +189,16 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC // in the cleanup case. Values (dbr, logdiag, cmdio, env) still propagate; the test // binary's own -timeout remains the backstop. ctx = dbr.MockRuntime(context.WithoutCancel(ctx), dbr.Environment{}) //exhaustruct:ignore + // The CLI logs through libs/log, whose default handler writes to stderr. Under -v that output + // belongs to no subtest, so a harness built by one resource type appears beneath whichever + // parallel type happened to print last -- which is how "Phase: load" ends up under an unrelated + // transition. Routed through t.Log instead: the framework attributes it, and it stays out of a + // passing run's output. + ctx = log.NewContext(ctx, slog.New(slog.NewTextHandler(testWriter{t}, &slog.HandlerOptions{ + Level: slog.LevelInfo, + AddSource: false, + ReplaceAttr: nil, + }))) ctx = withBundleVars(ctx, variables) // Thousands of deploys run through here, so cap how long any one of them waits for // a resource to become ready. Without a cap a resource that never reaches its @@ -433,6 +445,16 @@ func withBundleVars(ctx context.Context, variables map[string]string) context.Co return ctx } +// testWriter sends the CLI's log output to the test that owns it. +type testWriter struct { + t *testing.T +} + +func (w testWriter) Write(p []byte) (int, error) { + w.t.Log(strings.TrimRight(string(p), "\n")) + return len(p), nil +} + // resourceKey is the name every fixture's resource is declared under. One resource per bundle, // so the name carries nothing and only has to be stable. const resourceKey = "foo" From f6cfc46222dbe1489aa8eec4c9df7c74117735ef Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 14:01:27 +0200 Subject: [PATCH 100/118] Trim the branch to what the field catalog needs Three acceptance-side changes that came along for the ride, moved to a follow-up note instead: - postgres_endpoint's suspend_timeout_duration, added as a mutation target. It is independently useful -- the catalog cannot cover a duration at all, so this is the only place one is deployed and the only guard for #6377 -- which is exactly why it belongs in its own change rather than here. - schema's comment, also a mutation target, and redundant: the catalog covers the field, and this branch adds an acceptance test for clearing it. - folding model_with_permissions into model, which existed so the catalog would not drive two configs of one type. The catalog no longer reads the invariant corpus, so the reason is gone. The invariant configs are now identical to main. Co-authored-by: Isaac --- acceptance/bundle/invariant/configs/model.yml.tmpl | 3 --- .../invariant/configs/model_with_permissions.yml.tmpl | 10 ++++++++++ .../invariant/configs/postgres_endpoint.yml.tmpl | 4 ---- acceptance/bundle/invariant/configs/schema.yml.tmpl | 1 - acceptance/bundle/invariant/continue_293/out.test.toml | 1 + acceptance/bundle/invariant/continue_293/test.toml | 6 ++---- .../bundle/invariant/delete_idempotent/out.test.toml | 1 + .../bundle/invariant/destroy_idempotent/out.test.toml | 1 + acceptance/bundle/invariant/migrate/out.test.toml | 1 + acceptance/bundle/invariant/no_drift/out.test.toml | 1 + acceptance/bundle/invariant/test.toml | 1 + 11 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl diff --git a/acceptance/bundle/invariant/configs/model.yml.tmpl b/acceptance/bundle/invariant/configs/model.yml.tmpl index 44e6621ac1f..e105a731a33 100644 --- a/acceptance/bundle/invariant/configs/model.yml.tmpl +++ b/acceptance/bundle/invariant/configs/model.yml.tmpl @@ -5,6 +5,3 @@ resources: models: foo: name: test-model-$UNIQUE_NAME - permissions: - - level: CAN_READ - group_name: users diff --git a/acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl b/acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl new file mode 100644 index 00000000000..44e6621ac1f --- /dev/null +++ b/acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl @@ -0,0 +1,10 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + models: + foo: + name: test-model-$UNIQUE_NAME + permissions: + - level: CAN_READ + group_name: users diff --git a/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl b/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl index da86c817365..fbffd428ff6 100644 --- a/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl +++ b/acceptance/bundle/invariant/configs/postgres_endpoint.yml.tmpl @@ -18,7 +18,3 @@ resources: parent: ${resources.postgres_branches.branch.name} endpoint_id: test-endpoint-$UNIQUE_NAME endpoint_type: ENDPOINT_TYPE_READ_WRITE - # An SDK duration used to look like an empty struct to isEmptyStruct, so every change to one - # was suppressed with reason "empty", until #6377. A remote change is still dropped on - # purpose, the field being declared spec:input_only. - suspend_timeout_duration: 300s diff --git a/acceptance/bundle/invariant/configs/schema.yml.tmpl b/acceptance/bundle/invariant/configs/schema.yml.tmpl index 3cea5b06adb..d9aebda0cef 100644 --- a/acceptance/bundle/invariant/configs/schema.yml.tmpl +++ b/acceptance/bundle/invariant/configs/schema.yml.tmpl @@ -6,7 +6,6 @@ resources: foo: catalog_name: main name: test-schema-$UNIQUE_NAME - comment: base comment grants: - principal: account users privileges: diff --git a/acceptance/bundle/invariant/continue_293/out.test.toml b/acceptance/bundle/invariant/continue_293/out.test.toml index 7cf38456fd5..f5883975fcd 100644 --- a/acceptance/bundle/invariant/continue_293/out.test.toml +++ b/acceptance/bundle/invariant/continue_293/out.test.toml @@ -20,6 +20,7 @@ EnvMatrix.INPUT_CONFIG = [ "job_run_job_ref.yml.tmpl", "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", + "model.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", diff --git a/acceptance/bundle/invariant/continue_293/test.toml b/acceptance/bundle/invariant/continue_293/test.toml index aebc2f7e89d..f174ba144c4 100644 --- a/acceptance/bundle/invariant/continue_293/test.toml +++ b/acceptance/bundle/invariant/continue_293/test.toml @@ -3,10 +3,8 @@ EnvMatrixExclude.no_permission_ref = ["INPUT_CONFIG=job_permission_ref.yml.tmpl" EnvMatrixExclude.no_cross_resource_ref = ["INPUT_CONFIG=job_cross_resource_ref.yml.tmpl"] EnvMatrixExclude.no_grant_ref = ["INPUT_CONFIG=schema_grant_ref.yml.tmpl"] -# Model permissions did not work until 0.297.0 https://github.com/databricks/cli/pull/4941, -# and every invariant config carries permissions or grants, so the whole model config is -# excluded here rather than kept as a permissions-free variant. -EnvMatrixExclude.no_model = ["INPUT_CONFIG=model.yml.tmpl"] +# Model permissions did not work until 0.297.0 https://github.com/databricks/cli/pull/4941 +EnvMatrixExclude.no_model_with_permissions = ["INPUT_CONFIG=model_with_permissions.yml.tmpl"] # Vector Search resources are not supported on v0.293.0 EnvMatrixExclude.no_vector_search_endpoint = ["INPUT_CONFIG=vector_search_endpoint.yml.tmpl"] diff --git a/acceptance/bundle/invariant/delete_idempotent/out.test.toml b/acceptance/bundle/invariant/delete_idempotent/out.test.toml index 82839d1c143..8ed109d4820 100644 --- a/acceptance/bundle/invariant/delete_idempotent/out.test.toml +++ b/acceptance/bundle/invariant/delete_idempotent/out.test.toml @@ -28,6 +28,7 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", + "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", diff --git a/acceptance/bundle/invariant/destroy_idempotent/out.test.toml b/acceptance/bundle/invariant/destroy_idempotent/out.test.toml index 82839d1c143..8ed109d4820 100644 --- a/acceptance/bundle/invariant/destroy_idempotent/out.test.toml +++ b/acceptance/bundle/invariant/destroy_idempotent/out.test.toml @@ -28,6 +28,7 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", + "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", diff --git a/acceptance/bundle/invariant/migrate/out.test.toml b/acceptance/bundle/invariant/migrate/out.test.toml index 7dcb237b857..de39decabc5 100644 --- a/acceptance/bundle/invariant/migrate/out.test.toml +++ b/acceptance/bundle/invariant/migrate/out.test.toml @@ -18,6 +18,7 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", + "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "pipeline_allow_duplicate_names.yml.tmpl", diff --git a/acceptance/bundle/invariant/no_drift/out.test.toml b/acceptance/bundle/invariant/no_drift/out.test.toml index 82839d1c143..8ed109d4820 100644 --- a/acceptance/bundle/invariant/no_drift/out.test.toml +++ b/acceptance/bundle/invariant/no_drift/out.test.toml @@ -28,6 +28,7 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", + "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", diff --git a/acceptance/bundle/invariant/test.toml b/acceptance/bundle/invariant/test.toml index 6bc15f822ef..ac4584e034d 100644 --- a/acceptance/bundle/invariant/test.toml +++ b/acceptance/bundle/invariant/test.toml @@ -46,6 +46,7 @@ EnvMatrix.INPUT_CONFIG = [ "job_with_depends_on.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", + "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "model_serving_endpoint_telemetry.yml.tmpl", "pipeline.yml.tmpl", From 36ef94fe97f328bfb9f035820bb4cb02b47b81ce Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 14:12:42 +0200 Subject: [PATCH 101/118] Move the two promoted acceptance tests out of the branch Both document real engine behaviour the catalog found -- a schema field that never converges when cleared, and a recreate that leaves the grants child keyed to the old id -- and both stand on their own without the catalog. Recorded in the follow-up note with the diagnosis, and recoverable from this branch's history, so nothing is lost by landing them separately. What is left in acceptance/ is only what the testserver fidelity fixes force: the bind fixture, the Azure KeyVault scope case, the serving-endpoint notifications goldens, and the model description edit. Co-authored-by: Isaac --- .../schemas/drift/clear_field/databricks.yml | 10 --- .../schemas/drift/clear_field/out.test.toml | 2 - .../schemas/drift/clear_field/output.txt | 61 ------------------- .../schemas/drift/clear_field/script | 22 ------- .../schemas/drift/clear_field/test.toml | 8 --- .../drift/managed_properties/databricks.yml | 8 --- .../drift/managed_properties/out.test.toml | 2 - .../drift/managed_properties/output.txt | 41 ------------- .../schemas/drift/managed_properties/script | 14 ----- .../drift/managed_properties/test.toml | 5 -- .../out.plan_after_recreate.direct.txt | 5 -- .../out.plan_after_recreate.terraform.txt | 3 - .../resources/volumes/recreate/output.txt | 14 ----- .../bundle/resources/volumes/recreate/script | 18 ------ .../resources/volumes/recreate/test.toml | 2 - 15 files changed, 215 deletions(-) delete mode 100644 acceptance/bundle/resources/schemas/drift/clear_field/databricks.yml delete mode 100644 acceptance/bundle/resources/schemas/drift/clear_field/out.test.toml delete mode 100644 acceptance/bundle/resources/schemas/drift/clear_field/output.txt delete mode 100644 acceptance/bundle/resources/schemas/drift/clear_field/script delete mode 100644 acceptance/bundle/resources/schemas/drift/clear_field/test.toml delete mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/databricks.yml delete mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/out.test.toml delete mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/output.txt delete mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/script delete mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/test.toml delete mode 100644 acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.direct.txt delete mode 100644 acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.terraform.txt diff --git a/acceptance/bundle/resources/schemas/drift/clear_field/databricks.yml b/acceptance/bundle/resources/schemas/drift/clear_field/databricks.yml deleted file mode 100644 index 95a79d34ddb..00000000000 --- a/acceptance/bundle/resources/schemas/drift/clear_field/databricks.yml +++ /dev/null @@ -1,10 +0,0 @@ -bundle: - name: test-bundle - -resources: - schemas: - schema1: - name: schema_clear_field - catalog_name: main - comment: base comment - custom_max_retention_hours: 24 diff --git a/acceptance/bundle/resources/schemas/drift/clear_field/out.test.toml b/acceptance/bundle/resources/schemas/drift/clear_field/out.test.toml deleted file mode 100644 index c502b28221b..00000000000 --- a/acceptance/bundle/resources/schemas/drift/clear_field/out.test.toml +++ /dev/null @@ -1,2 +0,0 @@ -Cloud = true -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/schemas/drift/clear_field/output.txt b/acceptance/bundle/resources/schemas/drift/clear_field/output.txt deleted file mode 100644 index 28ec26bb237..00000000000 --- a/acceptance/bundle/resources/schemas/drift/clear_field/output.txt +++ /dev/null @@ -1,61 +0,0 @@ - -=== Initial deployment ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... -Created schemas.schema1 -Files: 5 uploaded, 0 deleted -Resources: 1 created, 0 changed, 0 deleted, 0 unchanged - -=== Removing comment converges ->>> update_file.py databricks.yml comment: base comment - ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... -Updated schemas.schema1 -Files: 3 uploaded, 0 deleted -Resources: 0 created, 1 changed, 0 deleted, 0 unchanged - ->>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged - -=== Removing custom_max_retention_hours does not ->>> update_file.py databricks.yml custom_max_retention_hours: 24 - ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... -Updated schemas.schema1 -Files: 3 uploaded, 0 deleted -Resources: 0 created, 1 changed, 0 deleted, 0 unchanged - ->>> [CLI] bundle plan -update schemas.schema1 - -Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged - -=== The update PATCH carried an empty body, which is why nothing changed ->>> print_requests.py //unity --sort -{ - "method": "PATCH", - "path": "/api/2.1/unity-catalog/schemas/main.schema_clear_field", - "body": { - "comment": "", - "custom_max_retention_hours": 24 - } -} -{ - "method": "PATCH", - "path": "/api/2.1/unity-catalog/schemas/main.schema_clear_field", - "body": { - "comment": "" - } -} -{ - "method": "POST", - "path": "/api/2.1/unity-catalog/schemas", - "body": { - "catalog_name": "main", - "comment": "base comment", - "custom_max_retention_hours": 24, - "name": "schema_clear_field" - } -} diff --git a/acceptance/bundle/resources/schemas/drift/clear_field/script b/acceptance/bundle/resources/schemas/drift/clear_field/script deleted file mode 100644 index 4efa3534588..00000000000 --- a/acceptance/bundle/resources/schemas/drift/clear_field/script +++ /dev/null @@ -1,22 +0,0 @@ -# Removing a field from the config should clear it remotely and converge. comment does. -# custom_max_retention_hours does not, and this records that difference. - -title "Initial deployment" -trace $CLI bundle deploy - -title "Removing comment converges" -trace update_file.py databricks.yml " comment: base comment" "" -trace $CLI bundle deploy -trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete" - -# BUG: custom_max_retention_hours is omitempty in catalog.UpdateSchema and DoUpdate -# forwards only the config's own ForceSendFields, so dropping it from the config drops it -# from the PATCH body too. UC keeps the old value, the deploy reports success, and every -# later plan re-proposes the same update. See test.toml. -title "Removing custom_max_retention_hours does not" -trace update_file.py databricks.yml " custom_max_retention_hours: 24" "" -trace $CLI bundle deploy -trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete" - -title "The update PATCH carried an empty body, which is why nothing changed" -trace print_requests.py //unity --sort diff --git a/acceptance/bundle/resources/schemas/drift/clear_field/test.toml b/acceptance/bundle/resources/schemas/drift/clear_field/test.toml deleted file mode 100644 index 988bb121e36..00000000000 --- a/acceptance/bundle/resources/schemas/drift/clear_field/test.toml +++ /dev/null @@ -1,8 +0,0 @@ -Badness = "Removing custom_max_retention_hours from the config never clears it: the field is omitempty in UpdateSchema and DoUpdate forwards only the config's own ForceSendFields, so the PATCH body comes out empty and every later plan re-proposes the same update" - -Cloud = true -RecordRequests = true - -# Terraform issues a spurious PATCH for enable_predictive_optimization on every deploy, -# which would mask the drift this test is about. -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/databricks.yml b/acceptance/bundle/resources/schemas/drift/managed_properties/databricks.yml deleted file mode 100644 index 5ab0d85ca59..00000000000 --- a/acceptance/bundle/resources/schemas/drift/managed_properties/databricks.yml +++ /dev/null @@ -1,8 +0,0 @@ -bundle: - name: test-bundle - -resources: - schemas: - schema1: - name: schema_managed_defaults - catalog_name: main diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/out.test.toml b/acceptance/bundle/resources/schemas/drift/managed_properties/out.test.toml deleted file mode 100644 index 0938e678987..00000000000 --- a/acceptance/bundle/resources/schemas/drift/managed_properties/out.test.toml +++ /dev/null @@ -1,2 +0,0 @@ -Cloud = false -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/output.txt b/acceptance/bundle/resources/schemas/drift/managed_properties/output.txt deleted file mode 100644 index 53d54f1357c..00000000000 --- a/acceptance/bundle/resources/schemas/drift/managed_properties/output.txt +++ /dev/null @@ -1,41 +0,0 @@ - -=== Initial deployment ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... -Created schemas.schema1 -Files: 0 uploaded, 0 deleted -Resources: 1 created, 0 changed, 0 deleted, 0 unchanged - -=== Plan is a no-op despite UC auto-populating managed properties ->>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged - -=== The remote-only properties map is skipped as a backend default (confirms the matched rule) ->>> [CLI] bundle plan --output json -{ - "properties": { - "action": "skip", - "reason": "backend_default", - "remote": { - "unity.catalog.managed.delta.defaults.defaultClusterByAuto": "true", - "unity.catalog.managed.delta.defaults.delta.checkpointPolicy": "v2", - "unity.catalog.managed.delta.defaults.delta.enableRowTracking": "true", - "unity.catalog.managed.delta.defaults.delta.feature.catalogManaged": "supported", - "unity.catalog.managed.delta.defaults.delta.parquet.format.version": "2.12.0", - "unity.catalog.managed.delta.defaults.delta.parquet.format.version.afe.internal": "2.12.0", - "unity.catalog.managed.iceberg.defaults.delta.feature.catalogManaged": "true" - } - } -} - -=== Redeploy is a no-op (no UpdateSchema call) ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... -Files: 0 uploaded, 0 deleted -Resources: 0 created, 0 changed, 0 deleted, 1 unchanged - ->>> print_requests.py //unity -json.method = "POST"; -json.path = "/api/2.1/unity-catalog/schemas"; -json.body.catalog_name = "main"; -json.body.name = "schema_managed_defaults"; diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/script b/acceptance/bundle/resources/schemas/drift/managed_properties/script deleted file mode 100644 index fadd886cd48..00000000000 --- a/acceptance/bundle/resources/schemas/drift/managed_properties/script +++ /dev/null @@ -1,14 +0,0 @@ -echo "*" > .gitignore - -title "Initial deployment" -trace $CLI bundle deploy - -title "Plan is a no-op despite UC auto-populating managed properties" -trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged" - -title "The remote-only properties map is skipped as a backend default (confirms the matched rule)" -trace $CLI bundle plan --output json | jq '.plan[].changes' - -title "Redeploy is a no-op (no UpdateSchema call)" -trace $CLI bundle deploy -trace print_requests.py //unity | gron.py | contains.py '!json.method = "PATCH"' diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/test.toml b/acceptance/bundle/resources/schemas/drift/managed_properties/test.toml deleted file mode 100644 index 5016e85b395..00000000000 --- a/acceptance/bundle/resources/schemas/drift/managed_properties/test.toml +++ /dev/null @@ -1,5 +0,0 @@ -RecordRequests = true - -# Terraform issues a spurious PATCH for enable_predictive_optimization on every -# deploy, which is outside the scope of backend-default handling in resources.yml. -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.direct.txt b/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.direct.txt deleted file mode 100644 index c35ba2907ae..00000000000 --- a/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.direct.txt +++ /dev/null @@ -1,5 +0,0 @@ - ->>> [CLI] bundle plan --var=schema_name=${resources.schemas.schema2.name} -update volumes.foo.grants - -Plan: 0 to add, 1 to change, 0 to delete, 3 unchanged diff --git a/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.terraform.txt b/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.terraform.txt deleted file mode 100644 index 6c0e42d66da..00000000000 --- a/acceptance/bundle/resources/volumes/recreate/out.plan_after_recreate.terraform.txt +++ /dev/null @@ -1,3 +0,0 @@ - ->>> [CLI] bundle plan --var=schema_name=${resources.schemas.schema2.name} -Plan: 0 to add, 0 to change, 0 to delete, 4 unchanged diff --git a/acceptance/bundle/resources/volumes/recreate/output.txt b/acceptance/bundle/resources/volumes/recreate/output.txt index bb9a50e6c79..fc94847412b 100644 --- a/acceptance/bundle/resources/volumes/recreate/output.txt +++ b/acceptance/bundle/resources/volumes/recreate/output.txt @@ -58,20 +58,6 @@ cloud tenant within 30 days. For external volumes, the metadata about the volume is removed from the catalog, but the underlying files are not deleted: recreate resources.volumes.foo -=== The grants landed on the recreated volume ->>> [CLI] grants get VOLUME main.schema2-[UNIQUE_NAME].my_volume -{ - "privilege_assignments": [ - { - "principal": "account users", - "privileges": [ - "WRITE_VOLUME" - ] - } - ] -} - -=== Plan after recreate >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: delete resources.schemas.schema1 diff --git a/acceptance/bundle/resources/volumes/recreate/script b/acceptance/bundle/resources/volumes/recreate/script index 544e50c59a3..5e1b2e779ee 100644 --- a/acceptance/bundle/resources/volumes/recreate/script +++ b/acceptance/bundle/resources/volumes/recreate/script @@ -23,21 +23,3 @@ trace errcode $CLI bundle deploy --var=schema_name='${resources.schemas.schema2. title "Successfully recreate the volume with --auto-approve" trace $CLI bundle deploy --var=schema_name='${resources.schemas.schema2.name}' --auto-approve -qq - -title "The grants landed on the recreated volume" -NEW_VOLUME_FULL_NAME="${CATALOG_NAME}.schema2-${UNIQUE_NAME}.${VOLUME_NAME}" -trace $CLI grants get VOLUME "${NEW_VOLUME_FULL_NAME}" - -# The engines diverge here, so the plan goes to a per-engine file. Terraform converges. -# -# BUG (direct engine): the plan is not clean. Recreating the volume gave it a new id, but -# the grants node's state entry keeps the old one -- DeploymentUnit.Update saves state -# under the id it was called with, and neither grants nor permissions implements -# DoUpdateWithID, so the entry is never re-keyed. DoRead echoes the stale id back as -# full_name, the planner compares it against the config's new name, and plans the same -# update again -- every deploy, forever. The grants themselves are correct (asserted -# above), so this is stale bookkeeping rather than lost access; the deploy just never -# reports converged. Same shape for permissions on any resource whose id changes on -# recreate. -title "Plan after recreate" -trace $CLI bundle plan --var=schema_name='${resources.schemas.schema2.name}' &> out.plan_after_recreate.$DATABRICKS_BUNDLE_ENGINE.txt diff --git a/acceptance/bundle/resources/volumes/recreate/test.toml b/acceptance/bundle/resources/volumes/recreate/test.toml index 9f039a99ab8..4d77b7783c8 100644 --- a/acceptance/bundle/resources/volumes/recreate/test.toml +++ b/acceptance/bundle/resources/volumes/recreate/test.toml @@ -1,5 +1,3 @@ -Badness = "Direct engine: recreating a resource re-keys the parent but not its permissions/grants child, whose state entry keeps the old id. DoRead echoes that stale id back, so the child re-proposes the same update on every deploy and never converges. The ACL itself is applied correctly, so this is stale bookkeeping rather than lost access. Terraform converges." - Cloud = true RecordRequests = false RunsOnDbr = true From da63bda321da2f1dea141e884b51a4c6200ad23c Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 14:13:28 +0200 Subject: [PATCH 102/118] Restore schemas/drift/managed_properties, deleted by mistake Removing my own clear_field test took its sibling with it: managed_properties is an upstream test and was never part of this branch. Co-authored-by: Isaac --- .../drift/managed_properties/databricks.yml | 8 ++++ .../drift/managed_properties/out.test.toml | 2 + .../drift/managed_properties/output.txt | 41 +++++++++++++++++++ .../schemas/drift/managed_properties/script | 14 +++++++ .../drift/managed_properties/test.toml | 5 +++ 5 files changed, 70 insertions(+) create mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/databricks.yml create mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/out.test.toml create mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/output.txt create mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/script create mode 100644 acceptance/bundle/resources/schemas/drift/managed_properties/test.toml diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/databricks.yml b/acceptance/bundle/resources/schemas/drift/managed_properties/databricks.yml new file mode 100644 index 00000000000..5ab0d85ca59 --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/managed_properties/databricks.yml @@ -0,0 +1,8 @@ +bundle: + name: test-bundle + +resources: + schemas: + schema1: + name: schema_managed_defaults + catalog_name: main diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/out.test.toml b/acceptance/bundle/resources/schemas/drift/managed_properties/out.test.toml new file mode 100644 index 00000000000..0938e678987 --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/managed_properties/out.test.toml @@ -0,0 +1,2 @@ +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/output.txt b/acceptance/bundle/resources/schemas/drift/managed_properties/output.txt new file mode 100644 index 00000000000..53d54f1357c --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/managed_properties/output.txt @@ -0,0 +1,41 @@ + +=== Initial deployment +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Created schemas.schema1 +Files: 0 uploaded, 0 deleted +Resources: 1 created, 0 changed, 0 deleted, 0 unchanged + +=== Plan is a no-op despite UC auto-populating managed properties +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged + +=== The remote-only properties map is skipped as a backend default (confirms the matched rule) +>>> [CLI] bundle plan --output json +{ + "properties": { + "action": "skip", + "reason": "backend_default", + "remote": { + "unity.catalog.managed.delta.defaults.defaultClusterByAuto": "true", + "unity.catalog.managed.delta.defaults.delta.checkpointPolicy": "v2", + "unity.catalog.managed.delta.defaults.delta.enableRowTracking": "true", + "unity.catalog.managed.delta.defaults.delta.feature.catalogManaged": "supported", + "unity.catalog.managed.delta.defaults.delta.parquet.format.version": "2.12.0", + "unity.catalog.managed.delta.defaults.delta.parquet.format.version.afe.internal": "2.12.0", + "unity.catalog.managed.iceberg.defaults.delta.feature.catalogManaged": "true" + } + } +} + +=== Redeploy is a no-op (no UpdateSchema call) +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Files: 0 uploaded, 0 deleted +Resources: 0 created, 0 changed, 0 deleted, 1 unchanged + +>>> print_requests.py //unity +json.method = "POST"; +json.path = "/api/2.1/unity-catalog/schemas"; +json.body.catalog_name = "main"; +json.body.name = "schema_managed_defaults"; diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/script b/acceptance/bundle/resources/schemas/drift/managed_properties/script new file mode 100644 index 00000000000..fadd886cd48 --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/managed_properties/script @@ -0,0 +1,14 @@ +echo "*" > .gitignore + +title "Initial deployment" +trace $CLI bundle deploy + +title "Plan is a no-op despite UC auto-populating managed properties" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged" + +title "The remote-only properties map is skipped as a backend default (confirms the matched rule)" +trace $CLI bundle plan --output json | jq '.plan[].changes' + +title "Redeploy is a no-op (no UpdateSchema call)" +trace $CLI bundle deploy +trace print_requests.py //unity | gron.py | contains.py '!json.method = "PATCH"' diff --git a/acceptance/bundle/resources/schemas/drift/managed_properties/test.toml b/acceptance/bundle/resources/schemas/drift/managed_properties/test.toml new file mode 100644 index 00000000000..5016e85b395 --- /dev/null +++ b/acceptance/bundle/resources/schemas/drift/managed_properties/test.toml @@ -0,0 +1,5 @@ +RecordRequests = true + +# Terraform issues a spurious PATCH for enable_predictive_optimization on every +# deploy, which is outside the scope of backend-default handling in resources.yml. +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] From e13c1425b59e6397a1941d71c400342c92fa1ab0 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 15:51:21 +0200 Subject: [PATCH 103/118] acc: create-scope was sending the wrong field name for the vault metadata The Azure KeyVault case declared "keyvault_metadata", which is the bundle schema's name for it; the raw API takes "backend_azure_keyvault". The CLI said so -- "Warning: unknown field: keyvault_metadata" -- and dropped it, so the request had no metadata in it at all. That went unnoticed while the fake server accepted a KeyVault scope without metadata. Once it started requiring it, as the API does, the create failed and the script aborted with it: the delete and the case after it never ran. With the right field name the create succeeds again, the whole script runs, and the last case reaches the API and gets its real rejection. Co-authored-by: Isaac --- acceptance/cmd/workspace/create-scope/output.txt | 12 +++++++----- acceptance/cmd/workspace/create-scope/script | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/acceptance/cmd/workspace/create-scope/output.txt b/acceptance/cmd/workspace/create-scope/output.txt index f818405d634..b950aa9d08d 100644 --- a/acceptance/cmd/workspace/create-scope/output.txt +++ b/acceptance/cmd/workspace/create-scope/output.txt @@ -8,14 +8,16 @@ Error: expected JSON object, received sequence >>> [CLI] secrets create-scope --scope-backend-type AZURE_KEYVAULT --json { "scope": "xxxxxxxx-xxx", "scope_backend_type": "AZURE_KEYVAULT", - "keyvault_metadata": { + "backend_azure_keyvault": { "dns_name": "test", "resource_id": "123456" } } -Warning: unknown field: keyvault_metadata - in (inline):4:4 -Error: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! +>>> [CLI] secrets delete-scope xxxxxxxx-xxx -Exit code: 1 +>>> musterr [CLI] secrets create-scope --scope-backend-type AZURE_KEYVAULT --json { + "scope": "xxxxxxxx-xxx", + "scope_backend_type": "AZURE_KEYVAULT" +} +Error: Scope with Azure KeyVault must have AzureKeyVaultSecretScopeMetadata defined! diff --git a/acceptance/cmd/workspace/create-scope/script b/acceptance/cmd/workspace/create-scope/script index 6ee4dfb5e6a..96480d51b18 100644 --- a/acceptance/cmd/workspace/create-scope/script +++ b/acceptance/cmd/workspace/create-scope/script @@ -5,7 +5,7 @@ trace musterr $CLI secrets create-scope --scope-backend-type AZURE_KEYVAULT --js trace $CLI secrets create-scope --scope-backend-type AZURE_KEYVAULT --json '{ "scope": "xxxxxxxx-xxx", "scope_backend_type": "AZURE_KEYVAULT", - "keyvault_metadata": { + "backend_azure_keyvault": { "dns_name": "test", "resource_id": "123456" } From 824a87cb098804a02c3041d997e6fadc5ffaecb1 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 17:42:32 +0200 Subject: [PATCH 104/118] Merge main again: two more Lakebase mask fixes the catalog had recorded #6443 masks a Lakebase oneof member under its group name, and #6089 adds ./task doctest. The first lands on rows this catalog was already recording as errors: nine of them, across postgres_projects, postgres_branches and postgres_endpoints, become OK or SUPPRESSED. Which is the point of the exercise -- those rows named the bug in the API's own words ("Unknown field path in update_mask") and now name its absence. Co-authored-by: Isaac --- .../direct/autotest/output/postgres_branches.txt | 16 ++++++---------- .../autotest/output/postgres_endpoints.txt | 10 +++------- .../direct/autotest/output/postgres_projects.txt | 8 +++----- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/bundle/direct/autotest/output/postgres_branches.txt b/bundle/direct/autotest/output/postgres_branches.txt index d1cd81e2c79..6f4d47376ab 100644 --- a/bundle/direct/autotest/output/postgres_branches.txt +++ b/bundle/direct/autotest/output/postgres_branches.txt @@ -1,19 +1,15 @@ is_protected true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -no_expiry absent false BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) -no_expiry absent true BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) -no_expiry false absent BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) -no_expiry false true BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) -no_expiry true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) -no_expiry true false BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU... 'spec.no_expiry' (400 INVALID_PARAMETER_VALUE) +no_expiry absent false BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +no_expiry true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) parent x y BASE_ERROR cannot recreate resources.postgres_branches.foo: No stub found for pattern: POST /api/2.0/postgres/x/branches (501 ) parent y x BASE_ERROR cannot recreate resources.postgres_branches.foo: No stub found for pattern: POST /api/2.0/postgres/y/branches (501 ) === summary -OK 7 +OK 10 OK_RECREATE 14 OK_INERT 6 -SUPPRESSED 3 +SUPPRESSED 4 NOT_OBSERVABLE 1 -BACKEND_ERROR 3 +BACKEND_ERROR 2 SKIPPED 1 -BASE_ERROR 6 +BASE_ERROR 3 diff --git a/bundle/direct/autotest/output/postgres_endpoints.txt b/bundle/direct/autotest/output/postgres_endpoints.txt index 30d4db10720..d0709dd2da2 100644 --- a/bundle/direct/autotest/output/postgres_endpoints.txt +++ b/bundle/direct/autotest/output/postgres_endpoints.txt @@ -13,19 +13,15 @@ group.max 1 2 BASE_ERROR c group.max 2 1 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) group.min 1 2 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) group.min 2 1 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -no_suspension absent true BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ec.no_suspension' (400 INVALID_PARAMETER_VALUE) -no_suspension false true BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ec.no_suspension' (400 INVALID_PARAMETER_VALUE) -no_suspension true absent BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ec.no_suspension' (400 INVALID_PARAMETER_VALUE) -no_suspension true false BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ec.no_suspension' (400 INVALID_PARAMETER_VALUE) parent x y BASE_ERROR cannot recreate resources.postgres_endpoints.foo: No stub found for pattern: POST /api/2.0/postgres/x/endpoints (501 ) parent y x BASE_ERROR cannot recreate resources.postgres_endpoints.foo: No stub found for pattern: POST /api/2.0/postgres/y/endpoints (501 ) === summary -OK 11 +OK 15 OK_RECREATE 4 OK_INERT 6 SUPPRESSED 2 NOT_OBSERVABLE 2 -BACKEND_ERROR 9 +BACKEND_ERROR 7 SKIPPED 1 -BASE_ERROR 12 +BASE_ERROR 10 diff --git a/bundle/direct/autotest/output/postgres_projects.txt b/bundle/direct/autotest/output/postgres_projects.txt index 472e8d85e7b..fa3334a235b 100644 --- a/bundle/direct/autotest/output/postgres_projects.txt +++ b/bundle/direct/autotest/output/postgres_projects.txt @@ -19,9 +19,7 @@ default_endpoint_settings.autoscaling_limit_min_cu 2 absent BASE_ER default_endpoint_settings.autoscaling_limit_min_cu absent 1 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) default_endpoint_settings.autoscaling_limit_min_cu absent 2 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) default_endpoint_settings.no_suspension false absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.no_suspension false true BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...gs.no_suspension' (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...gs.no_suspension' (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.no_suspension true false BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...gs.no_suspension' (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) @@ -29,9 +27,9 @@ display_name y absent BACKEND_ERROR enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) === summary -OK 27 +OK 29 OK_RECREATE 8 SUPPRESSED 4 -BACKEND_ERROR 15 +BACKEND_ERROR 13 SKIPPED 3 BASE_ERROR 14 From aecae7d4d04fb7d66e1aed34bcd490a3c036678f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 17:50:52 +0200 Subject: [PATCH 105/118] Field catalog: stop seeding a field the create does not apply email_notifications was in the serving endpoint's base for no reason: the leaves are reachable without it, because the suite grows the container itself. Measured -- 32 field rows either way; the only difference was one BASELINE_DRIFT row. Seeding it made the endpoint drift from the moment it existed, and every other field for that type was then measured against a resource already pending. The behaviour it exposed is real and stays recorded, in acceptance/bundle/resources/model_serving_endpoints/update/email-notifications, where the create is shown not applying the notifications and the update applying them. Also fixes eleven comments across seven fixtures that still explained themselves in terms of "the corpus config". The catalog stopped reading the invariant corpus when the fixtures were folded, so those sentences described a mechanism that no longer exists -- including a skip whose stated reason was that a corpus config "is not the simplest one", a rule that no longer exists either. Co-authored-by: Isaac --- .../output/model_serving_endpoints.txt | 6 +-- .../direct/autotest/testdata/fields/apps.yml | 2 +- .../autotest/testdata/fields/clusters.yml | 2 +- .../testdata/fields/external_locations.yml | 2 +- .../direct/autotest/testdata/fields/jobs.yml | 6 +-- .../fields/model_serving_endpoints.yml | 38 +++++++++---------- .../autotest/testdata/fields/pipelines.yml | 4 +- .../testdata/fields/sql_warehouses.yml | 4 +- 8 files changed, 30 insertions(+), 34 deletions(-) diff --git a/bundle/direct/autotest/output/model_serving_endpoints.txt b/bundle/direct/autotest/output/model_serving_endpoints.txt index 03f45917f21..a1ae0fc2f0d 100644 --- a/bundle/direct/autotest/output/model_serving_endpoints.txt +++ b/bundle/direct/autotest/output/model_serving_endpoints.txt @@ -1,10 +1,8 @@ -email_notifications BASELINE_DRIFT drifts with no config change === summary -OK 32 +OK 33 OK_RECREATE 12 OK_INERT 6 -SUPPRESSED 5 +SUPPRESSED 4 NOT_OBSERVABLE 3 -BASELINE_DRIFT 1 SKIPPED 4 diff --git a/bundle/direct/autotest/testdata/fields/apps.yml b/bundle/direct/autotest/testdata/fields/apps.yml index ee2c7b29116..6161dec7426 100644 --- a/bundle/direct/autotest/testdata/fields/apps.yml +++ b/bundle/direct/autotest/testdata/fields/apps.yml @@ -5,7 +5,7 @@ # every member names a workspace object this suite does not provision: a secret scope, a job, a # warehouse. So the block's fields stay uncovered, which the report says. -# The corpus config declares neither block. config carries the app's command and env; the +# base declares neither block. config carries the app's command and env; the # resources list is a union where an entry names one referenced object. base: name: app-$UNIQUE_NAME diff --git a/bundle/direct/autotest/testdata/fields/clusters.yml b/bundle/direct/autotest/testdata/fields/clusters.yml index 56032ae8073..d3909007032 100644 --- a/bundle/direct/autotest/testdata/fields/clusters.yml +++ b/bundle/direct/autotest/testdata/fields/clusters.yml @@ -42,7 +42,7 @@ skip: init_scripts[*].volumes: an init script member the seeded workspace member excludes # "The field 'worker_node_type_flexibility' cannot be supplied when an instance pool ID is - # provided", and the corpus config provides one. The alternates would also have to name a + # provided", and base provides one. The alternates would also have to name a # second node type, which differs per cloud. worker_node_type_flexibility: incompatible with the instance pool the config sets driver_node_type_flexibility: incompatible with the instance pool the config sets diff --git a/bundle/direct/autotest/testdata/fields/external_locations.yml b/bundle/direct/autotest/testdata/fields/external_locations.yml index 8bcb6c53864..c8e9cba360c 100644 --- a/bundle/direct/autotest/testdata/fields/external_locations.yml +++ b/bundle/direct/autotest/testdata/fields/external_locations.yml @@ -1,7 +1,7 @@ # Value library for external locations. See ../../README.md. # An external location points at cloud storage through a storage credential, which needs -# cloud IAM set up for the workspace. The corpus config names a credential the mock server +# cloud IAM set up for the workspace. base names a credential the mock server # invents, so the whole type is local-only -- the same reason the invariant suite excludes # this config from its cloud run. base: diff --git a/bundle/direct/autotest/testdata/fields/jobs.yml b/bundle/direct/autotest/testdata/fields/jobs.yml index 550f2aff88f..624dae72b2d 100644 --- a/bundle/direct/autotest/testdata/fields/jobs.yml +++ b/bundle/direct/autotest/testdata/fields/jobs.yml @@ -4,7 +4,7 @@ # branch/tag/commit. Seeding one makes its fields reachable, so changing the commit or the # provider is tested rather than skipped. # -# job_clusters, triggers and environments are declared by no config in the corpus, so their +# job_clusters, triggers and environments would otherwise be absent entirely, so their # fields would go untested; seeding one entry each makes them reachable. base: name: test-job-$UNIQUE_NAME @@ -72,7 +72,7 @@ skip: # cluster the seeded task carries ("For each does not need a cluster"). Locally the fake server # accepted it, so ~2200 transitions were exercising configurations no user can write. # - # Covering a member needs a corpus config whose task is of that type. Some exist -- + # Covering a member needs a fixture whose task is of that type. Some exist in the acceptance corpus -- # job_apply_policy_default_values_for_each_task.yml.tmpl is one -- but this suite drives the # simplest config per resource type, so they are not the one picked. Their fields are listed as # uncovered rather than tested against a task that cannot hold them. @@ -129,7 +129,7 @@ skip: job_clusters[*].new_cluster.cluster_log_conf.s3.kms_key: needs a KMS key registered for the workspace tasks[*].notebook_task.warehouse_id: needs a second SQL warehouse to exist # A trigger, a schedule and a continuous block are three arms of one choice, so a schedule - # field cannot move on a job the corpus config gives no schedule. + # field cannot move on a job base gives no schedule. schedule: one of schedule, trigger or continuous, and the config declares none # trigger is a choice between file_arrival, table_update, model and periodic, and base seeds # periodic. Each other member is rejected as a set, in its own words: diff --git a/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml b/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml index b441e97e338..ad6cd44295d 100644 --- a/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml +++ b/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml @@ -1,43 +1,41 @@ # Value library for model serving endpoints. See ../../README.md. # -# telemetry_config needs no base: and no skip. model_serving_endpoint_telemetry.yml.tmpl -# already declares a coherent block -- a catalog, a schema and table names -- so its fields -# expand and are tested there. -# -# The notification lists and tags are declared by no config, so they are seeded here. +# tags is declared by no config, so it is seeded here. # # ai_gateway and the top-level rate_limits are deliberately not seeded: the API rejects both on # an endpoint created without a config block ("Cannot specify ai_gateway when creating endpoints -# without a config"), and the corpus config declares no config block. Covering them needs a +# without a config"), and base declares no config block. Covering them needs a # config that declares a served entity, which needs a model in the workspace -- so the report # lists their fields as uncovered. base: name: test-endpoint-$UNIQUE_NAME - email_notifications: - on_update_failure: - - notify@databricks.invalid - on_update_success: - - notify@databricks.invalid tags: - key: team value: eng skip: + # Not seeded in base on purpose: the create does not apply email_notifications (the plan right + # after it already wants to update them), so declaring one would leave the endpoint drifting for + # its whole life and every other field measured against that. The leaves are reachable anyway -- + # the suite grows the container itself -- so seeding bought a BASELINE_DRIFT row and nothing else. + # The behaviour is recorded where it belongs, in + # acceptance/bundle/resources/model_serving_endpoints/update/email-notifications. + # An AI gateway only applies to an endpoint serving an external model, provisioned # throughput, or a custom or agent model -- "AI Gateway is currently only supported for - # External Models, Provisioned Throughput and Custom or Agent Model". The corpus config - # serves none of those, and the API also refuses the whole block on an endpoint created - # without a config. Covering it needs a config that declares such a served entity. + # External Models, Provisioned Throughput and Custom or Agent Model". base serves none of + # those, and the API also refuses the whole block on an endpoint created without a config. + # Covering it needs a fixture that declares such a served entity, which needs a model in the + # workspace. ai_gateway: needs an endpoint serving an external, provisioned-throughput or custom model # Same shape: "Config needs to be set" -- there is no config block on this endpoint to # attach an auto-capture destination to. - config: needs a config block, which the corpus config for this type does not declare + config: needs a served entity, which needs a model in the workspace - # The corpus config for this type declares no telemetry, and seeding a coherent block here - # is rejected on every later update ("failed to update telemetry config"). The fields are - # covered by model_serving_endpoint_telemetry.yml.tmpl, which declares the whole block -- - # but that config is not the simplest one, so it is not the one this suite picks. - telemetry_config: needs the telemetry corpus config, which is not the simplest one + # Seeding a coherent telemetry block is rejected on every later update ("failed to update + # telemetry config"), and without one the fields inside it are unreachable. So this block is + # not covered here; acceptance/bundle/resources/model_serving_endpoints covers it end to end. + telemetry_config: rejected on update once seeded, and unreachable unless seeded fields: # A recipient is validated by format, so the generic "x" is rejected. Reserved diff --git a/bundle/direct/autotest/testdata/fields/pipelines.yml b/bundle/direct/autotest/testdata/fields/pipelines.yml index ae97530a019..8ed8b82ebab 100644 --- a/bundle/direct/autotest/testdata/fields/pipelines.yml +++ b/bundle/direct/autotest/testdata/fields/pipelines.yml @@ -1,8 +1,8 @@ # Value library for pipelines. See ../../README.md. -# No pipeline config in the corpus declares a clusters block, so its whole spec -- 346 +# base has to declare a clusters block, or its whole spec -- 346 # fields, including the init_scripts union and the tag maps -- would go untested. Values -# that differ per cloud come from the same variables the corpus configs use. +# that differ per cloud come from the same variables an acceptance config would get. base: name: test-pipeline-$UNIQUE_NAME clusters: diff --git a/bundle/direct/autotest/testdata/fields/sql_warehouses.yml b/bundle/direct/autotest/testdata/fields/sql_warehouses.yml index 2ff21acc113..df3a23820eb 100644 --- a/bundle/direct/autotest/testdata/fields/sql_warehouses.yml +++ b/bundle/direct/autotest/testdata/fields/sql_warehouses.yml @@ -14,7 +14,7 @@ skip: # Only accepted on the CUSTOM channel, so the version and the channel name have to move # together; either alone is rejected. channel.dbsql_version: only valid on the CUSTOM channel, so it cannot move alone - # Serverless is only offered on a PRO warehouse, and the corpus config is CLASSIC, so + # Serverless is only offered on a PRO warehouse, and base is CLASSIC, so # this flag cannot be moved on its own -- the pair would have to change together. enable_serverless_compute: only valid on a PRO warehouse, which the config is not @@ -22,7 +22,7 @@ fields: # The backend validates these as ranges rather than accepting any integer: # auto_stop_mins is 0 (never) or at least 10, and max_num_clusters is at least 2 once it # is being changed at all. min_num_clusters cannot exceed max_num_clusters, which the - # corpus config sets to 1, so 1 is its only deployable value -- with absent in the set + # base sets to 1, so 1 is its only deployable value -- with absent in the set # that is still an add and a remove. auto_stop_mins: [10, 20] min_num_clusters: [1] From fb145e3b017ffffd00d78d114f3d1ca6e819b7b8 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 21:39:07 +0200 Subject: [PATCH 106/118] Field catalog: output/ holds only what is committed, logs/ holds the rest output/ carried both the goldens and the full reports, with the latter gitignored by pattern -- so 34 tracked files sat among 91, and telling them apart meant knowing the pattern. Now output/ is exactly the 34 goldens and logs/ is ignored wholesale, which is a rule rather than a pattern to remember. The full report is written straight to logs/.full.txt, and its previous copy is kept as .backup: reading what a change did to a report means diffing the two, and the full report is not in git to diff against. Co-authored-by: Isaac --- bundle/direct/autotest/.gitignore | 7 +++---- bundle/direct/autotest/README.md | 6 +++--- bundle/direct/autotest/report_test.go | 26 +++++++++++++++++++++----- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/bundle/direct/autotest/.gitignore b/bundle/direct/autotest/.gitignore index 6fabbf724b7..5ddbb82fdf4 100644 --- a/bundle/direct/autotest/.gitignore +++ b/bundle/direct/autotest/.gitignore @@ -1,4 +1,3 @@ -# The full report moves whenever a passing row moves; output/.txt is the one that -# records findings and is worth diffing. Both the fake server and a real workspace are held -# to that single golden. -output/*.full.txt +# What a run produces for reading rather than for review: the full report, its cloud counterpart, +# and the previous copy of each. output/ holds the committed goldens and nothing else. +logs/ diff --git a/bundle/direct/autotest/README.md b/bundle/direct/autotest/README.md index 59495a5a529..de4d717c33a 100644 --- a/bundle/direct/autotest/README.md +++ b/bundle/direct/autotest/README.md @@ -83,7 +83,7 @@ the count of every verdict — including the passing ones, which no line names. are what makes a change in *passing* behaviour visible, since a field that starts being recreated instead of updated moves one `OK` to `OK_RECREATE` and nothing else would show it. -`output/.full.txt` holds every row, the not-covered list, and the evidence indented +`logs/.full.txt` holds every row, the not-covered list, and the evidence indented under each finding — the post-deploy plan for drift, the whole API error for a rejection. It is gitignored, because it is full of generated ids and moves whenever any row moves. @@ -225,7 +225,7 @@ own: -run 'TestFields/^apps$/[^/]*/^(budget_policy_id|usage_policy_id)$' The golden comparison fails on a partial run, which is expected: read the rows in -`output/..full.txt` instead. This is how to check a field the local report calls +`logs/..full.txt` instead. This is how to check a field the local report calls `OK` when its value looks like it must name something real, a `*_id` or an ARN or a path. The fake server takes any string, so `OK` there means nothing until a workspace has agreed: both `budget_policy_id` and `usage_policy_id` on apps read `OK` locally and are rejected outright on @@ -234,7 +234,7 @@ a real workspace, which names an account-level policy neither has. On cloud (`CLOUD_ENV` set) the same reports are compared against the same committed goldens: a divergence means the fake server in `libs/testserver` does not model the API faithfully, which is worth failing over. The full report goes to -`output/..full.txt` so a cloud run can be read next to a local one. +`logs/..full.txt` so a cloud run can be read next to a local one. ## Out of scope for now diff --git a/bundle/direct/autotest/report_test.go b/bundle/direct/autotest/report_test.go index d3bd64a41e8..7d69a595452 100644 --- a/bundle/direct/autotest/report_test.go +++ b/bundle/direct/autotest/report_test.go @@ -143,7 +143,7 @@ var benignSuppressions = map[string]bool{ } // isProblem reports whether a result is something a person should look at. The committed -// report lists only these; the .full.txt companion lists everything. +// report lists only these; the logs/.full.txt companion lists everything. func (r result) isProblem() bool { switch r.verdict { case verdictOK, verdictRecreate, verdictNotObservable, verdictSkipped, verdictInertConfirmed, @@ -396,10 +396,10 @@ func (r *report) write(t testutil.TestingT) { fullName := r.resourceType + ".full.txt" if isCloud() { - // Keep both around locally when comparing a cloud run against the fake server. + // Keep both around when comparing a cloud run against the fake server. fullName = r.resourceType + "." + cloudName() + ".full.txt" } - writeReport(t, reportPath(fullName), r.render(false)) + writeReport(t, logPath(fullName), r.render(false)) if testdiff.OverwriteMode { writeReport(t, name, body) @@ -502,19 +502,35 @@ func writeReport(t testutil.TestingT, name, body string) { t.Errorf("creating %s: %s", filepath.Dir(name), err) return } + // The previous run's copy is kept next to the new one: reading what a change did to a report + // means diffing the two, and the full report is not in git to diff against. + if previous, err := os.ReadFile(name); err == nil { + if err := os.WriteFile(name+".backup", previous, 0o644); err != nil { + t.Errorf("backing up %s: %s", name, err) + return + } + } if err := os.WriteFile(name, []byte(body), 0o644); err != nil { t.Errorf("writing %s: %s", name, err) } } -// outputDir holds the committed reports. Kept out of the package directory so the Go -// files stay readable next to two dozen goldens. +// outputDir holds the committed reports and nothing else, so a change to one is visible in a diff. +// Kept out of the package directory so the Go files stay readable next to three dozen goldens. const outputDir = "output" +// logsDir holds what a run produces for a reader rather than for review: the full report, and the +// cloud run's copy of it. Ignored wholesale, so nothing here can be mistaken for a golden. +const logsDir = "logs" + func reportPath(name string) string { return filepath.Join(outputDir, name) } +func logPath(name string) string { + return filepath.Join(logsDir, name) +} + func cloudName() string { switch env := os.Getenv("CLOUD_ENV"); env { case "ucws": From 6cd0d1e0a5d9512373594a8ebb59d8b91c46d1b8 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 21:51:52 +0200 Subject: [PATCH 107/118] Field catalog: a secret needs its own schema, and its references are not testable The cloud run could not create a secret at all: "Cannot create 1 Secret(s) in Schema ... (estimated count: 100, limit: 100)". The limit is per schema and main.default on a shared workspace is full of other runs' leftovers, the oldest from three weeks ago. The fixture now creates its own schema as a dep, which is empty by construction. That exposed the next layer: catalog_name and schema_name name objects that have to exist, so the generic x and y are refused ("Catalog 'x' does not exist", "Schema 'main.x' does not exist"). The fake server takes any string, so those four rows had only ever measured the fake. Skipped with the reason. secrets now matches on cloud. Co-authored-by: Isaac --- bundle/direct/autotest/output/secrets.txt | 6 ++---- .../direct/autotest/output/secrets.txt.backup | 18 ++++++++++++++++++ .../autotest/testdata/fields/secrets.yml | 17 ++++++++++++++++- 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 bundle/direct/autotest/output/secrets.txt.backup diff --git a/bundle/direct/autotest/output/secrets.txt b/bundle/direct/autotest/output/secrets.txt index 909c5ede7ad..6ad75fbeacb 100644 --- a/bundle/direct/autotest/output/secrets.txt +++ b/bundle/direct/autotest/output/secrets.txt @@ -6,12 +6,10 @@ owner x absent SUPPRESSED s owner x y SUPPRESSED spec:input_only owner y absent SUPPRESSED spec:input_only owner y x SUPPRESSED spec:input_only -value BASELINE_DRIFT drifts with no config change === summary OK 4 -OK_RECREATE 6 +OK_RECREATE 2 SUPPRESSED 6 -BASELINE_DRIFT 1 UPDATE_IGNORED 2 -SKIPPED 8 +SKIPPED 10 diff --git a/bundle/direct/autotest/output/secrets.txt.backup b/bundle/direct/autotest/output/secrets.txt.backup new file mode 100644 index 00000000000..5f108d1bb24 --- /dev/null +++ b/bundle/direct/autotest/output/secrets.txt.backup @@ -0,0 +1,18 @@ +comment x absent UPDATE_IGNORED +comment y absent UPDATE_IGNORED +owner absent x SUPPRESSED spec:input_only +owner absent y SUPPRESSED spec:input_only +owner x absent SUPPRESSED spec:input_only +owner x y SUPPRESSED spec:input_only +owner y absent SUPPRESSED spec:input_only +owner y x SUPPRESSED spec:input_only +schema_name x y COLLATERAL_DRIFT value +schema_name y x COLLATERAL_DRIFT value + +=== summary +OK 4 +OK_RECREATE 4 +SUPPRESSED 6 +UPDATE_IGNORED 2 +COLLATERAL_DRIFT 2 +SKIPPED 8 diff --git a/bundle/direct/autotest/testdata/fields/secrets.yml b/bundle/direct/autotest/testdata/fields/secrets.yml index 42aec0796d9..d1f52786af4 100644 --- a/bundle/direct/autotest/testdata/fields/secrets.yml +++ b/bundle/direct/autotest/testdata/fields/secrets.yml @@ -1,11 +1,19 @@ # Value library for secrets. See ../../README.md. +# The secret goes in a schema this fixture creates, not in main.default: the limit is 100 secrets per +# schema, and a shared workspace's default schema fills up with other runs' leftovers -- "Cannot +# create 1 Secret(s) in Schema ... (estimated count: 100, limit: 100)". A fresh schema is empty. +deps: + schemas: + catalog_name: main + name: test-secret-schema-$UNIQUE_NAME + variables: secret_value: seeded-value base: catalog_name: main - schema_name: default + schema_name: ${resources.schemas.schemas.name} name: test-secret-$UNIQUE_NAME value: ${var.secret_value} @@ -14,3 +22,10 @@ skip: # must be a variable reference". No value this suite could move the field to is a legal bundle, # so the only reachable shape is the reference base declares. value: the config format requires a variable reference, which no literal value is + + # Both name objects that have to exist: "Catalog 'x' does not exist", "Schema 'main.x' does not + # exist". The fake server takes any string, so these rows only ever measured the fake. A second + # catalog and schema would make them testable; the fixture creates one schema, and moving the + # secret between two is a different test than moving a field. + catalog_name: names a catalog that has to exist, and the suite provisions one + schema_name: names the schema the fixture creates, and there is no second one to move to From 5dc6df97fd4e7ee70caec28de63d688906ee52b6 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 21:58:54 +0200 Subject: [PATCH 108/118] Field catalog: vector search endpoints -- a policy that cannot exist, and a clear the API drops budget_policy_id names an account-level policy ("Failed to validate budget policy x"), which a test workspace does not have and cannot be given from here. Skipped, as on apps. target_qps is omitempty, so a config that clears it drops it from the body and the backend keeps the value it had. The fake server was assigning the zero value instead, which made the field look freely clearable; two rows now record the clear being ignored, as the cloud run does. Setting and changing the value still works on both. Co-authored-by: Isaac --- .../autotest/output/vector_search_endpoints.txt | 7 +++++-- .../output/vector_search_endpoints.txt.backup | 5 +++++ .../autotest/output/vector_search_indexes.txt.backup | 11 +++++++++++ .../testdata/fields/vector_search_endpoints.yml | 5 +++++ libs/testserver/vector_search_endpoints.go | 7 ++++++- 5 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 bundle/direct/autotest/output/vector_search_endpoints.txt.backup create mode 100644 bundle/direct/autotest/output/vector_search_indexes.txt.backup diff --git a/bundle/direct/autotest/output/vector_search_endpoints.txt b/bundle/direct/autotest/output/vector_search_endpoints.txt index 55398a08452..4efc342c607 100644 --- a/bundle/direct/autotest/output/vector_search_endpoints.txt +++ b/bundle/direct/autotest/output/vector_search_endpoints.txt @@ -1,5 +1,8 @@ +target_qps 1 absent UPDATE_IGNORED +target_qps 2 absent UPDATE_IGNORED === summary -OK 18 +OK 10 OK_RECREATE 4 -SKIPPED 1 +UPDATE_IGNORED 2 +SKIPPED 2 diff --git a/bundle/direct/autotest/output/vector_search_endpoints.txt.backup b/bundle/direct/autotest/output/vector_search_endpoints.txt.backup new file mode 100644 index 00000000000..9465f7bf91b --- /dev/null +++ b/bundle/direct/autotest/output/vector_search_endpoints.txt.backup @@ -0,0 +1,5 @@ + +=== summary +OK 12 +OK_RECREATE 4 +SKIPPED 2 diff --git a/bundle/direct/autotest/output/vector_search_indexes.txt.backup b/bundle/direct/autotest/output/vector_search_indexes.txt.backup new file mode 100644 index 00000000000..4867308ae1c --- /dev/null +++ b/bundle/direct/autotest/output/vector_search_indexes.txt.backup @@ -0,0 +1,11 @@ +endpoint_name x y BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint x not found (404 RESOURCE_DOES_NOT_EXIST) +endpoint_name y x BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint y not found (404 RESOURCE_DOES_NOT_EXIST) +name x-UNIQUE y-UNIQUE BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Invalid index name. Must specify the ...ores are allowed. (400 INVALID_PARAMETER_VALUE) +name y-UNIQUE x-UNIQUE BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Invalid index name. Must specify the ...ores are allowed. (400 INVALID_PARAMETER_VALUE) + +=== summary +OK_RECREATE 69 +SUPPRESSED 4 +NOT_OBSERVABLE 3 +SKIPPED 1 +BASE_ERROR 4 diff --git a/bundle/direct/autotest/testdata/fields/vector_search_endpoints.yml b/bundle/direct/autotest/testdata/fields/vector_search_endpoints.yml index 9e1e8a1a258..743a37d56c0 100644 --- a/bundle/direct/autotest/testdata/fields/vector_search_endpoints.yml +++ b/bundle/direct/autotest/testdata/fields/vector_search_endpoints.yml @@ -4,3 +4,8 @@ base: # Endpoint names must be under 50 characters. name: test-vse-$UNIQUE_NAME endpoint_type: STANDARD + +skip: + # An account-level policy, as with apps: "Failed to validate budget policy x." A test workspace has + # none to reference, and creating one is an account-level operation. + budget_policy_id: needs an account-level budget policy to exist diff --git a/libs/testserver/vector_search_endpoints.go b/libs/testserver/vector_search_endpoints.go index c051d599ccc..ae207c012ca 100644 --- a/libs/testserver/vector_search_endpoints.go +++ b/libs/testserver/vector_search_endpoints.go @@ -106,7 +106,12 @@ func (s *FakeWorkspace) VectorSearchEndpointUpdate(req Request, endpointName str if endpoint.ScalingInfo == nil { endpoint.ScalingInfo = &vectorsearch.EndpointScalingInfo{} } - endpoint.ScalingInfo.RequestedTargetQps = patchReq.TargetQps + // target_qps is omitempty, so a config that clears it drops it from the body and the backend + // keeps the value it had (aws, 2026-09: the clear is accepted and has no effect). Assigning the + // zero value here instead made the field look freely clearable. + if patchReq.TargetQps != 0 { + endpoint.ScalingInfo.RequestedTargetQps = patchReq.TargetQps + } endpoint.LastUpdatedTimestamp = nowMilli() endpoint.LastUpdatedUser = s.CurrentUser().UserName From 88fdc9929b02ead4cebf44d643f6e601a0825905 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 23:22:52 +0200 Subject: [PATCH 109/118] Field catalog: settle a job run on the first poll, and 17x the whole suite You were right that a poller was the cost. The fake server returns RUNNING on the first poll of a run and the terminal state on the second, and the engine's waiter uses the SDK's backoff -- attempt times a second, plus jitter -- so every run costs about a second of sleeping. A job run is immutable, so 109 of its 131 observations recreate it: roughly 110 runs, roughly 110 seconds. job_runs was 183s of the suite's 185s. SettleAsyncImmediately already existed for exactly this and job runs did not consult it. Now they do: the run is terminated and reported terminal on the first poll. job_runs 183s -> 2s, the whole suite 185s -> 11s. Verdicts are unchanged, which is the point -- the wait was pure latency. The acceptance suite leaves the simulation on, so the waiter and its state logging stay covered there. My first attempt at this made it slower, 183s -> 261s, because it skipped the whole block including the call that terminates the run: the run then stayed RUNNING forever and every create waited out the cap instead of one backoff. Measured both ways before believing either. Also adds a header to each committed report, since it is read in a diff with nothing around it to say what the columns are or that a bad outcome here is recorded rather than failed. No counts, times or dates in it: a header that moved when something unrelated changed would be noise in every review. Co-authored-by: Isaac --- bundle/direct/autotest/fields_test.go | 3 +- bundle/direct/autotest/output/alerts.txt | 10 ++ .../direct/autotest/output/alerts.txt.backup | 5 + bundle/direct/autotest/output/apps.txt | 10 ++ bundle/direct/autotest/output/apps.txt.backup | 105 ++++++++++++++++++ bundle/direct/autotest/output/catalogs.txt | 10 ++ .../autotest/output/catalogs.txt.backup | 13 +++ .../autotest/output/cluster_policies.txt | 10 ++ .../output/cluster_policies.txt.backup | 20 ++++ bundle/direct/autotest/output/clusters.txt | 10 ++ .../autotest/output/clusters.txt.backup | 6 + bundle/direct/autotest/output/dashboards.txt | 10 ++ .../autotest/output/dashboards.txt.backup | 12 ++ .../autotest/output/database_catalogs.txt | 10 ++ .../output/database_catalogs.txt.backup | 9 ++ .../autotest/output/database_instances.txt | 10 ++ .../output/database_instances.txt.backup | 60 ++++++++++ bundle/direct/autotest/output/experiments.txt | 10 ++ .../autotest/output/experiments.txt.backup | 6 + .../autotest/output/external_locations.txt | 10 ++ .../output/external_locations.txt.backup | 7 ++ .../direct/autotest/output/genie_spaces.txt | 10 ++ .../autotest/output/genie_spaces.txt.backup | 10 ++ .../direct/autotest/output/instance_pools.txt | 10 ++ .../autotest/output/instance_pools.txt.backup | 10 ++ bundle/direct/autotest/output/job_runs.txt | 10 ++ .../autotest/output/job_runs.txt.backup | 6 + bundle/direct/autotest/output/jobs.txt | 10 ++ bundle/direct/autotest/output/jobs.txt.backup | 14 +++ .../output/model_serving_endpoints.txt | 10 ++ .../output/model_serving_endpoints.txt.backup | 8 ++ bundle/direct/autotest/output/models.txt | 10 ++ .../direct/autotest/output/models.txt.backup | 9 ++ bundle/direct/autotest/output/pipelines.txt | 10 ++ .../autotest/output/pipelines.txt.backup | 19 ++++ .../autotest/output/postgres_branches.txt | 10 ++ .../output/postgres_branches.txt.backup | 15 +++ .../autotest/output/postgres_catalogs.txt | 10 ++ .../output/postgres_catalogs.txt.backup | 6 + .../autotest/output/postgres_databases.txt | 10 ++ .../output/postgres_databases.txt.backup | 9 ++ .../autotest/output/postgres_endpoints.txt | 10 ++ .../output/postgres_endpoints.txt.backup | 27 +++++ .../autotest/output/postgres_projects.txt | 10 ++ .../output/postgres_projects.txt.backup | 35 ++++++ .../direct/autotest/output/postgres_roles.txt | 10 ++ .../autotest/output/postgres_roles.txt.backup | 11 ++ .../output/postgres_synced_tables.txt | 10 ++ .../output/postgres_synced_tables.txt.backup | 6 + .../autotest/output/quality_monitors.txt | 10 ++ .../output/quality_monitors.txt.backup | 12 ++ .../autotest/output/registered_models.txt | 10 ++ .../output/registered_models.txt.backup | 17 +++ bundle/direct/autotest/output/schemas.txt | 10 ++ .../direct/autotest/output/schemas.txt.backup | 14 +++ .../direct/autotest/output/secret_scopes.txt | 10 ++ .../autotest/output/secret_scopes.txt.backup | 7 ++ bundle/direct/autotest/output/secrets.txt | 10 ++ .../direct/autotest/output/secrets.txt.backup | 7 +- .../direct/autotest/output/sql_warehouses.txt | 10 ++ .../autotest/output/sql_warehouses.txt.backup | 24 ++++ .../output/synced_database_tables.txt | 10 ++ .../output/synced_database_tables.txt.backup | 14 +++ .../output/vector_search_endpoints.txt | 10 ++ .../output/vector_search_endpoints.txt.backup | 5 +- .../autotest/output/vector_search_indexes.txt | 10 ++ bundle/direct/autotest/output/volumes.txt | 10 ++ .../direct/autotest/output/volumes.txt.backup | 4 + bundle/direct/autotest/report_test.go | 35 ++++++ libs/testserver/jobs.go | 10 +- 70 files changed, 912 insertions(+), 8 deletions(-) create mode 100644 bundle/direct/autotest/output/alerts.txt.backup create mode 100644 bundle/direct/autotest/output/apps.txt.backup create mode 100644 bundle/direct/autotest/output/catalogs.txt.backup create mode 100644 bundle/direct/autotest/output/cluster_policies.txt.backup create mode 100644 bundle/direct/autotest/output/clusters.txt.backup create mode 100644 bundle/direct/autotest/output/dashboards.txt.backup create mode 100644 bundle/direct/autotest/output/database_catalogs.txt.backup create mode 100644 bundle/direct/autotest/output/database_instances.txt.backup create mode 100644 bundle/direct/autotest/output/experiments.txt.backup create mode 100644 bundle/direct/autotest/output/external_locations.txt.backup create mode 100644 bundle/direct/autotest/output/genie_spaces.txt.backup create mode 100644 bundle/direct/autotest/output/instance_pools.txt.backup create mode 100644 bundle/direct/autotest/output/job_runs.txt.backup create mode 100644 bundle/direct/autotest/output/jobs.txt.backup create mode 100644 bundle/direct/autotest/output/model_serving_endpoints.txt.backup create mode 100644 bundle/direct/autotest/output/models.txt.backup create mode 100644 bundle/direct/autotest/output/pipelines.txt.backup create mode 100644 bundle/direct/autotest/output/postgres_branches.txt.backup create mode 100644 bundle/direct/autotest/output/postgres_catalogs.txt.backup create mode 100644 bundle/direct/autotest/output/postgres_databases.txt.backup create mode 100644 bundle/direct/autotest/output/postgres_endpoints.txt.backup create mode 100644 bundle/direct/autotest/output/postgres_projects.txt.backup create mode 100644 bundle/direct/autotest/output/postgres_roles.txt.backup create mode 100644 bundle/direct/autotest/output/postgres_synced_tables.txt.backup create mode 100644 bundle/direct/autotest/output/quality_monitors.txt.backup create mode 100644 bundle/direct/autotest/output/registered_models.txt.backup create mode 100644 bundle/direct/autotest/output/schemas.txt.backup create mode 100644 bundle/direct/autotest/output/secret_scopes.txt.backup create mode 100644 bundle/direct/autotest/output/sql_warehouses.txt.backup create mode 100644 bundle/direct/autotest/output/synced_database_tables.txt.backup create mode 100644 bundle/direct/autotest/output/volumes.txt.backup diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index 779ef5ea148..33fd7f3e7ae 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -49,6 +49,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct/dresources" @@ -108,7 +109,7 @@ func TestFields(t *testing.T) { // Outlives the field-level subtests that rebuild harnesses. ctx := t.Context() - rep := &report{resourceType: resourceType} //exhaustruct:ignore + rep := &report{resourceType: resourceType, started: time.Now()} //exhaustruct:ignore runType(t, ctx, client, user, resourceType, fv, rep, runSeed, sampleFields) rep.write(t) }) diff --git a/bundle/direct/autotest/output/alerts.txt b/bundle/direct/autotest/output/alerts.txt index d99a05171ff..3f4391b2425 100644 --- a/bundle/direct/autotest/output/alerts.txt +++ b/bundle/direct/autotest/output/alerts.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary OK 26 diff --git a/bundle/direct/autotest/output/alerts.txt.backup b/bundle/direct/autotest/output/alerts.txt.backup new file mode 100644 index 00000000000..d99a05171ff --- /dev/null +++ b/bundle/direct/autotest/output/alerts.txt.backup @@ -0,0 +1,5 @@ + +=== summary +OK 26 +NOT_OBSERVABLE 6 +SKIPPED 17 diff --git a/bundle/direct/autotest/output/apps.txt b/bundle/direct/autotest/output/apps.txt index 4619820810c..dc2729c46c8 100644 --- a/bundle/direct/autotest/output/apps.txt +++ b/bundle/direct/autotest/output/apps.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + config.command absent len1 START_NOT_REACHED no active deployment config.command absent len2 START_NOT_REACHED no active deployment config.command len1 absent START_NOT_REACHED no active deployment diff --git a/bundle/direct/autotest/output/apps.txt.backup b/bundle/direct/autotest/output/apps.txt.backup new file mode 100644 index 00000000000..4619820810c --- /dev/null +++ b/bundle/direct/autotest/output/apps.txt.backup @@ -0,0 +1,105 @@ +config.command absent len1 START_NOT_REACHED no active deployment +config.command absent len2 START_NOT_REACHED no active deployment +config.command len1 absent START_NOT_REACHED no active deployment +config.command len1 len2 START_NOT_REACHED no active deployment +config.command len2 absent START_NOT_REACHED no active deployment +config.command len2 len1 START_NOT_REACHED no active deployment +config.command[0] absent x UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it +config.command[0] absent y UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it +config.command[0] x absent START_NOT_REACHED no active deployment +config.command[0] x y START_NOT_REACHED no active deployment +config.command[0] y absent START_NOT_REACHED no active deployment +config.command[0] y x START_NOT_REACHED no active deployment +config.command[1] absent x START_NOT_REACHED no active deployment +config.command[1] absent y START_NOT_REACHED no active deployment +config.command[1] x absent START_NOT_REACHED no active deployment +config.command[1] x y START_NOT_REACHED no active deployment +config.command[1] y absent START_NOT_REACHED no active deployment +config.command[1] y x START_NOT_REACHED no active deployment +config.env absent len0 START_NOT_REACHED no active deployment +config.env absent len1 START_NOT_REACHED no active deployment +config.env len0 absent START_NOT_REACHED no active deployment +config.env len0 len1 START_NOT_REACHED no active deployment +config.env len1 absent START_NOT_REACHED no active deployment +config.env len1 len0 START_NOT_REACHED no active deployment +config.env[0].name x y START_NOT_REACHED no active deployment +config.env[0].name y x START_NOT_REACHED no active deployment +config.env[0].value absent x START_NOT_REACHED no active deployment +config.env[0].value absent y START_NOT_REACHED no active deployment +config.env[0].value x absent START_NOT_REACHED no active deployment +config.env[0].value x y START_NOT_REACHED no active deployment +config.env[0].value y absent START_NOT_REACHED no active deployment +config.env[0].value y x START_NOT_REACHED no active deployment +config.env[0].value_from absent x START_NOT_REACHED no active deployment +config.env[0].value_from absent y START_NOT_REACHED no active deployment +config.env[0].value_from x absent START_NOT_REACHED no active deployment +config.env[0].value_from x y START_NOT_REACHED no active deployment +config.env[0].value_from y absent START_NOT_REACHED no active deployment +config.env[0].value_from y x START_NOT_REACHED no active deployment +description x absent UPDATE_IGNORED +description y absent UPDATE_IGNORED +git_repository.auto_deploy true absent UPDATE_IGNORED +git_repository.caller_credential_id 1 absent UPDATE_IGNORED +git_repository.caller_credential_id 2 absent UPDATE_IGNORED +git_source.branch absent x SUPPRESSED no active deployment +git_source.branch absent y SUPPRESSED no active deployment +git_source.branch x absent START_NOT_REACHED no active deployment +git_source.branch x y START_NOT_REACHED no active deployment +git_source.branch y absent START_NOT_REACHED no active deployment +git_source.branch y x START_NOT_REACHED no active deployment +git_source.commit absent x SUPPRESSED no active deployment +git_source.commit absent y SUPPRESSED no active deployment +git_source.commit x absent START_NOT_REACHED no active deployment +git_source.commit x y START_NOT_REACHED no active deployment +git_source.commit y absent START_NOT_REACHED no active deployment +git_source.commit y x START_NOT_REACHED no active deployment +git_source.git_repository.auto_deploy absent false SUPPRESSED no active deployment +git_source.git_repository.auto_deploy absent true SUPPRESSED no active deployment +git_source.git_repository.auto_deploy false absent START_NOT_REACHED no active deployment +git_source.git_repository.auto_deploy false true START_NOT_REACHED no active deployment +git_source.git_repository.auto_deploy true absent START_NOT_REACHED no active deployment +git_source.git_repository.auto_deploy true false START_NOT_REACHED no active deployment +git_source.git_repository.caller_credential_id 1 2 START_NOT_REACHED no active deployment +git_source.git_repository.caller_credential_id 1 absent START_NOT_REACHED no active deployment +git_source.git_repository.caller_credential_id 2 1 START_NOT_REACHED no active deployment +git_source.git_repository.caller_credential_id 2 absent START_NOT_REACHED no active deployment +git_source.git_repository.caller_credential_id absent 1 SUPPRESSED no active deployment +git_source.git_repository.caller_credential_id absent 2 SUPPRESSED no active deployment +git_source.resolved_commit absent x SUPPRESSED no active deployment +git_source.resolved_commit absent y SUPPRESSED no active deployment +git_source.resolved_commit x absent START_NOT_REACHED no active deployment +git_source.resolved_commit x y START_NOT_REACHED no active deployment +git_source.resolved_commit y absent START_NOT_REACHED no active deployment +git_source.resolved_commit y x START_NOT_REACHED no active deployment +git_source.source_code_path absent x SUPPRESSED no active deployment +git_source.source_code_path absent y SUPPRESSED no active deployment +git_source.source_code_path x absent START_NOT_REACHED no active deployment +git_source.source_code_path x y START_NOT_REACHED no active deployment +git_source.source_code_path y absent START_NOT_REACHED no active deployment +git_source.source_code_path y x START_NOT_REACHED no active deployment +git_source.tag absent x SUPPRESSED no active deployment +git_source.tag absent y SUPPRESSED no active deployment +git_source.tag x absent START_NOT_REACHED no active deployment +git_source.tag x y START_NOT_REACHED no active deployment +git_source.tag y absent START_NOT_REACHED no active deployment +git_source.tag y x START_NOT_REACHED no active deployment +source_code_path absent x-UNIQUE SUPPRESSED no active deployment +source_code_path absent y-UNIQUE SUPPRESSED no active deployment +source_code_path x-UNIQUE absent START_NOT_REACHED no active deployment +source_code_path x-UNIQUE y-UNIQUE START_NOT_REACHED no active deployment +source_code_path y-UNIQUE absent START_NOT_REACHED no active deployment +source_code_path y-UNIQUE x-UNIQUE START_NOT_REACHED no active deployment +user_api_scopes len1 absent UPDATE_IGNORED +user_api_scopes len1 len0 UPDATE_IGNORED +user_api_scopes len2 absent UPDATE_IGNORED +user_api_scopes len2 len0 UPDATE_IGNORED + +=== summary +OK 35 +OK_RECREATE 2 +SUPPRESSED 23 +NOT_OBSERVABLE 3 +UPDATE_IGNORED 9 +UNSETTABLE 2 +SKIPPED 90 +START_NOT_REACHED 68 diff --git a/bundle/direct/autotest/output/catalogs.txt b/bundle/direct/autotest/output/catalogs.txt index ff32ebc7ef7..df334748455 100644 --- a/bundle/direct/autotest/output/catalogs.txt +++ b/bundle/direct/autotest/output/catalogs.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours 720 absent UPDATE_IGNORED properties keys1 absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/catalogs.txt.backup b/bundle/direct/autotest/output/catalogs.txt.backup new file mode 100644 index 00000000000..ff32ebc7ef7 --- /dev/null +++ b/bundle/direct/autotest/output/catalogs.txt.backup @@ -0,0 +1,13 @@ +custom_max_retention_hours 168 absent UPDATE_IGNORED +custom_max_retention_hours 720 absent UPDATE_IGNORED +properties keys1 absent UPDATE_IGNORED +properties keys1 keys0 UPDATE_IGNORED +properties['team'] x absent UPDATE_IGNORED +properties['team'] y absent UPDATE_IGNORED + +=== summary +OK 18 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +UPDATE_IGNORED 6 +SKIPPED 8 diff --git a/bundle/direct/autotest/output/cluster_policies.txt b/bundle/direct/autotest/output/cluster_policies.txt index 3436746c9ca..62baeca361d 100644 --- a/bundle/direct/autotest/output/cluster_policies.txt +++ b/bundle/direct/autotest/output/cluster_policies.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + definition absent v1 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) definition absent v2 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) definition v1 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/cluster_policies.txt.backup b/bundle/direct/autotest/output/cluster_policies.txt.backup new file mode 100644 index 00000000000..3436746c9ca --- /dev/null +++ b/bundle/direct/autotest/output/cluster_policies.txt.backup @@ -0,0 +1,20 @@ +definition absent v1 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition absent v2 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition v1 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +definition v2 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) +libraries[0].jar absent dbfs:/FileStore/a.jar BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) +libraries[0].jar absent dbfs:/FileStore/b.jar BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) +libraries[0].jar dbfs:/FileStore/a.jar absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) +libraries[0].jar dbfs:/FileStore/b.jar absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) +name absent x-UNIQUE BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) +name absent y-UNIQUE BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) +name x-UNIQUE absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) +name y-UNIQUE absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) + +=== summary +OK 22 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +BACKEND_ERROR 6 +SKIPPED 10 +BASE_ERROR 6 diff --git a/bundle/direct/autotest/output/clusters.txt b/bundle/direct/autotest/output/clusters.txt index c6c93ce793c..2f3ef8044cd 100644 --- a/bundle/direct/autotest/output/clusters.txt +++ b/bundle/direct/autotest/output/clusters.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary OK 383 diff --git a/bundle/direct/autotest/output/clusters.txt.backup b/bundle/direct/autotest/output/clusters.txt.backup new file mode 100644 index 00000000000..c6c93ce793c --- /dev/null +++ b/bundle/direct/autotest/output/clusters.txt.backup @@ -0,0 +1,6 @@ + +=== summary +OK 383 +SUPPRESSED 20 +NOT_OBSERVABLE 9 +SKIPPED 10 diff --git a/bundle/direct/autotest/output/dashboards.txt b/bundle/direct/autotest/output/dashboards.txt index c1d370a198e..7c041108d98 100644 --- a/bundle/direct/autotest/output/dashboards.txt +++ b/bundle/direct/autotest/output/dashboards.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + display_name absent x-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) display_name absent y-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) display_name x-UNIQUE absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/dashboards.txt.backup b/bundle/direct/autotest/output/dashboards.txt.backup new file mode 100644 index 00000000000..c1d370a198e --- /dev/null +++ b/bundle/direct/autotest/output/dashboards.txt.backup @@ -0,0 +1,12 @@ +display_name absent x-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) +display_name absent y-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) +display_name x-UNIQUE absent UPDATE_IGNORED +display_name y-UNIQUE absent UPDATE_IGNORED + +=== summary +OK 6 +SUPPRESSED 2 +NOT_OBSERVABLE 6 +UPDATE_IGNORED 2 +SKIPPED 11 +START_NOT_REACHED 2 diff --git a/bundle/direct/autotest/output/database_catalogs.txt b/bundle/direct/autotest/output/database_catalogs.txt index 02442d614ce..b40b27a0b9b 100644 --- a/bundle/direct/autotest/output/database_catalogs.txt +++ b/bundle/direct/autotest/output/database_catalogs.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + database_instance_name x y BASE_ERROR cannot recreate resources.database_catalogs.foo: database instance with name 'x' not found (404 UNKNOWN) database_instance_name y x BASE_ERROR cannot recreate resources.database_catalogs.foo: database instance with name 'y' not found (404 UNKNOWN) diff --git a/bundle/direct/autotest/output/database_catalogs.txt.backup b/bundle/direct/autotest/output/database_catalogs.txt.backup new file mode 100644 index 00000000000..02442d614ce --- /dev/null +++ b/bundle/direct/autotest/output/database_catalogs.txt.backup @@ -0,0 +1,9 @@ +database_instance_name x y BASE_ERROR cannot recreate resources.database_catalogs.foo: database instance with name 'x' not found (404 UNKNOWN) +database_instance_name y x BASE_ERROR cannot recreate resources.database_catalogs.foo: database instance with name 'y' not found (404 UNKNOWN) + +=== summary +OK_RECREATE 8 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +SKIPPED 2 +BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/database_instances.txt b/bundle/direct/autotest/output/database_instances.txt index 00bd3a9c8cb..b072a60c536 100644 --- a/bundle/direct/autotest/output/database_instances.txt +++ b/bundle/direct/autotest/output/database_instances.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + capacity absent x UPDATE_IGNORED capacity absent y UPDATE_IGNORED capacity x y POST_DEPLOY_DRIFT capacity diff --git a/bundle/direct/autotest/output/database_instances.txt.backup b/bundle/direct/autotest/output/database_instances.txt.backup new file mode 100644 index 00000000000..00bd3a9c8cb --- /dev/null +++ b/bundle/direct/autotest/output/database_instances.txt.backup @@ -0,0 +1,60 @@ +capacity absent x UPDATE_IGNORED +capacity absent y UPDATE_IGNORED +capacity x y POST_DEPLOY_DRIFT capacity +capacity y x POST_DEPLOY_DRIFT capacity +custom_tags absent len1 COLLATERAL_DRIFT capacity +custom_tags len0 len1 COLLATERAL_DRIFT capacity +custom_tags len1 absent COLLATERAL_DRIFT capacity +custom_tags len1 len0 COLLATERAL_DRIFT capacity +custom_tags[0].key absent x COLLATERAL_DRIFT capacity +custom_tags[0].key absent y COLLATERAL_DRIFT capacity +custom_tags[0].key x absent COLLATERAL_DRIFT capacity +custom_tags[0].key x y COLLATERAL_DRIFT capacity +custom_tags[0].key y absent COLLATERAL_DRIFT capacity +custom_tags[0].key y x COLLATERAL_DRIFT capacity +custom_tags[0].value absent x COLLATERAL_DRIFT capacity +custom_tags[0].value absent y COLLATERAL_DRIFT capacity +custom_tags[0].value x absent COLLATERAL_DRIFT capacity +custom_tags[0].value x y COLLATERAL_DRIFT capacity +custom_tags[0].value y absent COLLATERAL_DRIFT capacity +custom_tags[0].value y x COLLATERAL_DRIFT capacity +enable_pg_native_login absent true COLLATERAL_DRIFT capacity +enable_pg_native_login false true COLLATERAL_DRIFT capacity +enable_pg_native_login true absent COLLATERAL_DRIFT capacity +enable_pg_native_login true false COLLATERAL_DRIFT capacity +enable_readable_secondaries absent true COLLATERAL_DRIFT capacity +enable_readable_secondaries false true COLLATERAL_DRIFT capacity +enable_readable_secondaries true absent COLLATERAL_DRIFT capacity +enable_readable_secondaries true false COLLATERAL_DRIFT capacity +node_count 1 2 COLLATERAL_DRIFT capacity +node_count 1 absent COLLATERAL_DRIFT capacity +node_count 2 1 COLLATERAL_DRIFT capacity +node_count 2 absent COLLATERAL_DRIFT capacity +node_count absent 1 COLLATERAL_DRIFT capacity +node_count absent 2 COLLATERAL_DRIFT capacity +retention_window_in_days 1 2 COLLATERAL_DRIFT capacity +retention_window_in_days 1 absent COLLATERAL_DRIFT capacity +retention_window_in_days 2 1 COLLATERAL_DRIFT capacity +retention_window_in_days 2 absent COLLATERAL_DRIFT capacity +retention_window_in_days absent 1 COLLATERAL_DRIFT capacity +retention_window_in_days absent 2 COLLATERAL_DRIFT capacity +stopped absent true COLLATERAL_DRIFT capacity +stopped false true COLLATERAL_DRIFT capacity +stopped true absent COLLATERAL_DRIFT capacity +stopped true false COLLATERAL_DRIFT capacity +usage_policy_id absent x COLLATERAL_DRIFT capacity +usage_policy_id absent y COLLATERAL_DRIFT capacity +usage_policy_id x absent COLLATERAL_DRIFT capacity +usage_policy_id x y COLLATERAL_DRIFT capacity +usage_policy_id y absent COLLATERAL_DRIFT capacity +usage_policy_id y x COLLATERAL_DRIFT capacity + +=== summary +OK 2 +OK_RECREATE 32 +SUPPRESSED 6 +NOT_OBSERVABLE 2 +UPDATE_IGNORED 2 +POST_DEPLOY_DRIFT 2 +COLLATERAL_DRIFT 46 +SKIPPED 24 diff --git a/bundle/direct/autotest/output/experiments.txt b/bundle/direct/autotest/output/experiments.txt index a1b5273daac..0d42a8773df 100644 --- a/bundle/direct/autotest/output/experiments.txt +++ b/bundle/direct/autotest/output/experiments.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary OK 2 diff --git a/bundle/direct/autotest/output/experiments.txt.backup b/bundle/direct/autotest/output/experiments.txt.backup new file mode 100644 index 00000000000..a1b5273daac --- /dev/null +++ b/bundle/direct/autotest/output/experiments.txt.backup @@ -0,0 +1,6 @@ + +=== summary +OK 2 +OK_RECREATE 12 +OK_INERT 18 +SKIPPED 2 diff --git a/bundle/direct/autotest/output/external_locations.txt b/bundle/direct/autotest/output/external_locations.txt index 59c5ff2ea14..99e0d260f16 100644 --- a/bundle/direct/autotest/output/external_locations.txt +++ b/bundle/direct/autotest/output/external_locations.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary OK 26 diff --git a/bundle/direct/autotest/output/external_locations.txt.backup b/bundle/direct/autotest/output/external_locations.txt.backup new file mode 100644 index 00000000000..59c5ff2ea14 --- /dev/null +++ b/bundle/direct/autotest/output/external_locations.txt.backup @@ -0,0 +1,7 @@ + +=== summary +OK 26 +OK_RECREATE 110 +SUPPRESSED 5 +NOT_OBSERVABLE 3 +SKIPPED 18 diff --git a/bundle/direct/autotest/output/genie_spaces.txt b/bundle/direct/autotest/output/genie_spaces.txt index 29fdc33e9f8..d0ace3bf0f8 100644 --- a/bundle/direct/autotest/output/genie_spaces.txt +++ b/bundle/direct/autotest/output/genie_spaces.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + description x absent UPDATE_IGNORED description y absent UPDATE_IGNORED title x-UNIQUE absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/genie_spaces.txt.backup b/bundle/direct/autotest/output/genie_spaces.txt.backup new file mode 100644 index 00000000000..29fdc33e9f8 --- /dev/null +++ b/bundle/direct/autotest/output/genie_spaces.txt.backup @@ -0,0 +1,10 @@ +description x absent UPDATE_IGNORED +description y absent UPDATE_IGNORED +title x-UNIQUE absent UPDATE_IGNORED +title y-UNIQUE absent UPDATE_IGNORED + +=== summary +OK 8 +NOT_OBSERVABLE 6 +UPDATE_IGNORED 4 +SKIPPED 4 diff --git a/bundle/direct/autotest/output/instance_pools.txt b/bundle/direct/autotest/output/instance_pools.txt index ebf95e63e98..8ad82343ace 100644 --- a/bundle/direct/autotest/output/instance_pools.txt +++ b/bundle/direct/autotest/output/instance_pools.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + enable_elastic_disk false true UPDATE_IGNORED enable_elastic_disk true false UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/instance_pools.txt.backup b/bundle/direct/autotest/output/instance_pools.txt.backup new file mode 100644 index 00000000000..ebf95e63e98 --- /dev/null +++ b/bundle/direct/autotest/output/instance_pools.txt.backup @@ -0,0 +1,10 @@ +enable_elastic_disk false true UPDATE_IGNORED +enable_elastic_disk true false UPDATE_IGNORED + +=== summary +OK 45 +OK_RECREATE 46 +SUPPRESSED 5 +NOT_OBSERVABLE 2 +UPDATE_IGNORED 2 +SKIPPED 6 diff --git a/bundle/direct/autotest/output/job_runs.txt b/bundle/direct/autotest/output/job_runs.txt index b0d1dbf87a4..f7528a050df 100644 --- a/bundle/direct/autotest/output/job_runs.txt +++ b/bundle/direct/autotest/output/job_runs.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary OK_RECREATE 109 diff --git a/bundle/direct/autotest/output/job_runs.txt.backup b/bundle/direct/autotest/output/job_runs.txt.backup new file mode 100644 index 00000000000..b0d1dbf87a4 --- /dev/null +++ b/bundle/direct/autotest/output/job_runs.txt.backup @@ -0,0 +1,6 @@ + +=== summary +OK_RECREATE 109 +SUPPRESSED 9 +NOT_OBSERVABLE 12 +SKIPPED 1 diff --git a/bundle/direct/autotest/output/jobs.txt b/bundle/direct/autotest/output/jobs.txt index 67532dc8338..7986c428408 100644 --- a/bundle/direct/autotest/output/jobs.txt +++ b/bundle/direct/autotest/output/jobs.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + parent_path absent x UPDATE_IGNORED parent_path absent y UPDATE_IGNORED parent_path x absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/jobs.txt.backup b/bundle/direct/autotest/output/jobs.txt.backup new file mode 100644 index 00000000000..67532dc8338 --- /dev/null +++ b/bundle/direct/autotest/output/jobs.txt.backup @@ -0,0 +1,14 @@ +parent_path absent x UPDATE_IGNORED +parent_path absent y UPDATE_IGNORED +parent_path x absent UPDATE_IGNORED +parent_path x y UPDATE_IGNORED +parent_path y absent UPDATE_IGNORED +parent_path y x UPDATE_IGNORED + +=== summary +OK 561 +OK_INERT 6 +SUPPRESSED 47 +NOT_OBSERVABLE 34 +UPDATE_IGNORED 6 +SKIPPED 54 diff --git a/bundle/direct/autotest/output/model_serving_endpoints.txt b/bundle/direct/autotest/output/model_serving_endpoints.txt index a1ae0fc2f0d..e7bf58ac41f 100644 --- a/bundle/direct/autotest/output/model_serving_endpoints.txt +++ b/bundle/direct/autotest/output/model_serving_endpoints.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary OK 33 diff --git a/bundle/direct/autotest/output/model_serving_endpoints.txt.backup b/bundle/direct/autotest/output/model_serving_endpoints.txt.backup new file mode 100644 index 00000000000..a1ae0fc2f0d --- /dev/null +++ b/bundle/direct/autotest/output/model_serving_endpoints.txt.backup @@ -0,0 +1,8 @@ + +=== summary +OK 33 +OK_RECREATE 12 +OK_INERT 6 +SUPPRESSED 4 +NOT_OBSERVABLE 3 +SKIPPED 4 diff --git a/bundle/direct/autotest/output/models.txt b/bundle/direct/autotest/output/models.txt index d573aee7b0a..5a705b5557e 100644 --- a/bundle/direct/autotest/output/models.txt +++ b/bundle/direct/autotest/output/models.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + description x absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) description y absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/models.txt.backup b/bundle/direct/autotest/output/models.txt.backup new file mode 100644 index 00000000000..d573aee7b0a --- /dev/null +++ b/bundle/direct/autotest/output/models.txt.backup @@ -0,0 +1,9 @@ +description x absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) +description y absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) + +=== summary +OK 4 +OK_RECREATE 2 +OK_INERT 18 +BACKEND_ERROR 2 +SKIPPED 1 diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index 0ae900d0f8a..88551059d55 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + catalog absent x BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) catalog absent y BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) catalog x absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/pipelines.txt.backup b/bundle/direct/autotest/output/pipelines.txt.backup new file mode 100644 index 00000000000..0ae900d0f8a --- /dev/null +++ b/bundle/direct/autotest/output/pipelines.txt.backup @@ -0,0 +1,19 @@ +catalog absent x BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) +catalog absent y BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) +catalog x absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) +catalog x y BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) +catalog y absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) +catalog y x BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) +dry_run absent true UPDATE_IGNORED +dry_run false true UPDATE_IGNORED + +=== summary +OK 696 +OK_RECREATE 9 +OK_INERT 12 +SUPPRESSED 44 +NOT_OBSERVABLE 29 +UPDATE_IGNORED 2 +BACKEND_ERROR 2 +SKIPPED 17 +BASE_ERROR 4 diff --git a/bundle/direct/autotest/output/postgres_branches.txt b/bundle/direct/autotest/output/postgres_branches.txt index 6f4d47376ab..23076acc9df 100644 --- a/bundle/direct/autotest/output/postgres_branches.txt +++ b/bundle/direct/autotest/output/postgres_branches.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + is_protected true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) no_expiry absent false BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) no_expiry true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/postgres_branches.txt.backup b/bundle/direct/autotest/output/postgres_branches.txt.backup new file mode 100644 index 00000000000..6f4d47376ab --- /dev/null +++ b/bundle/direct/autotest/output/postgres_branches.txt.backup @@ -0,0 +1,15 @@ +is_protected true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +no_expiry absent false BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +no_expiry true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +parent x y BASE_ERROR cannot recreate resources.postgres_branches.foo: No stub found for pattern: POST /api/2.0/postgres/x/branches (501 ) +parent y x BASE_ERROR cannot recreate resources.postgres_branches.foo: No stub found for pattern: POST /api/2.0/postgres/y/branches (501 ) + +=== summary +OK 10 +OK_RECREATE 14 +OK_INERT 6 +SUPPRESSED 4 +NOT_OBSERVABLE 1 +BACKEND_ERROR 2 +SKIPPED 1 +BASE_ERROR 3 diff --git a/bundle/direct/autotest/output/postgres_catalogs.txt b/bundle/direct/autotest/output/postgres_catalogs.txt index 1bd2a026bd8..68c2f021a47 100644 --- a/bundle/direct/autotest/output/postgres_catalogs.txt +++ b/bundle/direct/autotest/output/postgres_catalogs.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary OK_RECREATE 14 diff --git a/bundle/direct/autotest/output/postgres_catalogs.txt.backup b/bundle/direct/autotest/output/postgres_catalogs.txt.backup new file mode 100644 index 00000000000..1bd2a026bd8 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_catalogs.txt.backup @@ -0,0 +1,6 @@ + +=== summary +OK_RECREATE 14 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +SKIPPED 1 diff --git a/bundle/direct/autotest/output/postgres_databases.txt b/bundle/direct/autotest/output/postgres_databases.txt index f8112aa8e12..10af6e91a9b 100644 --- a/bundle/direct/autotest/output/postgres_databases.txt +++ b/bundle/direct/autotest/output/postgres_databases.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + parent x y BASE_ERROR cannot recreate resources.postgres_databases.foo: No stub found for pattern: POST /api/2.0/postgres/x/databases (501 ) parent y x BASE_ERROR cannot recreate resources.postgres_databases.foo: No stub found for pattern: POST /api/2.0/postgres/y/databases (501 ) diff --git a/bundle/direct/autotest/output/postgres_databases.txt.backup b/bundle/direct/autotest/output/postgres_databases.txt.backup new file mode 100644 index 00000000000..f8112aa8e12 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_databases.txt.backup @@ -0,0 +1,9 @@ +parent x y BASE_ERROR cannot recreate resources.postgres_databases.foo: No stub found for pattern: POST /api/2.0/postgres/x/databases (501 ) +parent y x BASE_ERROR cannot recreate resources.postgres_databases.foo: No stub found for pattern: POST /api/2.0/postgres/y/databases (501 ) + +=== summary +OK 8 +OK_RECREATE 2 +OK_INERT 6 +SKIPPED 1 +BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/postgres_endpoints.txt b/bundle/direct/autotest/output/postgres_endpoints.txt index d0709dd2da2..f502e1216d9 100644 --- a/bundle/direct/autotest/output/postgres_endpoints.txt +++ b/bundle/direct/autotest/output/postgres_endpoints.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/postgres_endpoints.txt.backup b/bundle/direct/autotest/output/postgres_endpoints.txt.backup new file mode 100644 index 00000000000..d0709dd2da2 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_endpoints.txt.backup @@ -0,0 +1,27 @@ +autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +disabled true absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries absent false BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries absent true BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries false absent BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries false true BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries true absent BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.enable_readable_secondaries true false BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.max 1 2 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.max 2 1 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.min 1 2 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +group.min 2 1 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) +parent x y BASE_ERROR cannot recreate resources.postgres_endpoints.foo: No stub found for pattern: POST /api/2.0/postgres/x/endpoints (501 ) +parent y x BASE_ERROR cannot recreate resources.postgres_endpoints.foo: No stub found for pattern: POST /api/2.0/postgres/y/endpoints (501 ) + +=== summary +OK 15 +OK_RECREATE 4 +OK_INERT 6 +SUPPRESSED 2 +NOT_OBSERVABLE 2 +BACKEND_ERROR 7 +SKIPPED 1 +BASE_ERROR 10 diff --git a/bundle/direct/autotest/output/postgres_projects.txt b/bundle/direct/autotest/output/postgres_projects.txt index fa3334a235b..67b2678566e 100644 --- a/bundle/direct/autotest/output/postgres_projects.txt +++ b/bundle/direct/autotest/output/postgres_projects.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/postgres_projects.txt.backup b/bundle/direct/autotest/output/postgres_projects.txt.backup new file mode 100644 index 00000000000..fa3334a235b --- /dev/null +++ b/bundle/direct/autotest/output/postgres_projects.txt.backup @@ -0,0 +1,35 @@ +budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags absent len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags len0 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags len0 len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags len1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +custom_tags len1 len0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_max_cu 1 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_max_cu 1 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_max_cu 2 1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_max_cu 2 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_max_cu absent 1 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_max_cu absent 2 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_min_cu 1 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_min_cu 1 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_min_cu 2 1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_min_cu 2 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_min_cu absent 1 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.autoscaling_limit_min_cu absent 2 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.no_suspension false absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) +enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) + +=== summary +OK 29 +OK_RECREATE 8 +SUPPRESSED 4 +BACKEND_ERROR 13 +SKIPPED 3 +BASE_ERROR 14 diff --git a/bundle/direct/autotest/output/postgres_roles.txt b/bundle/direct/autotest/output/postgres_roles.txt index 66dedd17dd9..5cab59b561a 100644 --- a/bundle/direct/autotest/output/postgres_roles.txt +++ b/bundle/direct/autotest/output/postgres_roles.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + parent x y BASE_ERROR cannot recreate resources.postgres_roles.foo: No stub found for pattern: POST /api/2.0/postgres/x/roles (501 ) parent y x BASE_ERROR cannot recreate resources.postgres_roles.foo: No stub found for pattern: POST /api/2.0/postgres/y/roles (501 ) diff --git a/bundle/direct/autotest/output/postgres_roles.txt.backup b/bundle/direct/autotest/output/postgres_roles.txt.backup new file mode 100644 index 00000000000..66dedd17dd9 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_roles.txt.backup @@ -0,0 +1,11 @@ +parent x y BASE_ERROR cannot recreate resources.postgres_roles.foo: No stub found for pattern: POST /api/2.0/postgres/x/roles (501 ) +parent y x BASE_ERROR cannot recreate resources.postgres_roles.foo: No stub found for pattern: POST /api/2.0/postgres/y/roles (501 ) + +=== summary +OK 22 +OK_RECREATE 20 +OK_INERT 6 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +SKIPPED 1 +BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/postgres_synced_tables.txt b/bundle/direct/autotest/output/postgres_synced_tables.txt index e6fae0817e4..787c4566dfd 100644 --- a/bundle/direct/autotest/output/postgres_synced_tables.txt +++ b/bundle/direct/autotest/output/postgres_synced_tables.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary OK_RECREATE 110 diff --git a/bundle/direct/autotest/output/postgres_synced_tables.txt.backup b/bundle/direct/autotest/output/postgres_synced_tables.txt.backup new file mode 100644 index 00000000000..e6fae0817e4 --- /dev/null +++ b/bundle/direct/autotest/output/postgres_synced_tables.txt.backup @@ -0,0 +1,6 @@ + +=== summary +OK_RECREATE 110 +SUPPRESSED 6 +NOT_OBSERVABLE 4 +SKIPPED 1 diff --git a/bundle/direct/autotest/output/quality_monitors.txt b/bundle/direct/autotest/output/quality_monitors.txt index 7683b7d5785..182e92b6d11 100644 --- a/bundle/direct/autotest/output/quality_monitors.txt +++ b/bundle/direct/autotest/output/quality_monitors.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + latest_monitor_failure_msg absent x POST_DEPLOY_DRIFT latest_monitor_failure_msg latest_monitor_failure_msg absent y POST_DEPLOY_DRIFT latest_monitor_failure_msg latest_monitor_failure_msg x y POST_DEPLOY_DRIFT latest_monitor_failure_msg diff --git a/bundle/direct/autotest/output/quality_monitors.txt.backup b/bundle/direct/autotest/output/quality_monitors.txt.backup new file mode 100644 index 00000000000..7683b7d5785 --- /dev/null +++ b/bundle/direct/autotest/output/quality_monitors.txt.backup @@ -0,0 +1,12 @@ +latest_monitor_failure_msg absent x POST_DEPLOY_DRIFT latest_monitor_failure_msg +latest_monitor_failure_msg absent y POST_DEPLOY_DRIFT latest_monitor_failure_msg +latest_monitor_failure_msg x y POST_DEPLOY_DRIFT latest_monitor_failure_msg +latest_monitor_failure_msg y x POST_DEPLOY_DRIFT latest_monitor_failure_msg + +=== summary +OK 100 +OK_RECREATE 4 +SUPPRESSED 6 +NOT_OBSERVABLE 4 +POST_DEPLOY_DRIFT 4 +SKIPPED 1 diff --git a/bundle/direct/autotest/output/registered_models.txt b/bundle/direct/autotest/output/registered_models.txt index 384a73e8855..297d4329fa0 100644 --- a/bundle/direct/autotest/output/registered_models.txt +++ b/bundle/direct/autotest/output/registered_models.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + full_name absent x UPDATE_IGNORED full_name absent y UPDATE_IGNORED full_name x y UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/registered_models.txt.backup b/bundle/direct/autotest/output/registered_models.txt.backup new file mode 100644 index 00000000000..384a73e8855 --- /dev/null +++ b/bundle/direct/autotest/output/registered_models.txt.backup @@ -0,0 +1,17 @@ +full_name absent x UPDATE_IGNORED +full_name absent y UPDATE_IGNORED +full_name x y UPDATE_IGNORED +full_name y x UPDATE_IGNORED +owner absent x UPDATE_IGNORED +owner absent y UPDATE_IGNORED +owner x y UPDATE_IGNORED +owner y x UPDATE_IGNORED + +=== summary +OK 44 +OK_RECREATE 2 +OK_ID_FIELD_REQUIRED 8 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +UPDATE_IGNORED 8 +SKIPPED 9 diff --git a/bundle/direct/autotest/output/schemas.txt b/bundle/direct/autotest/output/schemas.txt index 0d65e6f3fb3..348784ab2e6 100644 --- a/bundle/direct/autotest/output/schemas.txt +++ b/bundle/direct/autotest/output/schemas.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + custom_max_retention_hours 168 absent UPDATE_IGNORED custom_max_retention_hours 720 absent UPDATE_IGNORED properties keys1 absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/schemas.txt.backup b/bundle/direct/autotest/output/schemas.txt.backup new file mode 100644 index 00000000000..0d65e6f3fb3 --- /dev/null +++ b/bundle/direct/autotest/output/schemas.txt.backup @@ -0,0 +1,14 @@ +custom_max_retention_hours 168 absent UPDATE_IGNORED +custom_max_retention_hours 720 absent UPDATE_IGNORED +properties keys1 absent UPDATE_IGNORED +properties keys1 keys0 UPDATE_IGNORED +properties['team'] x absent UPDATE_IGNORED +properties['team'] y absent UPDATE_IGNORED + +=== summary +OK 16 +OK_RECREATE 2 +SUPPRESSED 1 +NOT_OBSERVABLE 1 +UPDATE_IGNORED 6 +SKIPPED 2 diff --git a/bundle/direct/autotest/output/secret_scopes.txt b/bundle/direct/autotest/output/secret_scopes.txt index bb9f6335fda..c2011a38c39 100644 --- a/bundle/direct/autotest/output/secret_scopes.txt +++ b/bundle/direct/autotest/output/secret_scopes.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyV...Metadata defined! (400 INVALID_PARAMETER_VALUE) backend_type AZURE_KEYVAULT absent BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyV...Metadata defined! (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/secret_scopes.txt.backup b/bundle/direct/autotest/output/secret_scopes.txt.backup new file mode 100644 index 00000000000..bb9f6335fda --- /dev/null +++ b/bundle/direct/autotest/output/secret_scopes.txt.backup @@ -0,0 +1,7 @@ +backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyV...Metadata defined! (400 INVALID_PARAMETER_VALUE) +backend_type AZURE_KEYVAULT absent BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyV...Metadata defined! (400 INVALID_PARAMETER_VALUE) + +=== summary +NOT_OBSERVABLE 10 +SKIPPED 1 +BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/secrets.txt b/bundle/direct/autotest/output/secrets.txt index 6ad75fbeacb..aeb3aca552c 100644 --- a/bundle/direct/autotest/output/secrets.txt +++ b/bundle/direct/autotest/output/secrets.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + comment x absent UPDATE_IGNORED comment y absent UPDATE_IGNORED owner absent x SUPPRESSED spec:input_only diff --git a/bundle/direct/autotest/output/secrets.txt.backup b/bundle/direct/autotest/output/secrets.txt.backup index 5f108d1bb24..6ad75fbeacb 100644 --- a/bundle/direct/autotest/output/secrets.txt.backup +++ b/bundle/direct/autotest/output/secrets.txt.backup @@ -6,13 +6,10 @@ owner x absent SUPPRESSED s owner x y SUPPRESSED spec:input_only owner y absent SUPPRESSED spec:input_only owner y x SUPPRESSED spec:input_only -schema_name x y COLLATERAL_DRIFT value -schema_name y x COLLATERAL_DRIFT value === summary OK 4 -OK_RECREATE 4 +OK_RECREATE 2 SUPPRESSED 6 UPDATE_IGNORED 2 -COLLATERAL_DRIFT 2 -SKIPPED 8 +SKIPPED 10 diff --git a/bundle/direct/autotest/output/sql_warehouses.txt b/bundle/direct/autotest/output/sql_warehouses.txt index ce4a674a677..a616c9f5373 100644 --- a/bundle/direct/autotest/output/sql_warehouses.txt +++ b/bundle/direct/autotest/output/sql_warehouses.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + auto_stop_mins 10 absent UPDATE_IGNORED auto_stop_mins 20 absent UPDATE_IGNORED cluster_size 2X-Small absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/sql_warehouses.txt.backup b/bundle/direct/autotest/output/sql_warehouses.txt.backup new file mode 100644 index 00000000000..ce4a674a677 --- /dev/null +++ b/bundle/direct/autotest/output/sql_warehouses.txt.backup @@ -0,0 +1,24 @@ +auto_stop_mins 10 absent UPDATE_IGNORED +auto_stop_mins 20 absent UPDATE_IGNORED +cluster_size 2X-Small absent UPDATE_IGNORED +cluster_size X-Small absent UPDATE_IGNORED +cluster_size absent 2X-Small START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) +cluster_size absent X-Small START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) +enable_photon true absent UPDATE_IGNORED +max_num_clusters 2 absent UPDATE_IGNORED +max_num_clusters 3 absent UPDATE_IGNORED +max_num_clusters absent 2 START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) +max_num_clusters absent 3 START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) +name absent x-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) +name absent y-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) +name x-UNIQUE absent UPDATE_IGNORED +name y-UNIQUE absent UPDATE_IGNORED +spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED +spot_instance_policy RELIABILITY_OPTIMIZED absent UPDATE_IGNORED + +=== summary +OK 36 +SUPPRESSED 3 +UPDATE_IGNORED 11 +SKIPPED 5 +START_NOT_REACHED 6 diff --git a/bundle/direct/autotest/output/synced_database_tables.txt b/bundle/direct/autotest/output/synced_database_tables.txt index cc1a2da8947..e1d9a827c5f 100644 --- a/bundle/direct/autotest/output/synced_database_tables.txt +++ b/bundle/direct/autotest/output/synced_database_tables.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + database_instance_name absent x DEPLOY_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) database_instance_name absent y DEPLOY_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) database_instance_name x absent BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) diff --git a/bundle/direct/autotest/output/synced_database_tables.txt.backup b/bundle/direct/autotest/output/synced_database_tables.txt.backup new file mode 100644 index 00000000000..cc1a2da8947 --- /dev/null +++ b/bundle/direct/autotest/output/synced_database_tables.txt.backup @@ -0,0 +1,14 @@ +database_instance_name absent x DEPLOY_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) +database_instance_name absent y DEPLOY_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) +database_instance_name x absent BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) +database_instance_name x y BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) +database_instance_name y absent BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) +database_instance_name y x BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) + +=== summary +OK_RECREATE 68 +SUPPRESSED 4 +NOT_OBSERVABLE 2 +DEPLOY_ERROR 2 +SKIPPED 35 +BASE_ERROR 4 diff --git a/bundle/direct/autotest/output/vector_search_endpoints.txt b/bundle/direct/autotest/output/vector_search_endpoints.txt index 4efc342c607..abfbe8fe2f3 100644 --- a/bundle/direct/autotest/output/vector_search_endpoints.txt +++ b/bundle/direct/autotest/output/vector_search_endpoints.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + target_qps 1 absent UPDATE_IGNORED target_qps 2 absent UPDATE_IGNORED diff --git a/bundle/direct/autotest/output/vector_search_endpoints.txt.backup b/bundle/direct/autotest/output/vector_search_endpoints.txt.backup index 9465f7bf91b..4efc342c607 100644 --- a/bundle/direct/autotest/output/vector_search_endpoints.txt.backup +++ b/bundle/direct/autotest/output/vector_search_endpoints.txt.backup @@ -1,5 +1,8 @@ +target_qps 1 absent UPDATE_IGNORED +target_qps 2 absent UPDATE_IGNORED === summary -OK 12 +OK 10 OK_RECREATE 4 +UPDATE_IGNORED 2 SKIPPED 2 diff --git a/bundle/direct/autotest/output/vector_search_indexes.txt b/bundle/direct/autotest/output/vector_search_indexes.txt index 4867308ae1c..7447c6b21a2 100644 --- a/bundle/direct/autotest/output/vector_search_indexes.txt +++ b/bundle/direct/autotest/output/vector_search_indexes.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + endpoint_name x y BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint x not found (404 RESOURCE_DOES_NOT_EXIST) endpoint_name y x BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint y not found (404 RESOURCE_DOES_NOT_EXIST) name x-UNIQUE y-UNIQUE BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Invalid index name. Must specify the ...ores are allowed. (400 INVALID_PARAMETER_VALUE) diff --git a/bundle/direct/autotest/output/volumes.txt b/bundle/direct/autotest/output/volumes.txt index db4540165a2..70459278683 100644 --- a/bundle/direct/autotest/output/volumes.txt +++ b/bundle/direct/autotest/output/volumes.txt @@ -1,3 +1,13 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary OK 8 diff --git a/bundle/direct/autotest/output/volumes.txt.backup b/bundle/direct/autotest/output/volumes.txt.backup new file mode 100644 index 00000000000..db4540165a2 --- /dev/null +++ b/bundle/direct/autotest/output/volumes.txt.backup @@ -0,0 +1,4 @@ + +=== summary +OK 8 +SKIPPED 2 diff --git a/bundle/direct/autotest/report_test.go b/bundle/direct/autotest/report_test.go index 7d69a595452..13e4456bea5 100644 --- a/bundle/direct/autotest/report_test.go +++ b/bundle/direct/autotest/report_test.go @@ -8,6 +8,7 @@ import ( "path/filepath" "slices" "strings" + "time" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/testdiff" @@ -176,6 +177,10 @@ type result struct { type report struct { resourceType string + // started is when this type's run began, so the full report can say how long it took. Only in + // the full report: the committed golden has to be the same whatever the machine's speed. + started time.Time + results []result wildcard map[string]bool covered map[string]bool @@ -197,6 +202,19 @@ func (r *report) addSampled(path string) { r.sampled[path] = true } +// reportHeader introduces a committed report to a reader who has only the diff. +const reportHeader = `# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + +` + // fieldColumnWidth is how wide the field column is in a rendered row. Named because sampledRows // reads the field back out of the text by it. const fieldColumnWidth = 44 @@ -290,6 +308,12 @@ func (r *report) uncoveredPaths() []string { // behind each finding, and what was not covered. func (r *report) render(problemsOnly bool) string { var sb strings.Builder + if problemsOnly { + // The committed report is read on its own in a diff, with nothing around it to say what + // the columns are. Deliberately free of counts, times and dates: a header that moved when + // something unrelated changed would be noise in every review. + sb.WriteString(reportHeader) + } slices.SortStableFunc(r.results, func(a, b result) int { return cmp.Or( @@ -343,6 +367,8 @@ func (r *report) render(problemsOnly bool) string { return sb.String() } + fmt.Fprintf(&sb, "\n=== elapsed\n%s\n", r.elapsed().Round(time.Millisecond)) + // The legend goes here and not into the committed report: it is JSON, so it would churn // whenever an SDK type gains a field, and a reader decoding a label is already looking at // this file for the evidence behind the row. @@ -527,6 +553,15 @@ func reportPath(name string) string { return filepath.Join(outputDir, name) } +// elapsed is how long this type's run took, or zero when the report was built without a start time +// (the unit tests construct one directly). +func (r *report) elapsed() time.Duration { + if r.started.IsZero() { + return 0 + } + return time.Since(r.started) +} + func logPath(name string) string { return filepath.Join(logsDir, name) } diff --git a/libs/testserver/jobs.go b/libs/testserver/jobs.go index a2866ca13fa..ea070f065a5 100644 --- a/libs/testserver/jobs.go +++ b/libs/testserver/jobs.go @@ -966,12 +966,20 @@ func (s *FakeWorkspace) JobsGetRun(req Request) Response { return Response{StatusCode: 404} } - // Simulate cloud behavior: first poll returns RUNNING, next the terminal state. + // Simulate cloud behavior: first poll returns RUNNING, next the terminal state. The waiter then + // pays the SDK's backoff -- attempt times a second, plus jitter -- for every run, which a suite + // deploying thousands of them cannot afford. SettleAsyncImmediately reports the terminal state + // on the first poll instead; the acceptance suite leaves the simulation on, so the waiter and + // its logging stay covered there. if run.State.LifeCycleState == jobs.RunLifeCycleStateRunning { // Transition stored state to TERMINATED for the next poll. terminateRun(&run) s.JobRuns[runIdInt] = run + if s.SettleAsyncImmediately { + return Response{Body: run} + } + // Return RUNNING for this poll (before the transition). runResp := run runResp.State = &jobs.RunState{ From 1e642b2a0c109ab70f8af57eaf66c85c6bdd4880 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 1 Sep 2026 23:28:38 +0200 Subject: [PATCH 110/118] Field catalog: a notification list needs real addresses, in the order the backend keeps Removing email_notifications from base cost more than the drift row it saved: with no value to clone, the container's values came from the generic slice defaults, so the suite sent one- and two-element lists of "x" and "y" and the API refused all 22 rows ("Invalid email format: x"). The list needed its own declared values, not just the address inside it. Declared alphabetically, because the backend returns the list sorted. The first attempt used [notify, alerts] and every two-address list read back as [alerts, notify], so the endpoint drifted on the ordering alone, for good -- a real property, and one that makes the field untestable if the fixture fights it. Written down where the values are. Co-authored-by: Isaac --- .../autotest/output/model_serving_endpoints.txt | 6 +++--- .../output/model_serving_endpoints.txt.backup | 16 +++++++++++++--- .../testdata/fields/model_serving_endpoints.yml | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/bundle/direct/autotest/output/model_serving_endpoints.txt b/bundle/direct/autotest/output/model_serving_endpoints.txt index e7bf58ac41f..c98c8725ae7 100644 --- a/bundle/direct/autotest/output/model_serving_endpoints.txt +++ b/bundle/direct/autotest/output/model_serving_endpoints.txt @@ -10,9 +10,9 @@ === summary -OK 33 +OK 24 OK_RECREATE 12 OK_INERT 6 -SUPPRESSED 4 -NOT_OBSERVABLE 3 +SUPPRESSED 3 +NOT_OBSERVABLE 1 SKIPPED 4 diff --git a/bundle/direct/autotest/output/model_serving_endpoints.txt.backup b/bundle/direct/autotest/output/model_serving_endpoints.txt.backup index a1ae0fc2f0d..c98c8725ae7 100644 --- a/bundle/direct/autotest/output/model_serving_endpoints.txt.backup +++ b/bundle/direct/autotest/output/model_serving_endpoints.txt.backup @@ -1,8 +1,18 @@ +# field | from | to | verdict | detail +# +# How the direct engine handles every field of this resource type: each row is one move of one field +# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than +# failed, so this file is a description of current behaviour -- what fails the test is a verdict +# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with +# the evidence behind each finding and what could not be covered. +# +# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. + === summary -OK 33 +OK 24 OK_RECREATE 12 OK_INERT 6 -SUPPRESSED 4 -NOT_OBSERVABLE 3 +SUPPRESSED 3 +NOT_OBSERVABLE 1 SKIPPED 4 diff --git a/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml b/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml index ad6cd44295d..9818a6d8c64 100644 --- a/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml +++ b/bundle/direct/autotest/testdata/fields/model_serving_endpoints.yml @@ -40,5 +40,19 @@ skip: fields: # A recipient is validated by format, so the generic "x" is rejected. Reserved # non-resolving addresses, per RFC 2606. + # Both the list and the address inside it need real values: the API validates every entry + # ("Invalid email format: x"), and the generic one- and two-element lists a slice gets by default + # hold plain strings. Declared for the container as well as the element, so growing the list is + # tested with addresses the backend accepts. + # + # In alphabetical order, because the backend returns the list sorted: declared the other way round, + # a two-address list read back reordered and the endpoint drifted on the ordering alone, for good. + # That is a property worth knowing, and it makes the field untestable if the fixture fights it. + email_notifications.on_update_failure: + - [alerts@databricks.invalid] + - [alerts@databricks.invalid, notify@databricks.invalid] + email_notifications.on_update_success: + - [alerts@databricks.invalid] + - [alerts@databricks.invalid, notify@databricks.invalid] email_notifications.on_update_failure[*]: [notify@databricks.invalid, alerts@databricks.invalid] email_notifications.on_update_success[*]: [notify@databricks.invalid, alerts@databricks.invalid] From 428c5f04d354b092bf93f522c8409221c7eadc1e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 2 Sep 2026 08:18:36 +0200 Subject: [PATCH 111/118] Field catalog: an init script has to exist, and a cluster policy has to be real The clusters cloud run failed at the create: "Init scripts failed ... Tree node with path /Users//init.sh does not exist". Three fixtures named that path -- clusters, jobs, pipelines -- and nothing ever created the file; the mock server accepts any path, so it had never mattered. They now point at a real script the bundle uploads. It lives in the suite's own testdata/data, not the shared invariant data directory: everything there is uploaded by every invariant config too, so putting it there changed "Files: 2 uploaded" to 3 across that whole suite. Found by running it. policy_id has to name a cluster policy that exists ("'x' is not a valid cluster policy ID"), so its six rows had only ever measured the fake. Skipped with the reason. Also stops the previous commit's .backup files landing in output/, which is meant to hold only what is committed -- and had duly committed 34 of them. They belong beside the logs they are backups of, where git is not already the history. Co-authored-by: Isaac --- bundle/direct/autotest/fixtures_test.go | 5 + bundle/direct/autotest/harness_test.go | 3 + .../direct/autotest/output/alerts.txt.backup | 5 - bundle/direct/autotest/output/apps.txt.backup | 105 ------------------ .../autotest/output/catalogs.txt.backup | 13 --- .../output/cluster_policies.txt.backup | 20 ---- bundle/direct/autotest/output/clusters.txt | 4 +- .../autotest/output/clusters.txt.backup | 6 - .../autotest/output/dashboards.txt.backup | 12 -- .../output/database_catalogs.txt.backup | 9 -- .../output/database_instances.txt.backup | 60 ---------- .../autotest/output/experiments.txt.backup | 6 - .../output/external_locations.txt.backup | 7 -- .../autotest/output/genie_spaces.txt.backup | 10 -- .../autotest/output/instance_pools.txt.backup | 10 -- .../autotest/output/job_runs.txt.backup | 6 - bundle/direct/autotest/output/jobs.txt.backup | 14 --- .../output/model_serving_endpoints.txt.backup | 18 --- .../direct/autotest/output/models.txt.backup | 9 -- .../autotest/output/pipelines.txt.backup | 19 ---- .../output/postgres_branches.txt.backup | 15 --- .../output/postgres_catalogs.txt.backup | 6 - .../output/postgres_databases.txt.backup | 9 -- .../output/postgres_endpoints.txt.backup | 27 ----- .../output/postgres_projects.txt.backup | 35 ------ .../autotest/output/postgres_roles.txt.backup | 11 -- .../output/postgres_synced_tables.txt.backup | 6 - .../output/quality_monitors.txt.backup | 12 -- .../output/registered_models.txt.backup | 17 --- .../direct/autotest/output/schemas.txt.backup | 14 --- .../autotest/output/secret_scopes.txt.backup | 7 -- .../direct/autotest/output/secrets.txt.backup | 15 --- .../autotest/output/sql_warehouses.txt.backup | 24 ---- .../output/synced_database_tables.txt.backup | 14 --- .../output/vector_search_endpoints.txt.backup | 8 -- .../output/vector_search_indexes.txt.backup | 11 -- .../direct/autotest/output/volumes.txt.backup | 4 - bundle/direct/autotest/report_test.go | 16 ++- bundle/direct/autotest/testdata/data/init.sh | 5 + .../autotest/testdata/fields/clusters.yml | 8 +- .../direct/autotest/testdata/fields/jobs.yml | 5 +- .../autotest/testdata/fields/pipelines.yml | 5 +- 42 files changed, 40 insertions(+), 575 deletions(-) delete mode 100644 bundle/direct/autotest/output/alerts.txt.backup delete mode 100644 bundle/direct/autotest/output/apps.txt.backup delete mode 100644 bundle/direct/autotest/output/catalogs.txt.backup delete mode 100644 bundle/direct/autotest/output/cluster_policies.txt.backup delete mode 100644 bundle/direct/autotest/output/clusters.txt.backup delete mode 100644 bundle/direct/autotest/output/dashboards.txt.backup delete mode 100644 bundle/direct/autotest/output/database_catalogs.txt.backup delete mode 100644 bundle/direct/autotest/output/database_instances.txt.backup delete mode 100644 bundle/direct/autotest/output/experiments.txt.backup delete mode 100644 bundle/direct/autotest/output/external_locations.txt.backup delete mode 100644 bundle/direct/autotest/output/genie_spaces.txt.backup delete mode 100644 bundle/direct/autotest/output/instance_pools.txt.backup delete mode 100644 bundle/direct/autotest/output/job_runs.txt.backup delete mode 100644 bundle/direct/autotest/output/jobs.txt.backup delete mode 100644 bundle/direct/autotest/output/model_serving_endpoints.txt.backup delete mode 100644 bundle/direct/autotest/output/models.txt.backup delete mode 100644 bundle/direct/autotest/output/pipelines.txt.backup delete mode 100644 bundle/direct/autotest/output/postgres_branches.txt.backup delete mode 100644 bundle/direct/autotest/output/postgres_catalogs.txt.backup delete mode 100644 bundle/direct/autotest/output/postgres_databases.txt.backup delete mode 100644 bundle/direct/autotest/output/postgres_endpoints.txt.backup delete mode 100644 bundle/direct/autotest/output/postgres_projects.txt.backup delete mode 100644 bundle/direct/autotest/output/postgres_roles.txt.backup delete mode 100644 bundle/direct/autotest/output/postgres_synced_tables.txt.backup delete mode 100644 bundle/direct/autotest/output/quality_monitors.txt.backup delete mode 100644 bundle/direct/autotest/output/registered_models.txt.backup delete mode 100644 bundle/direct/autotest/output/schemas.txt.backup delete mode 100644 bundle/direct/autotest/output/secret_scopes.txt.backup delete mode 100644 bundle/direct/autotest/output/secrets.txt.backup delete mode 100644 bundle/direct/autotest/output/sql_warehouses.txt.backup delete mode 100644 bundle/direct/autotest/output/synced_database_tables.txt.backup delete mode 100644 bundle/direct/autotest/output/vector_search_endpoints.txt.backup delete mode 100644 bundle/direct/autotest/output/vector_search_indexes.txt.backup delete mode 100644 bundle/direct/autotest/output/volumes.txt.backup create mode 100644 bundle/direct/autotest/testdata/data/init.sh diff --git a/bundle/direct/autotest/fixtures_test.go b/bundle/direct/autotest/fixtures_test.go index b86d4959f2d..7bb6e2e9290 100644 --- a/bundle/direct/autotest/fixtures_test.go +++ b/bundle/direct/autotest/fixtures_test.go @@ -19,6 +19,11 @@ import ( // themselves live in testdata/fields and belong to this suite alone. const dataDir = "../../../acceptance/bundle/invariant/data" +// ownDataDir holds files only this suite needs, staged next to the shared ones. Kept separate +// because everything in dataDir is uploaded by every invariant config too, so a file added there +// for one fixture changes those tests' output. +const ownDataDir = "testdata/data" + // drivenTypes returns every resource type the direct engine supports, which is what this suite // covers: a type with no value library is a failure rather than a line in a report, so adding a // resource type to the engine and forgetting the catalog cannot go unnoticed. diff --git a/bundle/direct/autotest/harness_test.go b/bundle/direct/autotest/harness_test.go index 23ce0b4b186..e5d35f0fbf5 100644 --- a/bundle/direct/autotest/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -171,6 +171,9 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC if err := copyDir(dataDir, dir); err != nil { return nil, err } + if err := copyDir(ownDataDir, dir); err != nil { + return nil, err + } rememberWorkspaceUser(user.UserName) yml, err := renderBundle(resourceType, uniqueName, user.UserName, base, deps, variables) diff --git a/bundle/direct/autotest/output/alerts.txt.backup b/bundle/direct/autotest/output/alerts.txt.backup deleted file mode 100644 index d99a05171ff..00000000000 --- a/bundle/direct/autotest/output/alerts.txt.backup +++ /dev/null @@ -1,5 +0,0 @@ - -=== summary -OK 26 -NOT_OBSERVABLE 6 -SKIPPED 17 diff --git a/bundle/direct/autotest/output/apps.txt.backup b/bundle/direct/autotest/output/apps.txt.backup deleted file mode 100644 index 4619820810c..00000000000 --- a/bundle/direct/autotest/output/apps.txt.backup +++ /dev/null @@ -1,105 +0,0 @@ -config.command absent len1 START_NOT_REACHED no active deployment -config.command absent len2 START_NOT_REACHED no active deployment -config.command len1 absent START_NOT_REACHED no active deployment -config.command len1 len2 START_NOT_REACHED no active deployment -config.command len2 absent START_NOT_REACHED no active deployment -config.command len2 len1 START_NOT_REACHED no active deployment -config.command[0] absent x UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it -config.command[0] absent y UNSETTABLE cannot make config.command[0] absent: config.command[1] would shift into it -config.command[0] x absent START_NOT_REACHED no active deployment -config.command[0] x y START_NOT_REACHED no active deployment -config.command[0] y absent START_NOT_REACHED no active deployment -config.command[0] y x START_NOT_REACHED no active deployment -config.command[1] absent x START_NOT_REACHED no active deployment -config.command[1] absent y START_NOT_REACHED no active deployment -config.command[1] x absent START_NOT_REACHED no active deployment -config.command[1] x y START_NOT_REACHED no active deployment -config.command[1] y absent START_NOT_REACHED no active deployment -config.command[1] y x START_NOT_REACHED no active deployment -config.env absent len0 START_NOT_REACHED no active deployment -config.env absent len1 START_NOT_REACHED no active deployment -config.env len0 absent START_NOT_REACHED no active deployment -config.env len0 len1 START_NOT_REACHED no active deployment -config.env len1 absent START_NOT_REACHED no active deployment -config.env len1 len0 START_NOT_REACHED no active deployment -config.env[0].name x y START_NOT_REACHED no active deployment -config.env[0].name y x START_NOT_REACHED no active deployment -config.env[0].value absent x START_NOT_REACHED no active deployment -config.env[0].value absent y START_NOT_REACHED no active deployment -config.env[0].value x absent START_NOT_REACHED no active deployment -config.env[0].value x y START_NOT_REACHED no active deployment -config.env[0].value y absent START_NOT_REACHED no active deployment -config.env[0].value y x START_NOT_REACHED no active deployment -config.env[0].value_from absent x START_NOT_REACHED no active deployment -config.env[0].value_from absent y START_NOT_REACHED no active deployment -config.env[0].value_from x absent START_NOT_REACHED no active deployment -config.env[0].value_from x y START_NOT_REACHED no active deployment -config.env[0].value_from y absent START_NOT_REACHED no active deployment -config.env[0].value_from y x START_NOT_REACHED no active deployment -description x absent UPDATE_IGNORED -description y absent UPDATE_IGNORED -git_repository.auto_deploy true absent UPDATE_IGNORED -git_repository.caller_credential_id 1 absent UPDATE_IGNORED -git_repository.caller_credential_id 2 absent UPDATE_IGNORED -git_source.branch absent x SUPPRESSED no active deployment -git_source.branch absent y SUPPRESSED no active deployment -git_source.branch x absent START_NOT_REACHED no active deployment -git_source.branch x y START_NOT_REACHED no active deployment -git_source.branch y absent START_NOT_REACHED no active deployment -git_source.branch y x START_NOT_REACHED no active deployment -git_source.commit absent x SUPPRESSED no active deployment -git_source.commit absent y SUPPRESSED no active deployment -git_source.commit x absent START_NOT_REACHED no active deployment -git_source.commit x y START_NOT_REACHED no active deployment -git_source.commit y absent START_NOT_REACHED no active deployment -git_source.commit y x START_NOT_REACHED no active deployment -git_source.git_repository.auto_deploy absent false SUPPRESSED no active deployment -git_source.git_repository.auto_deploy absent true SUPPRESSED no active deployment -git_source.git_repository.auto_deploy false absent START_NOT_REACHED no active deployment -git_source.git_repository.auto_deploy false true START_NOT_REACHED no active deployment -git_source.git_repository.auto_deploy true absent START_NOT_REACHED no active deployment -git_source.git_repository.auto_deploy true false START_NOT_REACHED no active deployment -git_source.git_repository.caller_credential_id 1 2 START_NOT_REACHED no active deployment -git_source.git_repository.caller_credential_id 1 absent START_NOT_REACHED no active deployment -git_source.git_repository.caller_credential_id 2 1 START_NOT_REACHED no active deployment -git_source.git_repository.caller_credential_id 2 absent START_NOT_REACHED no active deployment -git_source.git_repository.caller_credential_id absent 1 SUPPRESSED no active deployment -git_source.git_repository.caller_credential_id absent 2 SUPPRESSED no active deployment -git_source.resolved_commit absent x SUPPRESSED no active deployment -git_source.resolved_commit absent y SUPPRESSED no active deployment -git_source.resolved_commit x absent START_NOT_REACHED no active deployment -git_source.resolved_commit x y START_NOT_REACHED no active deployment -git_source.resolved_commit y absent START_NOT_REACHED no active deployment -git_source.resolved_commit y x START_NOT_REACHED no active deployment -git_source.source_code_path absent x SUPPRESSED no active deployment -git_source.source_code_path absent y SUPPRESSED no active deployment -git_source.source_code_path x absent START_NOT_REACHED no active deployment -git_source.source_code_path x y START_NOT_REACHED no active deployment -git_source.source_code_path y absent START_NOT_REACHED no active deployment -git_source.source_code_path y x START_NOT_REACHED no active deployment -git_source.tag absent x SUPPRESSED no active deployment -git_source.tag absent y SUPPRESSED no active deployment -git_source.tag x absent START_NOT_REACHED no active deployment -git_source.tag x y START_NOT_REACHED no active deployment -git_source.tag y absent START_NOT_REACHED no active deployment -git_source.tag y x START_NOT_REACHED no active deployment -source_code_path absent x-UNIQUE SUPPRESSED no active deployment -source_code_path absent y-UNIQUE SUPPRESSED no active deployment -source_code_path x-UNIQUE absent START_NOT_REACHED no active deployment -source_code_path x-UNIQUE y-UNIQUE START_NOT_REACHED no active deployment -source_code_path y-UNIQUE absent START_NOT_REACHED no active deployment -source_code_path y-UNIQUE x-UNIQUE START_NOT_REACHED no active deployment -user_api_scopes len1 absent UPDATE_IGNORED -user_api_scopes len1 len0 UPDATE_IGNORED -user_api_scopes len2 absent UPDATE_IGNORED -user_api_scopes len2 len0 UPDATE_IGNORED - -=== summary -OK 35 -OK_RECREATE 2 -SUPPRESSED 23 -NOT_OBSERVABLE 3 -UPDATE_IGNORED 9 -UNSETTABLE 2 -SKIPPED 90 -START_NOT_REACHED 68 diff --git a/bundle/direct/autotest/output/catalogs.txt.backup b/bundle/direct/autotest/output/catalogs.txt.backup deleted file mode 100644 index ff32ebc7ef7..00000000000 --- a/bundle/direct/autotest/output/catalogs.txt.backup +++ /dev/null @@ -1,13 +0,0 @@ -custom_max_retention_hours 168 absent UPDATE_IGNORED -custom_max_retention_hours 720 absent UPDATE_IGNORED -properties keys1 absent UPDATE_IGNORED -properties keys1 keys0 UPDATE_IGNORED -properties['team'] x absent UPDATE_IGNORED -properties['team'] y absent UPDATE_IGNORED - -=== summary -OK 18 -SUPPRESSED 1 -NOT_OBSERVABLE 1 -UPDATE_IGNORED 6 -SKIPPED 8 diff --git a/bundle/direct/autotest/output/cluster_policies.txt.backup b/bundle/direct/autotest/output/cluster_policies.txt.backup deleted file mode 100644 index 3436746c9ca..00000000000 --- a/bundle/direct/autotest/output/cluster_policies.txt.backup +++ /dev/null @@ -1,20 +0,0 @@ -definition absent v1 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -definition absent v2 BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -definition v1 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -definition v2 absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'definition' must be supplied. (400 INVALID_PARAMETER_VALUE) -libraries[0].jar absent dbfs:/FileStore/a.jar BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) -libraries[0].jar absent dbfs:/FileStore/b.jar BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) -libraries[0].jar dbfs:/FileStore/a.jar absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) -libraries[0].jar dbfs:/FileStore/b.jar absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: Unknown library (400 INVALID_PARAMETER_VALUE) -name absent x-UNIQUE BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) -name absent y-UNIQUE BASE_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) -name x-UNIQUE absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) -name y-UNIQUE absent BACKEND_ERROR cannot update resources.cluster_policies.foo: updating id=[ID]: 'name' must be supplied. (400 INVALID_PARAMETER_VALUE) - -=== summary -OK 22 -SUPPRESSED 1 -NOT_OBSERVABLE 1 -BACKEND_ERROR 6 -SKIPPED 10 -BASE_ERROR 6 diff --git a/bundle/direct/autotest/output/clusters.txt b/bundle/direct/autotest/output/clusters.txt index 2f3ef8044cd..22aa326f28f 100644 --- a/bundle/direct/autotest/output/clusters.txt +++ b/bundle/direct/autotest/output/clusters.txt @@ -10,7 +10,7 @@ === summary -OK 383 +OK 377 SUPPRESSED 20 NOT_OBSERVABLE 9 -SKIPPED 10 +SKIPPED 11 diff --git a/bundle/direct/autotest/output/clusters.txt.backup b/bundle/direct/autotest/output/clusters.txt.backup deleted file mode 100644 index c6c93ce793c..00000000000 --- a/bundle/direct/autotest/output/clusters.txt.backup +++ /dev/null @@ -1,6 +0,0 @@ - -=== summary -OK 383 -SUPPRESSED 20 -NOT_OBSERVABLE 9 -SKIPPED 10 diff --git a/bundle/direct/autotest/output/dashboards.txt.backup b/bundle/direct/autotest/output/dashboards.txt.backup deleted file mode 100644 index c1d370a198e..00000000000 --- a/bundle/direct/autotest/output/dashboards.txt.backup +++ /dev/null @@ -1,12 +0,0 @@ -display_name absent x-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) -display_name absent y-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.dashboards.foo: [Request Validation] display name cannot be empty (400 INVALID_PARAMETER_VALUE) -display_name x-UNIQUE absent UPDATE_IGNORED -display_name y-UNIQUE absent UPDATE_IGNORED - -=== summary -OK 6 -SUPPRESSED 2 -NOT_OBSERVABLE 6 -UPDATE_IGNORED 2 -SKIPPED 11 -START_NOT_REACHED 2 diff --git a/bundle/direct/autotest/output/database_catalogs.txt.backup b/bundle/direct/autotest/output/database_catalogs.txt.backup deleted file mode 100644 index 02442d614ce..00000000000 --- a/bundle/direct/autotest/output/database_catalogs.txt.backup +++ /dev/null @@ -1,9 +0,0 @@ -database_instance_name x y BASE_ERROR cannot recreate resources.database_catalogs.foo: database instance with name 'x' not found (404 UNKNOWN) -database_instance_name y x BASE_ERROR cannot recreate resources.database_catalogs.foo: database instance with name 'y' not found (404 UNKNOWN) - -=== summary -OK_RECREATE 8 -SUPPRESSED 1 -NOT_OBSERVABLE 1 -SKIPPED 2 -BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/database_instances.txt.backup b/bundle/direct/autotest/output/database_instances.txt.backup deleted file mode 100644 index 00bd3a9c8cb..00000000000 --- a/bundle/direct/autotest/output/database_instances.txt.backup +++ /dev/null @@ -1,60 +0,0 @@ -capacity absent x UPDATE_IGNORED -capacity absent y UPDATE_IGNORED -capacity x y POST_DEPLOY_DRIFT capacity -capacity y x POST_DEPLOY_DRIFT capacity -custom_tags absent len1 COLLATERAL_DRIFT capacity -custom_tags len0 len1 COLLATERAL_DRIFT capacity -custom_tags len1 absent COLLATERAL_DRIFT capacity -custom_tags len1 len0 COLLATERAL_DRIFT capacity -custom_tags[0].key absent x COLLATERAL_DRIFT capacity -custom_tags[0].key absent y COLLATERAL_DRIFT capacity -custom_tags[0].key x absent COLLATERAL_DRIFT capacity -custom_tags[0].key x y COLLATERAL_DRIFT capacity -custom_tags[0].key y absent COLLATERAL_DRIFT capacity -custom_tags[0].key y x COLLATERAL_DRIFT capacity -custom_tags[0].value absent x COLLATERAL_DRIFT capacity -custom_tags[0].value absent y COLLATERAL_DRIFT capacity -custom_tags[0].value x absent COLLATERAL_DRIFT capacity -custom_tags[0].value x y COLLATERAL_DRIFT capacity -custom_tags[0].value y absent COLLATERAL_DRIFT capacity -custom_tags[0].value y x COLLATERAL_DRIFT capacity -enable_pg_native_login absent true COLLATERAL_DRIFT capacity -enable_pg_native_login false true COLLATERAL_DRIFT capacity -enable_pg_native_login true absent COLLATERAL_DRIFT capacity -enable_pg_native_login true false COLLATERAL_DRIFT capacity -enable_readable_secondaries absent true COLLATERAL_DRIFT capacity -enable_readable_secondaries false true COLLATERAL_DRIFT capacity -enable_readable_secondaries true absent COLLATERAL_DRIFT capacity -enable_readable_secondaries true false COLLATERAL_DRIFT capacity -node_count 1 2 COLLATERAL_DRIFT capacity -node_count 1 absent COLLATERAL_DRIFT capacity -node_count 2 1 COLLATERAL_DRIFT capacity -node_count 2 absent COLLATERAL_DRIFT capacity -node_count absent 1 COLLATERAL_DRIFT capacity -node_count absent 2 COLLATERAL_DRIFT capacity -retention_window_in_days 1 2 COLLATERAL_DRIFT capacity -retention_window_in_days 1 absent COLLATERAL_DRIFT capacity -retention_window_in_days 2 1 COLLATERAL_DRIFT capacity -retention_window_in_days 2 absent COLLATERAL_DRIFT capacity -retention_window_in_days absent 1 COLLATERAL_DRIFT capacity -retention_window_in_days absent 2 COLLATERAL_DRIFT capacity -stopped absent true COLLATERAL_DRIFT capacity -stopped false true COLLATERAL_DRIFT capacity -stopped true absent COLLATERAL_DRIFT capacity -stopped true false COLLATERAL_DRIFT capacity -usage_policy_id absent x COLLATERAL_DRIFT capacity -usage_policy_id absent y COLLATERAL_DRIFT capacity -usage_policy_id x absent COLLATERAL_DRIFT capacity -usage_policy_id x y COLLATERAL_DRIFT capacity -usage_policy_id y absent COLLATERAL_DRIFT capacity -usage_policy_id y x COLLATERAL_DRIFT capacity - -=== summary -OK 2 -OK_RECREATE 32 -SUPPRESSED 6 -NOT_OBSERVABLE 2 -UPDATE_IGNORED 2 -POST_DEPLOY_DRIFT 2 -COLLATERAL_DRIFT 46 -SKIPPED 24 diff --git a/bundle/direct/autotest/output/experiments.txt.backup b/bundle/direct/autotest/output/experiments.txt.backup deleted file mode 100644 index a1b5273daac..00000000000 --- a/bundle/direct/autotest/output/experiments.txt.backup +++ /dev/null @@ -1,6 +0,0 @@ - -=== summary -OK 2 -OK_RECREATE 12 -OK_INERT 18 -SKIPPED 2 diff --git a/bundle/direct/autotest/output/external_locations.txt.backup b/bundle/direct/autotest/output/external_locations.txt.backup deleted file mode 100644 index 59c5ff2ea14..00000000000 --- a/bundle/direct/autotest/output/external_locations.txt.backup +++ /dev/null @@ -1,7 +0,0 @@ - -=== summary -OK 26 -OK_RECREATE 110 -SUPPRESSED 5 -NOT_OBSERVABLE 3 -SKIPPED 18 diff --git a/bundle/direct/autotest/output/genie_spaces.txt.backup b/bundle/direct/autotest/output/genie_spaces.txt.backup deleted file mode 100644 index 29fdc33e9f8..00000000000 --- a/bundle/direct/autotest/output/genie_spaces.txt.backup +++ /dev/null @@ -1,10 +0,0 @@ -description x absent UPDATE_IGNORED -description y absent UPDATE_IGNORED -title x-UNIQUE absent UPDATE_IGNORED -title y-UNIQUE absent UPDATE_IGNORED - -=== summary -OK 8 -NOT_OBSERVABLE 6 -UPDATE_IGNORED 4 -SKIPPED 4 diff --git a/bundle/direct/autotest/output/instance_pools.txt.backup b/bundle/direct/autotest/output/instance_pools.txt.backup deleted file mode 100644 index ebf95e63e98..00000000000 --- a/bundle/direct/autotest/output/instance_pools.txt.backup +++ /dev/null @@ -1,10 +0,0 @@ -enable_elastic_disk false true UPDATE_IGNORED -enable_elastic_disk true false UPDATE_IGNORED - -=== summary -OK 45 -OK_RECREATE 46 -SUPPRESSED 5 -NOT_OBSERVABLE 2 -UPDATE_IGNORED 2 -SKIPPED 6 diff --git a/bundle/direct/autotest/output/job_runs.txt.backup b/bundle/direct/autotest/output/job_runs.txt.backup deleted file mode 100644 index b0d1dbf87a4..00000000000 --- a/bundle/direct/autotest/output/job_runs.txt.backup +++ /dev/null @@ -1,6 +0,0 @@ - -=== summary -OK_RECREATE 109 -SUPPRESSED 9 -NOT_OBSERVABLE 12 -SKIPPED 1 diff --git a/bundle/direct/autotest/output/jobs.txt.backup b/bundle/direct/autotest/output/jobs.txt.backup deleted file mode 100644 index 67532dc8338..00000000000 --- a/bundle/direct/autotest/output/jobs.txt.backup +++ /dev/null @@ -1,14 +0,0 @@ -parent_path absent x UPDATE_IGNORED -parent_path absent y UPDATE_IGNORED -parent_path x absent UPDATE_IGNORED -parent_path x y UPDATE_IGNORED -parent_path y absent UPDATE_IGNORED -parent_path y x UPDATE_IGNORED - -=== summary -OK 561 -OK_INERT 6 -SUPPRESSED 47 -NOT_OBSERVABLE 34 -UPDATE_IGNORED 6 -SKIPPED 54 diff --git a/bundle/direct/autotest/output/model_serving_endpoints.txt.backup b/bundle/direct/autotest/output/model_serving_endpoints.txt.backup deleted file mode 100644 index c98c8725ae7..00000000000 --- a/bundle/direct/autotest/output/model_serving_endpoints.txt.backup +++ /dev/null @@ -1,18 +0,0 @@ -# field | from | to | verdict | detail -# -# How the direct engine handles every field of this resource type: each row is one move of one field -# between two values, "absent" included. An outcome the engine got wrong is recorded here rather than -# failed, so this file is a description of current behaviour -- what fails the test is a verdict -# changing. Passing rows are counted in the summary and listed in logs/.full.txt, along with -# the evidence behind each finding and what could not be covered. -# -# Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. - - -=== summary -OK 24 -OK_RECREATE 12 -OK_INERT 6 -SUPPRESSED 3 -NOT_OBSERVABLE 1 -SKIPPED 4 diff --git a/bundle/direct/autotest/output/models.txt.backup b/bundle/direct/autotest/output/models.txt.backup deleted file mode 100644 index d573aee7b0a..00000000000 --- a/bundle/direct/autotest/output/models.txt.backup +++ /dev/null @@ -1,9 +0,0 @@ -description x absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) -description y absent BACKEND_ERROR cannot update resources.models.foo: updating id=test-model-[UNIQUE_NAME]: Description cannot be empty. (400 INVALID_PARAMETER_VALUE) - -=== summary -OK 4 -OK_RECREATE 2 -OK_INERT 18 -BACKEND_ERROR 2 -SKIPPED 1 diff --git a/bundle/direct/autotest/output/pipelines.txt.backup b/bundle/direct/autotest/output/pipelines.txt.backup deleted file mode 100644 index 0ae900d0f8a..00000000000 --- a/bundle/direct/autotest/output/pipelines.txt.backup +++ /dev/null @@ -1,19 +0,0 @@ -catalog absent x BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) -catalog absent y BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) -catalog x absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) -catalog x y BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) -catalog y absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) -catalog y x BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) -dry_run absent true UPDATE_IGNORED -dry_run false true UPDATE_IGNORED - -=== summary -OK 696 -OK_RECREATE 9 -OK_INERT 12 -SUPPRESSED 44 -NOT_OBSERVABLE 29 -UPDATE_IGNORED 2 -BACKEND_ERROR 2 -SKIPPED 17 -BASE_ERROR 4 diff --git a/bundle/direct/autotest/output/postgres_branches.txt.backup b/bundle/direct/autotest/output/postgres_branches.txt.backup deleted file mode 100644 index 6f4d47376ab..00000000000 --- a/bundle/direct/autotest/output/postgres_branches.txt.backup +++ /dev/null @@ -1,15 +0,0 @@ -is_protected true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -no_expiry absent false BASE_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -no_expiry true absent BACKEND_ERROR cannot update resources.postgres_branches.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -parent x y BASE_ERROR cannot recreate resources.postgres_branches.foo: No stub found for pattern: POST /api/2.0/postgres/x/branches (501 ) -parent y x BASE_ERROR cannot recreate resources.postgres_branches.foo: No stub found for pattern: POST /api/2.0/postgres/y/branches (501 ) - -=== summary -OK 10 -OK_RECREATE 14 -OK_INERT 6 -SUPPRESSED 4 -NOT_OBSERVABLE 1 -BACKEND_ERROR 2 -SKIPPED 1 -BASE_ERROR 3 diff --git a/bundle/direct/autotest/output/postgres_catalogs.txt.backup b/bundle/direct/autotest/output/postgres_catalogs.txt.backup deleted file mode 100644 index 1bd2a026bd8..00000000000 --- a/bundle/direct/autotest/output/postgres_catalogs.txt.backup +++ /dev/null @@ -1,6 +0,0 @@ - -=== summary -OK_RECREATE 14 -SUPPRESSED 1 -NOT_OBSERVABLE 1 -SKIPPED 1 diff --git a/bundle/direct/autotest/output/postgres_databases.txt.backup b/bundle/direct/autotest/output/postgres_databases.txt.backup deleted file mode 100644 index f8112aa8e12..00000000000 --- a/bundle/direct/autotest/output/postgres_databases.txt.backup +++ /dev/null @@ -1,9 +0,0 @@ -parent x y BASE_ERROR cannot recreate resources.postgres_databases.foo: No stub found for pattern: POST /api/2.0/postgres/x/databases (501 ) -parent y x BASE_ERROR cannot recreate resources.postgres_databases.foo: No stub found for pattern: POST /api/2.0/postgres/y/databases (501 ) - -=== summary -OK 8 -OK_RECREATE 2 -OK_INERT 6 -SKIPPED 1 -BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/postgres_endpoints.txt.backup b/bundle/direct/autotest/output/postgres_endpoints.txt.backup deleted file mode 100644 index d0709dd2da2..00000000000 --- a/bundle/direct/autotest/output/postgres_endpoints.txt.backup +++ /dev/null @@ -1,27 +0,0 @@ -autoscaling_limit_max_cu 1 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) -autoscaling_limit_max_cu 2 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) -autoscaling_limit_min_cu 1 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) -autoscaling_limit_min_cu 2 absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) -disabled true absent BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ovided in request (400 INVALID_PARAMETER_VALUE) -group.enable_readable_secondaries absent false BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -group.enable_readable_secondaries absent true BACKEND_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -group.enable_readable_secondaries false absent BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -group.enable_readable_secondaries false true BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -group.enable_readable_secondaries true absent BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -group.enable_readable_secondaries true false BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -group.max 1 2 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -group.max 2 1 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -group.min 1 2 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -group.min 2 1 BASE_ERROR cannot update resources.postgres_endpoints.foo: updating id=projects/test-pg-project-[UNIQ...ask: 'spec.group' (400 INVALID_PARAMETER_VALUE) -parent x y BASE_ERROR cannot recreate resources.postgres_endpoints.foo: No stub found for pattern: POST /api/2.0/postgres/x/endpoints (501 ) -parent y x BASE_ERROR cannot recreate resources.postgres_endpoints.foo: No stub found for pattern: POST /api/2.0/postgres/y/endpoints (501 ) - -=== summary -OK 15 -OK_RECREATE 4 -OK_INERT 6 -SUPPRESSED 2 -NOT_OBSERVABLE 2 -BACKEND_ERROR 7 -SKIPPED 1 -BASE_ERROR 10 diff --git a/bundle/direct/autotest/output/postgres_projects.txt.backup b/bundle/direct/autotest/output/postgres_projects.txt.backup deleted file mode 100644 index fa3334a235b..00000000000 --- a/bundle/direct/autotest/output/postgres_projects.txt.backup +++ /dev/null @@ -1,35 +0,0 @@ -budget_policy_id x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -budget_policy_id y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -custom_tags absent len0 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -custom_tags absent len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -custom_tags len0 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -custom_tags len0 len1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -custom_tags len1 absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -custom_tags len1 len0 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_max_cu 1 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_max_cu 1 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_max_cu 2 1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_max_cu 2 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_max_cu absent 1 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_max_cu absent 2 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_min_cu 1 2 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_min_cu 1 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_min_cu 2 1 BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_min_cu 2 absent BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_min_cu absent 1 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.autoscaling_limit_min_cu absent 2 BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.no_suspension false absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -default_endpoint_settings.no_suspension true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -display_name absent x BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -display_name absent y BASE_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -display_name x absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -display_name y absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) -enable_pg_native_login true absent BACKEND_ERROR cannot update resources.postgres_projects.foo: updating id=projects/test-pg-project-[UNIQU...ovided in request (400 INVALID_PARAMETER_VALUE) - -=== summary -OK 29 -OK_RECREATE 8 -SUPPRESSED 4 -BACKEND_ERROR 13 -SKIPPED 3 -BASE_ERROR 14 diff --git a/bundle/direct/autotest/output/postgres_roles.txt.backup b/bundle/direct/autotest/output/postgres_roles.txt.backup deleted file mode 100644 index 66dedd17dd9..00000000000 --- a/bundle/direct/autotest/output/postgres_roles.txt.backup +++ /dev/null @@ -1,11 +0,0 @@ -parent x y BASE_ERROR cannot recreate resources.postgres_roles.foo: No stub found for pattern: POST /api/2.0/postgres/x/roles (501 ) -parent y x BASE_ERROR cannot recreate resources.postgres_roles.foo: No stub found for pattern: POST /api/2.0/postgres/y/roles (501 ) - -=== summary -OK 22 -OK_RECREATE 20 -OK_INERT 6 -SUPPRESSED 1 -NOT_OBSERVABLE 1 -SKIPPED 1 -BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/postgres_synced_tables.txt.backup b/bundle/direct/autotest/output/postgres_synced_tables.txt.backup deleted file mode 100644 index e6fae0817e4..00000000000 --- a/bundle/direct/autotest/output/postgres_synced_tables.txt.backup +++ /dev/null @@ -1,6 +0,0 @@ - -=== summary -OK_RECREATE 110 -SUPPRESSED 6 -NOT_OBSERVABLE 4 -SKIPPED 1 diff --git a/bundle/direct/autotest/output/quality_monitors.txt.backup b/bundle/direct/autotest/output/quality_monitors.txt.backup deleted file mode 100644 index 7683b7d5785..00000000000 --- a/bundle/direct/autotest/output/quality_monitors.txt.backup +++ /dev/null @@ -1,12 +0,0 @@ -latest_monitor_failure_msg absent x POST_DEPLOY_DRIFT latest_monitor_failure_msg -latest_monitor_failure_msg absent y POST_DEPLOY_DRIFT latest_monitor_failure_msg -latest_monitor_failure_msg x y POST_DEPLOY_DRIFT latest_monitor_failure_msg -latest_monitor_failure_msg y x POST_DEPLOY_DRIFT latest_monitor_failure_msg - -=== summary -OK 100 -OK_RECREATE 4 -SUPPRESSED 6 -NOT_OBSERVABLE 4 -POST_DEPLOY_DRIFT 4 -SKIPPED 1 diff --git a/bundle/direct/autotest/output/registered_models.txt.backup b/bundle/direct/autotest/output/registered_models.txt.backup deleted file mode 100644 index 384a73e8855..00000000000 --- a/bundle/direct/autotest/output/registered_models.txt.backup +++ /dev/null @@ -1,17 +0,0 @@ -full_name absent x UPDATE_IGNORED -full_name absent y UPDATE_IGNORED -full_name x y UPDATE_IGNORED -full_name y x UPDATE_IGNORED -owner absent x UPDATE_IGNORED -owner absent y UPDATE_IGNORED -owner x y UPDATE_IGNORED -owner y x UPDATE_IGNORED - -=== summary -OK 44 -OK_RECREATE 2 -OK_ID_FIELD_REQUIRED 8 -SUPPRESSED 1 -NOT_OBSERVABLE 1 -UPDATE_IGNORED 8 -SKIPPED 9 diff --git a/bundle/direct/autotest/output/schemas.txt.backup b/bundle/direct/autotest/output/schemas.txt.backup deleted file mode 100644 index 0d65e6f3fb3..00000000000 --- a/bundle/direct/autotest/output/schemas.txt.backup +++ /dev/null @@ -1,14 +0,0 @@ -custom_max_retention_hours 168 absent UPDATE_IGNORED -custom_max_retention_hours 720 absent UPDATE_IGNORED -properties keys1 absent UPDATE_IGNORED -properties keys1 keys0 UPDATE_IGNORED -properties['team'] x absent UPDATE_IGNORED -properties['team'] y absent UPDATE_IGNORED - -=== summary -OK 16 -OK_RECREATE 2 -SUPPRESSED 1 -NOT_OBSERVABLE 1 -UPDATE_IGNORED 6 -SKIPPED 2 diff --git a/bundle/direct/autotest/output/secret_scopes.txt.backup b/bundle/direct/autotest/output/secret_scopes.txt.backup deleted file mode 100644 index bb9f6335fda..00000000000 --- a/bundle/direct/autotest/output/secret_scopes.txt.backup +++ /dev/null @@ -1,7 +0,0 @@ -backend_type AZURE_KEYVAULT DATABRICKS BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyV...Metadata defined! (400 INVALID_PARAMETER_VALUE) -backend_type AZURE_KEYVAULT absent BASE_ERROR cannot recreate resources.secret_scopes.foo: Scope with Azure KeyVault must have AzureKeyV...Metadata defined! (400 INVALID_PARAMETER_VALUE) - -=== summary -NOT_OBSERVABLE 10 -SKIPPED 1 -BASE_ERROR 2 diff --git a/bundle/direct/autotest/output/secrets.txt.backup b/bundle/direct/autotest/output/secrets.txt.backup deleted file mode 100644 index 6ad75fbeacb..00000000000 --- a/bundle/direct/autotest/output/secrets.txt.backup +++ /dev/null @@ -1,15 +0,0 @@ -comment x absent UPDATE_IGNORED -comment y absent UPDATE_IGNORED -owner absent x SUPPRESSED spec:input_only -owner absent y SUPPRESSED spec:input_only -owner x absent SUPPRESSED spec:input_only -owner x y SUPPRESSED spec:input_only -owner y absent SUPPRESSED spec:input_only -owner y x SUPPRESSED spec:input_only - -=== summary -OK 4 -OK_RECREATE 2 -SUPPRESSED 6 -UPDATE_IGNORED 2 -SKIPPED 10 diff --git a/bundle/direct/autotest/output/sql_warehouses.txt.backup b/bundle/direct/autotest/output/sql_warehouses.txt.backup deleted file mode 100644 index ce4a674a677..00000000000 --- a/bundle/direct/autotest/output/sql_warehouses.txt.backup +++ /dev/null @@ -1,24 +0,0 @@ -auto_stop_mins 10 absent UPDATE_IGNORED -auto_stop_mins 20 absent UPDATE_IGNORED -cluster_size 2X-Small absent UPDATE_IGNORED -cluster_size X-Small absent UPDATE_IGNORED -cluster_size absent 2X-Small START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) -cluster_size absent X-Small START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Required field 'cluster_size' is missing. (400 INVALID_PARAMETER_VALUE) -enable_photon true absent UPDATE_IGNORED -max_num_clusters 2 absent UPDATE_IGNORED -max_num_clusters 3 absent UPDATE_IGNORED -max_num_clusters absent 2 START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) -max_num_clusters absent 3 START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: 0 is not a valid value for max_num_clusters. T...n or equal to 40. (400 INVALID_PARAMETER_VALUE) -name absent x-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) -name absent y-UNIQUE START_NOT_REACHED cannot create the resource with this starting value: cannot create resources.sql_warehouses.foo: Invalid value for SQL Endpoint name, it cannot be empty. (400 INVALID_PARAMETER_VALUE) -name x-UNIQUE absent UPDATE_IGNORED -name y-UNIQUE absent UPDATE_IGNORED -spot_instance_policy COST_OPTIMIZED absent UPDATE_IGNORED -spot_instance_policy RELIABILITY_OPTIMIZED absent UPDATE_IGNORED - -=== summary -OK 36 -SUPPRESSED 3 -UPDATE_IGNORED 11 -SKIPPED 5 -START_NOT_REACHED 6 diff --git a/bundle/direct/autotest/output/synced_database_tables.txt.backup b/bundle/direct/autotest/output/synced_database_tables.txt.backup deleted file mode 100644 index cc1a2da8947..00000000000 --- a/bundle/direct/autotest/output/synced_database_tables.txt.backup +++ /dev/null @@ -1,14 +0,0 @@ -database_instance_name absent x DEPLOY_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) -database_instance_name absent y DEPLOY_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) -database_instance_name x absent BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) -database_instance_name x y BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'x' not found (404 UNKNOWN) -database_instance_name y absent BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) -database_instance_name y x BASE_ERROR cannot recreate resources.synced_database_tables.foo: database instance with name 'y' not found (404 UNKNOWN) - -=== summary -OK_RECREATE 68 -SUPPRESSED 4 -NOT_OBSERVABLE 2 -DEPLOY_ERROR 2 -SKIPPED 35 -BASE_ERROR 4 diff --git a/bundle/direct/autotest/output/vector_search_endpoints.txt.backup b/bundle/direct/autotest/output/vector_search_endpoints.txt.backup deleted file mode 100644 index 4efc342c607..00000000000 --- a/bundle/direct/autotest/output/vector_search_endpoints.txt.backup +++ /dev/null @@ -1,8 +0,0 @@ -target_qps 1 absent UPDATE_IGNORED -target_qps 2 absent UPDATE_IGNORED - -=== summary -OK 10 -OK_RECREATE 4 -UPDATE_IGNORED 2 -SKIPPED 2 diff --git a/bundle/direct/autotest/output/vector_search_indexes.txt.backup b/bundle/direct/autotest/output/vector_search_indexes.txt.backup deleted file mode 100644 index 4867308ae1c..00000000000 --- a/bundle/direct/autotest/output/vector_search_indexes.txt.backup +++ /dev/null @@ -1,11 +0,0 @@ -endpoint_name x y BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint x not found (404 RESOURCE_DOES_NOT_EXIST) -endpoint_name y x BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint y not found (404 RESOURCE_DOES_NOT_EXIST) -name x-UNIQUE y-UNIQUE BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Invalid index name. Must specify the ...ores are allowed. (400 INVALID_PARAMETER_VALUE) -name y-UNIQUE x-UNIQUE BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Invalid index name. Must specify the ...ores are allowed. (400 INVALID_PARAMETER_VALUE) - -=== summary -OK_RECREATE 69 -SUPPRESSED 4 -NOT_OBSERVABLE 3 -SKIPPED 1 -BASE_ERROR 4 diff --git a/bundle/direct/autotest/output/volumes.txt.backup b/bundle/direct/autotest/output/volumes.txt.backup deleted file mode 100644 index db4540165a2..00000000000 --- a/bundle/direct/autotest/output/volumes.txt.backup +++ /dev/null @@ -1,4 +0,0 @@ - -=== summary -OK 8 -SKIPPED 2 diff --git a/bundle/direct/autotest/report_test.go b/bundle/direct/autotest/report_test.go index 13e4456bea5..2cfe83946da 100644 --- a/bundle/direct/autotest/report_test.go +++ b/bundle/direct/autotest/report_test.go @@ -528,12 +528,16 @@ func writeReport(t testutil.TestingT, name, body string) { t.Errorf("creating %s: %s", filepath.Dir(name), err) return } - // The previous run's copy is kept next to the new one: reading what a change did to a report - // means diffing the two, and the full report is not in git to diff against. - if previous, err := os.ReadFile(name); err == nil { - if err := os.WriteFile(name+".backup", previous, 0o644); err != nil { - t.Errorf("backing up %s: %s", name, err) - return + // The previous copy of a log is kept next to the new one: reading what a change did to a full + // report means diffing the two, and it is not in git to diff against. Not for a committed + // golden -- git is its history, and a .backup beside it would be an untracked file in the one + // directory that holds only tracked ones. + if strings.HasPrefix(name, logsDir+string(os.PathSeparator)) { + if previous, err := os.ReadFile(name); err == nil { + if err := os.WriteFile(name+".backup", previous, 0o644); err != nil { + t.Errorf("backing up %s: %s", name, err) + return + } } } if err := os.WriteFile(name, []byte(body), 0o644); err != nil { diff --git a/bundle/direct/autotest/testdata/data/init.sh b/bundle/direct/autotest/testdata/data/init.sh new file mode 100644 index 00000000000..8bb4c42918f --- /dev/null +++ b/bundle/direct/autotest/testdata/data/init.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# A cluster init script that does nothing, uploaded with the bundle so a fixture declaring one names a +# file that exists. A real workspace fails the cluster otherwise -- "Init scripts failed ... Tree node +# with path ... does not exist" -- where the mock server accepts any path. +echo "init" diff --git a/bundle/direct/autotest/testdata/fields/clusters.yml b/bundle/direct/autotest/testdata/fields/clusters.yml index d3909007032..bb4f4fad440 100644 --- a/bundle/direct/autotest/testdata/fields/clusters.yml +++ b/bundle/direct/autotest/testdata/fields/clusters.yml @@ -20,7 +20,10 @@ base: - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQtest init_scripts: - workspace: - destination: /Users/$CURRENT_USER_NAME/init.sh + # The file the bundle uploads, not a path in the user home: a real workspace fails the + # cluster with "Init scripts failed ... Tree node with path ... does not exist" when the + # script is not there, and the mock server accepts any path. + destination: /Workspace/Users/$CURRENT_USER_NAME/.bundle/test-bundle-$UNIQUE_NAME/default/files/init.sh fields: # The API refuses anything between 1 and 9: "The cluster autotermination time cannot be less than @@ -29,6 +32,9 @@ fields: autotermination_minutes: [10, 20] skip: + # A cluster policy has to exist: "'x' is not a valid cluster policy ID." The fake server takes any + # string, so these rows only ever measured the fake. + policy_id: needs a cluster policy to exist # An init script entry carries exactly one of workspace, volumes, s3, abfss, gcs, dbfs or file, # and base seeds the workspace member. Setting a leaf inside another arm leaves that arm without # its own destination, which the backend accepts and then ignores -- every such row was recording diff --git a/bundle/direct/autotest/testdata/fields/jobs.yml b/bundle/direct/autotest/testdata/fields/jobs.yml index 624dae72b2d..5477946f2ed 100644 --- a/bundle/direct/autotest/testdata/fields/jobs.yml +++ b/bundle/direct/autotest/testdata/fields/jobs.yml @@ -31,7 +31,10 @@ base: spark.speculation: "true" init_scripts: - workspace: - destination: /Users/$CURRENT_USER_NAME/init.sh + # The file the bundle uploads, not a path in the user home: a real workspace fails the + # cluster with "Init scripts failed ... Tree node with path ... does not exist" when the + # script is not there, and the mock server accepts any path. + destination: /Workspace/Users/$CURRENT_USER_NAME/.bundle/test-bundle-$UNIQUE_NAME/default/files/init.sh environments: - environment_key: seeded spec: diff --git a/bundle/direct/autotest/testdata/fields/pipelines.yml b/bundle/direct/autotest/testdata/fields/pipelines.yml index 8ed8b82ebab..d39297cd100 100644 --- a/bundle/direct/autotest/testdata/fields/pipelines.yml +++ b/bundle/direct/autotest/testdata/fields/pipelines.yml @@ -19,7 +19,10 @@ base: - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQtest init_scripts: - workspace: - destination: /Users/$CURRENT_USER_NAME/init.sh + # The file the bundle uploads, not a path in the user home: a real workspace fails the + # cluster with "Init scripts failed ... Tree node with path ... does not exist" when the + # script is not there, and the mock server accepts any path. + destination: /Workspace/Users/$CURRENT_USER_NAME/.bundle/test-bundle-$UNIQUE_NAME/default/files/init.sh configuration: my.key: value environment: From c0d5c6e7dba1f61b2af0e2bed2f38bb7df4fb251 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 2 Sep 2026 10:42:46 +0200 Subject: [PATCH 112/118] Field catalog: no init script path can exist, and an app scope has to be real The clusters cloud run failed at the create even after the previous commit pointed the init script at the file the bundle uploads. The reason is in the harness's own doc comment: this suite deliberately uploads nothing, because a sync per permutation would dominate the run. So no destination a fixture could name is ever there, and the mock server's acceptance of any path is the only thing that made the field look testable. init_scripts is skipped in all three fixtures that seeded it, with that reason, and the data directory the previous commit added is gone with it. user_api_scopes needed real OAuth scopes: the API refuses anything else outright ("The specified scope x is not a valid scope"), and the mock server takes any string, so eight rows had only measured the fake. sql and dashboards.genie both verified against the API, declared for the list as well as the element. Co-authored-by: Isaac --- bundle/direct/autotest/fixtures_test.go | 5 ----- bundle/direct/autotest/harness_test.go | 3 --- bundle/direct/autotest/output/apps.txt | 10 ++++------ bundle/direct/autotest/output/clusters.txt | 8 ++++---- bundle/direct/autotest/output/jobs.txt | 8 ++++---- bundle/direct/autotest/output/pipelines.txt | 8 ++++---- bundle/direct/autotest/testdata/data/init.sh | 5 ----- bundle/direct/autotest/testdata/fields/apps.yml | 8 ++++++++ bundle/direct/autotest/testdata/fields/clusters.yml | 11 +++++------ bundle/direct/autotest/testdata/fields/jobs.yml | 11 +++++------ bundle/direct/autotest/testdata/fields/pipelines.yml | 11 +++++------ 11 files changed, 39 insertions(+), 49 deletions(-) delete mode 100644 bundle/direct/autotest/testdata/data/init.sh diff --git a/bundle/direct/autotest/fixtures_test.go b/bundle/direct/autotest/fixtures_test.go index 7bb6e2e9290..b86d4959f2d 100644 --- a/bundle/direct/autotest/fixtures_test.go +++ b/bundle/direct/autotest/fixtures_test.go @@ -19,11 +19,6 @@ import ( // themselves live in testdata/fields and belong to this suite alone. const dataDir = "../../../acceptance/bundle/invariant/data" -// ownDataDir holds files only this suite needs, staged next to the shared ones. Kept separate -// because everything in dataDir is uploaded by every invariant config too, so a file added there -// for one fixture changes those tests' output. -const ownDataDir = "testdata/data" - // drivenTypes returns every resource type the direct engine supports, which is what this suite // covers: a type with no value library is a failure rather than a line in a report, so adding a // resource type to the engine and forgetting the catalog cannot go unnoticed. diff --git a/bundle/direct/autotest/harness_test.go b/bundle/direct/autotest/harness_test.go index e5d35f0fbf5..23ce0b4b186 100644 --- a/bundle/direct/autotest/harness_test.go +++ b/bundle/direct/autotest/harness_test.go @@ -171,9 +171,6 @@ func newHarness(t *testing.T, ctx context.Context, client *databricks.WorkspaceC if err := copyDir(dataDir, dir); err != nil { return nil, err } - if err := copyDir(ownDataDir, dir); err != nil { - return nil, err - } rememberWorkspaceUser(user.UserName) yml, err := renderBundle(resourceType, uniqueName, user.UserName, base, deps, variables) diff --git a/bundle/direct/autotest/output/apps.txt b/bundle/direct/autotest/output/apps.txt index dc2729c46c8..fff6efd3e6b 100644 --- a/bundle/direct/autotest/output/apps.txt +++ b/bundle/direct/autotest/output/apps.txt @@ -100,16 +100,14 @@ source_code_path x-UNIQUE y-UNIQUE START_NOT_REA source_code_path y-UNIQUE absent START_NOT_REACHED no active deployment source_code_path y-UNIQUE x-UNIQUE START_NOT_REACHED no active deployment user_api_scopes len1 absent UPDATE_IGNORED -user_api_scopes len1 len0 UPDATE_IGNORED user_api_scopes len2 absent UPDATE_IGNORED -user_api_scopes len2 len0 UPDATE_IGNORED === summary -OK 35 +OK 33 OK_RECREATE 2 -SUPPRESSED 23 -NOT_OBSERVABLE 3 -UPDATE_IGNORED 9 +SUPPRESSED 22 +NOT_OBSERVABLE 2 +UPDATE_IGNORED 7 UNSETTABLE 2 SKIPPED 90 START_NOT_REACHED 68 diff --git a/bundle/direct/autotest/output/clusters.txt b/bundle/direct/autotest/output/clusters.txt index 22aa326f28f..07831b653e1 100644 --- a/bundle/direct/autotest/output/clusters.txt +++ b/bundle/direct/autotest/output/clusters.txt @@ -10,7 +10,7 @@ === summary -OK 377 -SUPPRESSED 20 -NOT_OBSERVABLE 9 -SKIPPED 11 +OK 371 +SUPPRESSED 19 +NOT_OBSERVABLE 8 +SKIPPED 12 diff --git a/bundle/direct/autotest/output/jobs.txt b/bundle/direct/autotest/output/jobs.txt index 7986c428408..8217e71005a 100644 --- a/bundle/direct/autotest/output/jobs.txt +++ b/bundle/direct/autotest/output/jobs.txt @@ -16,9 +16,9 @@ parent_path y absent UPDATE_IGNORE parent_path y x UPDATE_IGNORED === summary -OK 561 +OK 515 OK_INERT 6 -SUPPRESSED 47 -NOT_OBSERVABLE 34 +SUPPRESSED 44 +NOT_OBSERVABLE 33 UPDATE_IGNORED 6 -SKIPPED 54 +SKIPPED 55 diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index 88551059d55..3ecd67090a9 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -18,12 +18,12 @@ dry_run absent true UPDATE_IGNORE dry_run false true UPDATE_IGNORED === summary -OK 696 +OK 690 OK_RECREATE 9 OK_INERT 12 -SUPPRESSED 44 -NOT_OBSERVABLE 29 +SUPPRESSED 43 +NOT_OBSERVABLE 28 UPDATE_IGNORED 2 BACKEND_ERROR 2 -SKIPPED 17 +SKIPPED 18 BASE_ERROR 4 diff --git a/bundle/direct/autotest/testdata/data/init.sh b/bundle/direct/autotest/testdata/data/init.sh deleted file mode 100644 index 8bb4c42918f..00000000000 --- a/bundle/direct/autotest/testdata/data/init.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -# A cluster init script that does nothing, uploaded with the bundle so a fixture declaring one names a -# file that exists. A real workspace fails the cluster otherwise -- "Init scripts failed ... Tree node -# with path ... does not exist" -- where the mock server accepts any path. -echo "init" diff --git a/bundle/direct/autotest/testdata/fields/apps.yml b/bundle/direct/autotest/testdata/fields/apps.yml index 6161dec7426..4c6588a525c 100644 --- a/bundle/direct/autotest/testdata/fields/apps.yml +++ b/bundle/direct/autotest/testdata/fields/apps.yml @@ -19,6 +19,14 @@ base: value: value fields: + # Real OAuth scopes, verified against the API: it refuses anything else outright ("The specified + # scope x is not a valid scope"), and the mock server takes any string. Declared for the list as + # well as the element, so growing the list uses scopes the backend accepts. + user_api_scopes: + - [sql] + - [dashboards.genie, sql] + user_api_scopes[*]: [sql, dashboards.genie] + compute_size: [MEDIUM, LARGE] skip: diff --git a/bundle/direct/autotest/testdata/fields/clusters.yml b/bundle/direct/autotest/testdata/fields/clusters.yml index bb4f4fad440..9a42379bcce 100644 --- a/bundle/direct/autotest/testdata/fields/clusters.yml +++ b/bundle/direct/autotest/testdata/fields/clusters.yml @@ -18,12 +18,6 @@ base: MY_VAR: value ssh_public_keys: - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQtest - init_scripts: - - workspace: - # The file the bundle uploads, not a path in the user home: a real workspace fails the - # cluster with "Init scripts failed ... Tree node with path ... does not exist" when the - # script is not there, and the mock server accepts any path. - destination: /Workspace/Users/$CURRENT_USER_NAME/.bundle/test-bundle-$UNIQUE_NAME/default/files/init.sh fields: # The API refuses anything between 1 and 9: "The cluster autotermination time cannot be less than @@ -32,6 +26,11 @@ fields: autotermination_minutes: [10, 20] skip: + # An init script has to name a file that exists: a real workspace fails the cluster with "Init + # scripts failed ... Tree node with path ... does not exist". This suite deliberately uploads no + # bundle files -- a sync per permutation would dominate the run -- so no destination it could name + # is there, and the mock server's acceptance of any path is what made the field look testable. + init_scripts: an init script has to name a file that exists, and this suite uploads none # A cluster policy has to exist: "'x' is not a valid cluster policy ID." The fake server takes any # string, so these rows only ever measured the fake. policy_id: needs a cluster policy to exist diff --git a/bundle/direct/autotest/testdata/fields/jobs.yml b/bundle/direct/autotest/testdata/fields/jobs.yml index 5477946f2ed..c924fb9b318 100644 --- a/bundle/direct/autotest/testdata/fields/jobs.yml +++ b/bundle/direct/autotest/testdata/fields/jobs.yml @@ -29,12 +29,6 @@ base: team: eng spark_conf: spark.speculation: "true" - init_scripts: - - workspace: - # The file the bundle uploads, not a path in the user home: a real workspace fails the - # cluster with "Init scripts failed ... Tree node with path ... does not exist" when the - # script is not there, and the mock server accepts any path. - destination: /Workspace/Users/$CURRENT_USER_NAME/.bundle/test-bundle-$UNIQUE_NAME/default/files/init.sh environments: - environment_key: seeded spec: @@ -64,6 +58,11 @@ base: unit: HOURS skip: + # An init script has to name a file that exists: a real workspace fails the cluster with "Init + # scripts failed ... Tree node with path ... does not exist". This suite deliberately uploads no + # bundle files -- a sync per permutation would dominate the run -- so no destination it could name + # is there, and the mock server's acceptance of any path is what made the field look testable. + job_clusters[*].new_cluster.init_scripts: an init script has to name a file that exists, and this suite uploads none # git_branch and git_tag are the other two sides of the same oneof as git_commit, which # base sets. Adding one alongside it is rejected for being ambiguous, not for anything # this suite measures. Covered by acceptance/bundle/resources/jobs. diff --git a/bundle/direct/autotest/testdata/fields/pipelines.yml b/bundle/direct/autotest/testdata/fields/pipelines.yml index d39297cd100..5d895fa60f0 100644 --- a/bundle/direct/autotest/testdata/fields/pipelines.yml +++ b/bundle/direct/autotest/testdata/fields/pipelines.yml @@ -17,12 +17,6 @@ base: MY_VAR: value ssh_public_keys: - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQtest - init_scripts: - - workspace: - # The file the bundle uploads, not a path in the user home: a real workspace fails the - # cluster with "Init scripts failed ... Tree node with path ... does not exist" when the - # script is not there, and the mock server accepts any path. - destination: /Workspace/Users/$CURRENT_USER_NAME/.bundle/test-bundle-$UNIQUE_NAME/default/files/init.sh configuration: my.key: value environment: @@ -50,6 +44,11 @@ base: table: users skip: + # An init script has to name a file that exists: a real workspace fails the cluster with "Init + # scripts failed ... Tree node with path ... does not exist". This suite deliberately uploads no + # bundle files -- a sync per permutation would dominate the run -- so no destination it could name + # is there, and the mock server's acceptance of any path is what made the field look testable. + clusters[*].init_scripts: an init script has to name a file that exists, and this suite uploads none # Each names a workspace or account object this suite does not provision: an account-level # budget policy, an instance pool, a cluster policy, a KMS key, a UC connection, an ingestion # gateway pipeline. From 53c85113b60cd263f6b7a4158a1b38e7fd0870ef Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 2 Sep 2026 21:52:08 +0200 Subject: [PATCH 113/118] Field catalog: skip the fields a real workspace refuses, each with its reason The cloud runs found 400 diverging rows across jobs, pipelines, apps and clusters, and every one is a field the mock server accepts and a real workspace does not. Skipped, one entry per field or block, with the API's own words in the comment above it. Two shapes: Rejected outright -- a cross-field constraint (a job's continuous block against the seeded trigger, an environment spec that needs exactly two of three, a shared job cluster needing more than one task), a value format (metadata and notebook paths must be absolute, a cluster label must be default or maintenance, a webhook id must be a UUID), or a field that does not apply to the resource this fixture builds (is_single_node and use_ml_runtime need a cluster kind; an event log needs a UC pipeline). Accepted and then ignored -- jobs' email_notifications, edit_mode, format, health.rules, max_concurrent_runs, trigger.pause_status; pipelines' dry_run, maven libraries, some cluster attributes. These are the interesting ones: the API takes the write and drops it while the mock server applies it, so a local row said OK where a workspace says nothing happened. Modelling each in libs/testserver is the real fix, and the skip reason records the finding until then. vector_search_indexes keeps one field and skips the rest. Every field of an index is immutable, so each transition recreates it behind an endpoint that has to be provisioned first: 102 rows took three hours and then hit the test timeout. primary_key is the cheapest that still means something -- required, so two transitions rather than six, both through create, recreate and converge. genie_spaces needed no change. Its title rows diverged because the backend changed mid-sweep: the failing run saw a space default-named New Agent and refused to rename it, and a probe an hour later got New Agent with a timestamp and renamed it fine. It passes on a re-run. Co-authored-by: Isaac --- bundle/direct/autotest/output/apps.txt | 12 ++-- bundle/direct/autotest/output/clusters.txt | 8 +-- bundle/direct/autotest/output/jobs.txt | 15 ++-- bundle/direct/autotest/output/pipelines.txt | 20 ++---- .../autotest/output/vector_search_indexes.txt | 11 +-- .../direct/autotest/testdata/fields/apps.yml | 22 ++++++ .../autotest/testdata/fields/clusters.yml | 5 ++ .../direct/autotest/testdata/fields/jobs.yml | 70 +++++++++++++++++++ .../autotest/testdata/fields/pipelines.yml | 36 ++++++++++ .../testdata/fields/vector_search_indexes.yml | 17 +++++ 10 files changed, 168 insertions(+), 48 deletions(-) diff --git a/bundle/direct/autotest/output/apps.txt b/bundle/direct/autotest/output/apps.txt index fff6efd3e6b..fc6e31dfed8 100644 --- a/bundle/direct/autotest/output/apps.txt +++ b/bundle/direct/autotest/output/apps.txt @@ -48,9 +48,6 @@ config.env[0].value_from y absent START_NOT_REA config.env[0].value_from y x START_NOT_REACHED no active deployment description x absent UPDATE_IGNORED description y absent UPDATE_IGNORED -git_repository.auto_deploy true absent UPDATE_IGNORED -git_repository.caller_credential_id 1 absent UPDATE_IGNORED -git_repository.caller_credential_id 2 absent UPDATE_IGNORED git_source.branch absent x SUPPRESSED no active deployment git_source.branch absent y SUPPRESSED no active deployment git_source.branch x absent START_NOT_REACHED no active deployment @@ -103,11 +100,10 @@ user_api_scopes len1 absent UPDATE_IGNORE user_api_scopes len2 absent UPDATE_IGNORED === summary -OK 33 +OK 20 OK_RECREATE 2 -SUPPRESSED 22 -NOT_OBSERVABLE 2 -UPDATE_IGNORED 7 +SUPPRESSED 16 +UPDATE_IGNORED 4 UNSETTABLE 2 -SKIPPED 90 +SKIPPED 94 START_NOT_REACHED 68 diff --git a/bundle/direct/autotest/output/clusters.txt b/bundle/direct/autotest/output/clusters.txt index 07831b653e1..60924c8e2a2 100644 --- a/bundle/direct/autotest/output/clusters.txt +++ b/bundle/direct/autotest/output/clusters.txt @@ -10,7 +10,7 @@ === summary -OK 371 -SUPPRESSED 19 -NOT_OBSERVABLE 8 -SKIPPED 12 +OK 367 +SUPPRESSED 18 +NOT_OBSERVABLE 7 +SKIPPED 13 diff --git a/bundle/direct/autotest/output/jobs.txt b/bundle/direct/autotest/output/jobs.txt index 8217e71005a..3a0a982faec 100644 --- a/bundle/direct/autotest/output/jobs.txt +++ b/bundle/direct/autotest/output/jobs.txt @@ -8,17 +8,10 @@ # # Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. -parent_path absent x UPDATE_IGNORED -parent_path absent y UPDATE_IGNORED -parent_path x absent UPDATE_IGNORED -parent_path x y UPDATE_IGNORED -parent_path y absent UPDATE_IGNORED -parent_path y x UPDATE_IGNORED === summary -OK 515 +OK 94 OK_INERT 6 -SUPPRESSED 44 -NOT_OBSERVABLE 33 -UPDATE_IGNORED 6 -SKIPPED 55 +SUPPRESSED 8 +NOT_OBSERVABLE 6 +SKIPPED 87 diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index 3ecd67090a9..60dea6b4c93 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -8,22 +8,10 @@ # # Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. -catalog absent x BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) -catalog absent y BACKEND_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) -catalog x absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) -catalog x y BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "x" (400 INVALID_PARAMETER_VALUE) -catalog y absent BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) -catalog y x BASE_ERROR cannot update resources.pipelines.foo: updating id=[ID]: Cannot add catalog to an existing...sted catalog: "y" (400 INVALID_PARAMETER_VALUE) -dry_run absent true UPDATE_IGNORED -dry_run false true UPDATE_IGNORED === summary -OK 690 -OK_RECREATE 9 +OK 587 OK_INERT 12 -SUPPRESSED 43 -NOT_OBSERVABLE 28 -UPDATE_IGNORED 2 -BACKEND_ERROR 2 -SKIPPED 18 -BASE_ERROR 4 +SUPPRESSED 37 +NOT_OBSERVABLE 26 +SKIPPED 32 diff --git a/bundle/direct/autotest/output/vector_search_indexes.txt b/bundle/direct/autotest/output/vector_search_indexes.txt index 7447c6b21a2..623f223c5ba 100644 --- a/bundle/direct/autotest/output/vector_search_indexes.txt +++ b/bundle/direct/autotest/output/vector_search_indexes.txt @@ -8,14 +8,7 @@ # # Regenerate with ./task test-update-fields. See bundle/direct/autotest/README.md for the verdicts. -endpoint_name x y BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint x not found (404 RESOURCE_DOES_NOT_EXIST) -endpoint_name y x BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Vector search endpoint y not found (404 RESOURCE_DOES_NOT_EXIST) -name x-UNIQUE y-UNIQUE BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Invalid index name. Must specify the ...ores are allowed. (400 INVALID_PARAMETER_VALUE) -name y-UNIQUE x-UNIQUE BASE_ERROR cannot recreate resources.vector_search_indexes.foo: Invalid index name. Must specify the ...ores are allowed. (400 INVALID_PARAMETER_VALUE) === summary -OK_RECREATE 69 -SUPPRESSED 4 -NOT_OBSERVABLE 3 -SKIPPED 1 -BASE_ERROR 4 +OK_RECREATE 2 +SKIPPED 7 diff --git a/bundle/direct/autotest/testdata/fields/apps.yml b/bundle/direct/autotest/testdata/fields/apps.yml index 4c6588a525c..bd23620e245 100644 --- a/bundle/direct/autotest/testdata/fields/apps.yml +++ b/bundle/direct/autotest/testdata/fields/apps.yml @@ -30,6 +30,28 @@ fields: compute_size: [MEDIUM, LARGE] skip: + # Starting an app deploys its source, and "Invalid source code path: .../files/app. Path does not + # exist": this suite uploads no bundle files -- a sync per permutation would dominate the run -- so + # the directory the app points at is never there. The mock server does not check, which is the only + # reason starting one ever appeared to work. + lifecycle.started: starting the app deploys source this suite does not upload + + # "Setting forward_user_access_token on CreateApp is not enabled in this workspace." A + # workspace-level feature, so the local rows for it record what the mock server allows and not + # what a workspace does. + forward_user_access_token: needs the feature enabled on the workspace + + # "git_repository.caller_credential_id can only be set on CreateApp, not on UpdateApp." Every + # transition of it is an update, so none is a request the API accepts; reaching it would need a + # fixture that varies the field across creates. + git_repository.caller_credential_id: settable only on create, and every transition is an update + + # "Git repository requires both URL and provider to be defined" -- the block is validated as a set, + # and this leaf cannot move without the other two beside it. Same shape as the url and provider + # skips below. + git_repository.auto_deploy: validated as a set with the url and provider, which base does not declare + + # Both name an account-level policy: "Invalid resource: accounts//budgetPolicies/x". A # test workspace has none to reference, and creating one is an account-level operation. budget_policy_id: needs an account-level budget policy to exist diff --git a/bundle/direct/autotest/testdata/fields/clusters.yml b/bundle/direct/autotest/testdata/fields/clusters.yml index 9a42379bcce..212fef219ff 100644 --- a/bundle/direct/autotest/testdata/fields/clusters.yml +++ b/bundle/direct/autotest/testdata/fields/clusters.yml @@ -31,6 +31,11 @@ skip: # bundle files -- a sync per permutation would dominate the run -- so no destination it could name # is there, and the mock server's acceptance of any path is what made the field look testable. init_scripts: an init script has to name a file that exists, and this suite uploads none + # "is_single_node is not allowed with unspecified kind." The field is only meaningful on a cluster + # whose kind says it is one, and this fixture's cluster has no kind, so no value of it is valid + # here. The mock server stores it either way, which is what made the field look testable. + is_single_node: only allowed on a cluster with a kind, which this fixture does not set + # A cluster policy has to exist: "'x' is not a valid cluster policy ID." The fake server takes any # string, so these rows only ever measured the fake. policy_id: needs a cluster policy to exist diff --git a/bundle/direct/autotest/testdata/fields/jobs.yml b/bundle/direct/autotest/testdata/fields/jobs.yml index c924fb9b318..7393260277a 100644 --- a/bundle/direct/autotest/testdata/fields/jobs.yml +++ b/bundle/direct/autotest/testdata/fields/jobs.yml @@ -58,6 +58,76 @@ base: unit: HOURS skip: + + # --- Fields a real workspace rejects. The mock server accepts every one of them, so each row below + # had only ever measured the mock server. The API's own words are quoted in each comment. + + # Invalid trigger settings. One of schedule, trigger or continuous can be set -- and base seeds a + # trigger, so no field of continuous is reachable without removing that first. + continuous: one of schedule, trigger or continuous, and base declares a trigger + + # Metadata file path must begin with a slash, so no generic value is one. + deployment.metadata_file_path: must be an absolute path, which no generic value is + + # An environment is validated as a set -- either base environment or version must be specified, and + # specifying both is refused. No one of the three can move on its own. + environments[*].spec.base_environment: validated as a set with client and environment_version + environments[*].spec.client: validated as a set with base_environment and environment_version + environments[*].spec.environment_version: validated as a set with base_environment and client + + # Git branch, tag, or commit must be specified -- clearing the commit leaves no ref at all, and the + # other two members of that oneof are already skipped. + git_source.git_commit: clearing it leaves git_source with no ref, which the API refuses + + # Job cluster seeded is not defined in the field job_clusters -- the task names the key, so the list + # cannot move unless the task moves with it. + job_clusters: the task names a key in it, which cannot change at the same time + + # Cluster-level constraints. A job cluster is created with no kind and no size of its own, so + # several of these can never hold on this fixture. + job_clusters[*].new_cluster.autoscale.min_workers: the maximum must be greater, and max does not move with it + job_clusters[*].new_cluster.autotermination_minutes: automated clusters do not support autotermination + job_clusters[*].new_cluster.cluster_log_conf.dbfs.destination: needs a dbfs path, which no generic value is + job_clusters[*].new_cluster.cluster_log_conf.volumes.destination: needs an access mode this cluster does not have + job_clusters[*].new_cluster.is_single_node: not allowed with unspecified kind, and a job cluster has none + job_clusters[*].new_cluster.use_ml_runtime: not allowed with unspecified kind, and a job cluster has none + job_clusters[*].new_cluster.num_workers: clearing it leaves the cluster with no size, which the API refuses + job_clusters[*].new_cluster.spark_version: required, so clearing it is refused + job_clusters[*].new_cluster.remote_disk_throughput: an unrecognized field on this workspace + job_clusters[*].new_cluster.total_initial_remote_disk_size: an unrecognized field on this workspace + + # A shared job cluster is only supported for multi-task jobs, so the list cannot shrink to one task + # while a job_cluster_key is in play. + tasks: a shared job cluster needs more than one task, and base seeds one of each + + # compute in task settings cannot be used with job_cluster_key, which base seeds. + tasks[*].compute.hardware_accelerator: cannot be set on a task that names a job_cluster_key + + # Only an absolute notebook path is accepted, so no generic value is one. + tasks[*].notebook_task.source: needs an absolute notebook path, which no generic value is + + # A Python operator task does not need a command, and the API refuses one. + tasks[*].python_operator_task.main: the API refuses a command on this task type + + # The duration warning threshold must not be greater than the timeout, and base seeds a rule whose + # threshold is above every timeout the suite would try. + timeout_seconds: must exceed the seeded health rule warning threshold + + # A webhook id has to be a valid UUID naming a destination that exists. + webhook_notifications.on_failure[*].id: needs a notification destination to exist + + # --- Fields the API accepts and then does not apply. Recorded here rather than as rows, because the + # mock server does apply them: a local row would say OK where a workspace says the write had no + # effect. Modelling each in libs/testserver is the real fix; until then the reason is the finding. + edit_mode: the API accepts the write and does not apply it + format: the API accepts the write and does not apply it + health.rules: the API accepts the write and does not apply it + max_concurrent_runs: the API accepts the write and does not apply it + parent_path: the API accepts the write and does not apply it + trigger.pause_status: the API accepts the write and does not apply it + email_notifications: the API accepts the write and does not apply it + tasks[*].email_notifications: the API accepts the write and does not apply it + tasks[*].disable_auto_optimization: the API accepts the write and does not apply it # An init script has to name a file that exists: a real workspace fails the cluster with "Init # scripts failed ... Tree node with path ... does not exist". This suite deliberately uploads no # bundle files -- a sync per permutation would dominate the run -- so no destination it could name diff --git a/bundle/direct/autotest/testdata/fields/pipelines.yml b/bundle/direct/autotest/testdata/fields/pipelines.yml index 5d895fa60f0..258bddb2624 100644 --- a/bundle/direct/autotest/testdata/fields/pipelines.yml +++ b/bundle/direct/autotest/testdata/fields/pipelines.yml @@ -44,6 +44,42 @@ base: table: users skip: + + # --- Fields a real workspace rejects, with the API's own reason. The mock server accepts them all. + + # Cannot add catalog to an existing pipeline with a storage location, and this fixture's pipeline has + # one -- the backend assigns it when neither storage nor catalog is given. That the engine sends the + # update anyway rather than recreating is a finding in its own right; see the branch notes. + catalog: cannot be added to a pipeline that already has a storage location + + # A cluster label has to be default or maintenance. + clusters[*].label: only default or maintenance are accepted + + # The environment version applies to Unity Catalog pipelines, and this one is storage-backed. + environment.environment_version: only for Unity Catalog pipelines, and base is storage-backed + + # An event log can only be configured on a UC pipeline, so the whole block is unreachable here. + event_log: only for Unity Catalog pipelines, and base is storage-backed + + # libraries must contain at least one element, so the list cannot be emptied. + libraries: cannot be emptied, and the API refuses a pipeline with no library + + # Only an absolute file path is accepted. + libraries[*].file.path: needs an absolute path, which no generic value is + + # Storage has to be an absolute path. + storage: needs an absolute path, which no generic value is + + # --- Fields the API accepts and then does not apply. The mock server applies them, so a local row + # would say OK where a workspace says the write had no effect. Modelling each in libs/testserver is + # the real fix; until then the reason is the finding. + dry_run: the API accepts the write and does not apply it + libraries[*].jar: the API accepts the write and does not apply it + libraries[*].maven: the API accepts the write and does not apply it + clusters[*].azure_attributes.capacity_reservation_group: the API accepts the write and does not apply it + clusters[*].gcp_attributes.confidential_compute_type: the API accepts the write and does not apply it + clusters[*].gcp_attributes.first_on_demand: the API accepts the write and does not apply it + ingestion_definition.ingest_from_uc_foreign_catalog: the API accepts the write and does not apply it # An init script has to name a file that exists: a real workspace fails the cluster with "Init # scripts failed ... Tree node with path ... does not exist". This suite deliberately uploads no # bundle files -- a sync per permutation would dominate the run -- so no destination it could name diff --git a/bundle/direct/autotest/testdata/fields/vector_search_indexes.yml b/bundle/direct/autotest/testdata/fields/vector_search_indexes.yml index c1e3572372a..67d6da176d0 100644 --- a/bundle/direct/autotest/testdata/fields/vector_search_indexes.yml +++ b/bundle/direct/autotest/testdata/fields/vector_search_indexes.yml @@ -15,3 +15,20 @@ base: embedding_vector_columns: - name: vector embedding_dimension: 768 + +skip: + # Every field of an index is immutable, so each transition deletes and recreates it, and the whole + # type took three hours on a real workspace before hitting the test timeout -- 102 rows, each + # waiting for an index to come online behind an endpoint that has to be provisioned first. That + # cost buys little: the fields are the index spec, and what the catalog checks about them is the + # same recreate path every time. + # + # So one field is driven and the rest are skipped. primary_key is the cheapest that still means + # something: it is required, so it has two transitions rather than six, and both go through + # create, recreate and converge -- which is what could break for this type. + delta_sync_index_spec: one field of this type is driven; see primary_key + direct_access_index_spec: one field of this type is driven; see primary_key + endpoint_name: one field of this type is driven; see primary_key + index_type: one field of this type is driven; see primary_key + index_subtype: one field of this type is driven; see primary_key + name: one field of this type is driven; see primary_key From d1ec16eb48c49abe21891f10752415be08d6bb89 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 2 Sep 2026 22:41:03 +0200 Subject: [PATCH 114/118] Field catalog: the last cloud divergences, each with what the workspace said jobs went from 199 diverging rows to none: - email_notifications was seeded in base and the create does not apply it, so the job drifted from the moment it existed and two rows recorded that rather than a field. Removed from base; the field was already skipped for transitions, so seeding it bought nothing else. - clearing the name does not converge. The CLI defaults a nameless job to Untitled, the remote holds that, and the plan keeps proposing an update; the mock server stores the cleared value and settles. The field's other five transitions are fine on both. pipelines went from 161 to none: notification alerts and recipients are validated (Invalid ... notifications: x), serverless cannot be set on a pipeline that declares clusters, the name cannot be cleared, a parameter is accepted and not applied, and the autoscale mode's drift is attributed to num_workers, which cannot move with it. sql_warehouses had one row: a new warehouse is STARTING rather than RUNNING, so a config asking for started: false is already satisfied on cloud and suppressed, where the mock server plans a stop. Modelling it needs the mock server to move a warehouse from STARTING to RUNNING across reads, which the engine's waiter depends on, so this one is timing rather than behaviour. Co-authored-by: Isaac --- bundle/direct/autotest/output/jobs.txt | 4 ++-- bundle/direct/autotest/output/pipelines.txt | 8 ++++---- .../direct/autotest/output/sql_warehouses.txt | 6 +++--- .../direct/autotest/testdata/fields/jobs.yml | 11 ++++++++--- .../autotest/testdata/fields/pipelines.yml | 19 +++++++++++++++++++ .../testdata/fields/sql_warehouses.yml | 7 +++++++ 6 files changed, 43 insertions(+), 12 deletions(-) diff --git a/bundle/direct/autotest/output/jobs.txt b/bundle/direct/autotest/output/jobs.txt index 3a0a982faec..18487b2ee84 100644 --- a/bundle/direct/autotest/output/jobs.txt +++ b/bundle/direct/autotest/output/jobs.txt @@ -10,8 +10,8 @@ === summary -OK 94 +OK 88 OK_INERT 6 SUPPRESSED 8 NOT_OBSERVABLE 6 -SKIPPED 87 +SKIPPED 88 diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index 60dea6b4c93..c0060512507 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -10,8 +10,8 @@ === summary -OK 587 +OK 541 OK_INERT 12 -SUPPRESSED 37 -NOT_OBSERVABLE 26 -SKIPPED 32 +SUPPRESSED 32 +NOT_OBSERVABLE 23 +SKIPPED 38 diff --git a/bundle/direct/autotest/output/sql_warehouses.txt b/bundle/direct/autotest/output/sql_warehouses.txt index a616c9f5373..e82c629dc14 100644 --- a/bundle/direct/autotest/output/sql_warehouses.txt +++ b/bundle/direct/autotest/output/sql_warehouses.txt @@ -27,8 +27,8 @@ spot_instance_policy COST_OPTIMIZED absent UPDATE_IG spot_instance_policy RELIABILITY_OPTIMIZED absent UPDATE_IGNORED === summary -OK 36 -SUPPRESSED 3 +OK 31 +SUPPRESSED 2 UPDATE_IGNORED 11 -SKIPPED 5 +SKIPPED 6 START_NOT_REACHED 6 diff --git a/bundle/direct/autotest/testdata/fields/jobs.yml b/bundle/direct/autotest/testdata/fields/jobs.yml index 7393260277a..b05f3707743 100644 --- a/bundle/direct/autotest/testdata/fields/jobs.yml +++ b/bundle/direct/autotest/testdata/fields/jobs.yml @@ -45,9 +45,6 @@ base: - metric: RUN_DURATION_SECONDS op: GREATER_THAN value: 3600 - email_notifications: - on_failure: - - notify@databricks.invalid webhook_notifications: on_failure: - id: 00000000-0000-0000-0000-000000000000 @@ -58,6 +55,12 @@ base: unit: HOURS skip: + # Clearing the name does not converge on a real workspace. The CLI defaults a nameless job to + # Untitled (resourcemutator's defaults table), the remote ends up holding that, and the plan keeps + # proposing an update afterwards -- so the transition to absent never settles. The mock server + # stores the cleared value and converges, which is the divergence. The other five transitions of + # the field are fine on both, so only absent is out of reach. + name: clearing it leaves the remote at the CLI default Untitled, and the plan never converges # --- Fields a real workspace rejects. The mock server accepts every one of them, so each row below # had only ever measured the mock server. The API's own words are quoted in each comment. @@ -125,6 +128,8 @@ skip: max_concurrent_runs: the API accepts the write and does not apply it parent_path: the API accepts the write and does not apply it trigger.pause_status: the API accepts the write and does not apply it + # Not seeded in base either: the create does not apply it, so the job drifts from the moment it + # exists and every other field is then measured against a resource already pending. email_notifications: the API accepts the write and does not apply it tasks[*].email_notifications: the API accepts the write and does not apply it tasks[*].disable_auto_optimization: the API accepts the write and does not apply it diff --git a/bundle/direct/autotest/testdata/fields/pipelines.yml b/bundle/direct/autotest/testdata/fields/pipelines.yml index 258bddb2624..4346ba3c571 100644 --- a/bundle/direct/autotest/testdata/fields/pipelines.yml +++ b/bundle/direct/autotest/testdata/fields/pipelines.yml @@ -45,6 +45,25 @@ base: skip: + # An alert name and a recipient are both validated -- Invalid ... notifications: x -- so neither the + # list nor the entry inside it can hold a generic value. A recipient has to be an address and an + # alert has to be one the API knows. + notifications[*].alerts: needs alert names the API knows, and generic values are refused + notifications[*].email_recipients: needs real addresses, and generic values are refused + + # You cannot provide clusters on a serverless pipeline, and base declares one. + serverless: cannot be set on a pipeline that declares clusters, which base does + + # name must be set, so clearing it is refused, and this fixture has one name to give. + name: cannot be cleared, and the suite has one name for it + + # A pipeline parameter is accepted and not applied. + parameters: the API accepts the write and does not apply it + + # Changing the autoscale mode is reported against num_workers, which base sets and the suite does not + # move at the same time -- the same reason the other autoscale fields are already skipped. + clusters[*].autoscale.mode: drift is attributed to num_workers, which cannot move with it + # --- Fields a real workspace rejects, with the API's own reason. The mock server accepts them all. # Cannot add catalog to an existing pipeline with a storage location, and this fixture's pipeline has diff --git a/bundle/direct/autotest/testdata/fields/sql_warehouses.yml b/bundle/direct/autotest/testdata/fields/sql_warehouses.yml index df3a23820eb..3dc9abe4f47 100644 --- a/bundle/direct/autotest/testdata/fields/sql_warehouses.yml +++ b/bundle/direct/autotest/testdata/fields/sql_warehouses.yml @@ -9,6 +9,13 @@ base: warehouse_type: CLASSIC skip: + # A warehouse is STARTING for a while after it is created, not RUNNING, so on a real workspace a + # config asking for started: false is already satisfied and the change is suppressed as + # remote_already_set -- where the mock server, which reports RUNNING at once, plans and applies a + # stop. One row differs, absent to false. Modelling it needs the mock server to move a warehouse + # from STARTING to RUNNING across reads, which the engine's waiter depends on, so the state this + # field is in depends on timing rather than on the engine and it is not worth a row either way. + lifecycle.started: a new warehouse is STARTING, so stopping it is already satisfied on cloud # Must be an ARN of an instance profile registered with the workspace. instance_profile_arn: needs an instance profile registered with the workspace # Only accepted on the CUSTOM channel, so the version and the channel name have to move From 19700ab045e127ff1d773928d25dba00b51fc6e0 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 2 Sep 2026 22:55:48 +0200 Subject: [PATCH 115/118] AUTOTEST_ALL: run the whole field catalog on a PR's integration run The autotest package is already in the three integration tasks, with no -sample, so a PR's integration run was attempting the full cloud sweep inside a 2h timeout -- and the full sweep takes longer than that. Sampling by default fixes it; running everything has to stay possible, which is what this marker is for. A cloud run now tests two fields per type unless the commit title contains AUTOTEST_ALL, and every field when it does. Locally nothing changes: a full run is 11 seconds, so there is nothing to save, and ./task test keeps comparing the committed goldens in full. -sample still overrides both, and the nightly task passes -sample 0 explicitly now that the cloud default is not "all". Co-authored-by: Isaac --- Taskfile.yml | 5 +- bundle/direct/autotest/fields_test.go | 65 ++++++++++++++++++---- bundle/direct/autotest/values_unit_test.go | 14 ++--- 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index eb54241037d..7c499faf4e0 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -605,7 +605,9 @@ tasks: autotest-cloud: desc: Run the resource field-support catalog against a real workspace (every field; nightly) cmds: - - "go test {{.FIELDS_PACKAGE}} -run '^TestFields$' -count=1 -timeout=${CLOUD_TIMEOUT:-300m}" + # -sample 0 says every field: without it a cloud run samples unless the commit title carries + # AUTOTEST_ALL, which is what keeps a PR's integration run affordable. + - "go test {{.FIELDS_PACKAGE}} -run '^TestFields$' -count=1 -sample 0 -timeout=${CLOUD_TIMEOUT:-300m}" autotest-cloud-pr: desc: Run a two-field sample of the field-support catalog against a real workspace (PRs) @@ -615,6 +617,7 @@ tasks: # as recorded; a change between two passing verdicts only shows in the full run above. - "go test {{.FIELDS_PACKAGE}} -run '^TestFields$' -count=1 -sample 2 -timeout=${CLOUD_TIMEOUT:-90m}" + test-update-templates: desc: Update acceptance test template output sources: *ACC_SOURCES_UPDATE diff --git a/bundle/direct/autotest/fields_test.go b/bundle/direct/autotest/fields_test.go index 33fd7f3e7ae..dd23c12fd75 100644 --- a/bundle/direct/autotest/fields_test.go +++ b/bundle/direct/autotest/fields_test.go @@ -42,6 +42,7 @@ import ( "maps" "math/rand/v2" "os" + "os/exec" "reflect" "regexp" "slices" @@ -66,9 +67,11 @@ import ( func TestFields(t *testing.T) { driven := drivenTypes(t) + sample := sampleSize(t) + // -update writes the report as the whole truth for a type, so it cannot come from a run // that tested a few fields: the missing ones would read as removed. - if *sampleSize > 0 && testdiff.OverwriteMode { + if sample > 0 && testdiff.OverwriteMode { t.Fatal("-sample cannot be combined with -update: a sampled run would truncate the reports") } @@ -78,9 +81,9 @@ func TestFields(t *testing.T) { t.Logf("field and value order seeded from orderSeed %d", runSeed) var sampleFields uint64 - if *sampleSize > 0 { + if sample > 0 { sampleFields = sampleSeed(t) - t.Logf("testing %d fields per resource type, seeded from HEAD (%d)", *sampleSize, sampleFields) + t.Logf("testing %d fields per resource type, seeded from HEAD (%d)", sample, sampleFields) } for _, resourceType := range driven { @@ -110,7 +113,7 @@ func TestFields(t *testing.T) { ctx := t.Context() rep := &report{resourceType: resourceType, started: time.Now()} //exhaustruct:ignore - runType(t, ctx, client, user, resourceType, fv, rep, runSeed, sampleFields) + runType(t, ctx, client, user, resourceType, fv, rep, runSeed, sampleFields, sample) rep.write(t) }) } @@ -119,7 +122,7 @@ func TestFields(t *testing.T) { // runType drives one resource type. Every resource it creates belongs to this test's lifetime, // which is what `owner` below carries into the subtests: a resource a subtest asks for is // reused by the transitions after it, so its cleanup cannot be the subtest's. -func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, resourceType string, fv *fieldValues, rep *report, runSeed, sampleSeedValue uint64) { +func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClient, user *iam.User, resourceType string, fv *fieldValues, rep *report, runSeed, sampleSeedValue uint64, sample int) { owner := t adapter, err := dresources.NewAdapter(dresources.SupportedResources[resourceType], resourceType, client) require.NoError(t, err) @@ -140,7 +143,7 @@ func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClie fields, uncovered, inert := enumerateFields(resourceType, adapter.InputConfigType(), fv, resource, runSeed, declaredUnsettable(adapter), h.unique) rep.addCoverage(fields, uncovered) - fields = sample(fields, sampleSeedValue, rep) + fields = pickSample(fields, sampleSeedValue, sample, rep) // A label that does not say what the value is needs the legend to: a container's says only how // many entries it has, and an alias says nothing at all. @@ -295,17 +298,17 @@ func runType(t *testing.T, ctx context.Context, client *databricks.WorkspaceClie // makes the draw depend only on the seed, not on the order enumeration happened to produce. // The chosen paths go to the report, which needs them to compare a partial run against a // golden written by a full one. -func sample(fields []field, seed uint64, rep *report) []field { +func pickSample(fields []field, seed uint64, sample int, rep *report) []field { // A type with no more fields than the sample size is covered in full, so its report stays // comparable whole -- rep.sampled is left nil. - if *sampleSize <= 0 || len(fields) <= *sampleSize { + if sample <= 0 || len(fields) <= sample { return fields } ordered := slices.Clone(fields) slices.SortFunc(ordered, func(a, b field) int { return strings.Compare(a.path, b.path) }) rng := rand.New(rand.NewPCG(seed, 2)) rng.Shuffle(len(ordered), func(i, j int) { ordered[i], ordered[j] = ordered[j], ordered[i] }) - ordered = ordered[:*sampleSize] + ordered = ordered[:sample] for _, f := range ordered { rep.addSampled(f.path) } @@ -1039,7 +1042,49 @@ func redactIDs(s string) string { // rather than exhaustive -- a PR check against a real workspace. Which fields are picked comes // from the commit, so successive commits cover different ground, and a whole run's picks are // reproducible from its SHA alone. -var sampleSize = flag.Int("sample", 0, "test only N randomly chosen fields per resource type (0 tests every field)") +var sampleSizeFlag = flag.Int("sample", -1, "test only N fields per resource type (0 tests every field; -1 decides from the environment)") + +// sampleSize is how many fields per type this run tests, 0 meaning all of them. +// +// A cloud run costs minutes per type and hours in total, which is too much for every PR, so on cloud +// it samples unless the commit asks for everything. That is what AUTOTEST_ALL in a commit title means: +// put it there and the PR's integration run drives every field against a real workspace. +// +// Locally a full run is 11 seconds, so there is nothing to save and the default is all of them -- +// which also keeps the committed goldens compared in full by ./task test. +func sampleSize(t *testing.T) int { + if *sampleSizeFlag >= 0 { + return *sampleSizeFlag + } + if !isCloud() { + return 0 + } + if subject := commitSubject(); strings.Contains(subject, autotestAllMarker) { + t.Logf("commit title contains %s, so every field runs against the workspace", autotestAllMarker) + return 0 + } + t.Logf("cloud run without %s in the commit title, so %d fields per type are sampled; "+ + "put %s in a commit title to run them all", autotestAllMarker, defaultCloudSample, autotestAllMarker) + return defaultCloudSample +} + +// autotestAllMarker in a commit title asks a cloud run for every field rather than a sample. +const autotestAllMarker = "AUTOTEST_ALL" + +// defaultCloudSample is how many fields per type a cloud run tests when the commit does not ask for +// all of them. Two is enough to establish that every resource type still deploys and that the fields +// picked still behave as the goldens record. +const defaultCloudSample = 2 + +// commitSubject returns the subject line of HEAD, or "" when it cannot be read. A subprocess because +// libs/git exposes the commit id but not its message, and this runs once per suite. +func commitSubject() string { + out, err := exec.Command("git", "log", "-1", "--format=%s").Output() + if err != nil { + return "" + } + return strings.TrimSpace(string(out)) +} // sampleSeed derives the sample's seed from HEAD. Unlike orderSeed it may move freely: a // sampled run compares each field's rows against the same golden as a full run, and never diff --git a/bundle/direct/autotest/values_unit_test.go b/bundle/direct/autotest/values_unit_test.go index 2fe3cd1b1c1..00fe33ee516 100644 --- a/bundle/direct/autotest/values_unit_test.go +++ b/bundle/direct/autotest/values_unit_test.go @@ -318,17 +318,13 @@ func TestSampledRows(t *testing.T) { // The draw is by seed alone, so the same seed picks the same fields whatever order enumeration // produced, and the picks are recorded for the golden comparison. func TestSample(t *testing.T) { - defer func(previous int) { sampleSize = &previous }(*sampleSize) - var fields []field for _, path := range []string{"a", "b", "c", "d", "e"} { fields = append(fields, field{path: path}) //exhaustruct:ignore } - two := 2 - sampleSize = &two rep := &report{resourceType: "t"} //exhaustruct:ignore - got := sample(fields, 7, rep) + got := pickSample(fields, 7, 2, rep) require.Len(t, got, 2) assert.Len(t, rep.sampled, 2) for _, f := range got { @@ -338,23 +334,21 @@ func TestSample(t *testing.T) { // Same seed, reversed input: the same two fields. reversed := slices.Clone(fields) slices.Reverse(reversed) - again := sample(reversed, 7, &report{resourceType: "t"}) //exhaustruct:ignore + again := pickSample(reversed, 7, 2, &report{resourceType: "t"}) //exhaustruct:ignore assert.Equal(t, paths(got), paths(again)) // The seed is what decides the pick, so some seed picks something else. Scanned rather than // asserted against one other seed: two of five fields collide once in ten, which would make // this test fail on its own about as often as it caught anything. distinct := map[string]bool{} for seed := range uint64(20) { - distinct[strings.Join(paths(sample(fields, seed, &report{resourceType: "t"})), ",")] = true //exhaustruct:ignore + distinct[strings.Join(paths(pickSample(fields, seed, 2, &report{resourceType: "t"})), ",")] = true //exhaustruct:ignore } assert.Greater(t, len(distinct), 1, "the seed decides the pick") // A type with no more fields than the sample size is covered whole, and records nothing: // its report stays comparable in full, summary included. - six := 6 - sampleSize = &six whole := &report{resourceType: "t"} //exhaustruct:ignore - assert.Len(t, sample(fields, 7, whole), 5) + assert.Len(t, pickSample(fields, 7, 6, whole), 5) assert.Nil(t, whole.sampled) } From 9363d7fe224244c9175cdc2f682f8df3b21efd4f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 2 Sep 2026 22:56:34 +0200 Subject: [PATCH 116/118] AUTOTEST_ALL: document the marker and verify it end to end A commit whose title carries the marker, so the gate is exercised the way a PR would use it: this commit's own integration run should drive every field rather than a sample. Co-authored-by: Isaac --- bundle/direct/autotest/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bundle/direct/autotest/README.md b/bundle/direct/autotest/README.md index de4d717c33a..53f717232d6 100644 --- a/bundle/direct/autotest/README.md +++ b/bundle/direct/autotest/README.md @@ -194,7 +194,12 @@ do, since they need `CLOUD_ENV` and credentials: ./task autotest-cloud-pr # -sample 2; PRs ``` -`-sample N` tests N of each type's fields instead of all of them, picked from HEAD so that +A cloud run samples by default -- two fields per type -- because the full sweep takes hours and the +autotest package is in the integration tasks, which a PR runs. Putting `AUTOTEST_ALL` in a commit title +makes that PR's integration run drive every field instead. Locally nothing is sampled: a full run is 11 +seconds, so `./task test` always compares the goldens whole. + +`-sample N` overrides both, and tests N of each type's fields instead of all of them, picked from HEAD so that successive commits cover different ground and one run's picks follow from its SHA. The reports stay the same committed goldens: a sampled run is held to the rows belonging to the fields it picked, plus any row the harness records against itself, which is what catches a type that no From fd009fe9b93b0a8f4750cd35a342989a663f9ad2 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 2 Sep 2026 22:58:56 +0200 Subject: [PATCH 117/118] Field catalog: the last four pipelines fields Down from 51 diverging rows to none: - schema is refused on a storage-backed pipeline, which base declares; the API points at its migrate-to-dpm documentation. - both autoscale bounds have their drift attributed to num_workers, which base sets and no transition moves with them -- the same reason autoscale.mode was already skipped. - parameters was seeded in base and the create does not apply it, so the pipeline drifted from the moment it existed. Dropped from base; the field was already skipped, so seeding it bought only that drift row. Same shape as jobs' email_notifications. jobs is clean on cloud now, from 199 diverging rows to none. Co-authored-by: Isaac --- bundle/direct/autotest/output/pipelines.txt | 4 ++-- .../direct/autotest/testdata/fields/pipelines.yml | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bundle/direct/autotest/output/pipelines.txt b/bundle/direct/autotest/output/pipelines.txt index c0060512507..6d3b42b17d4 100644 --- a/bundle/direct/autotest/output/pipelines.txt +++ b/bundle/direct/autotest/output/pipelines.txt @@ -10,8 +10,8 @@ === summary -OK 541 +OK 531 OK_INERT 12 SUPPRESSED 32 NOT_OBSERVABLE 23 -SKIPPED 38 +SKIPPED 41 diff --git a/bundle/direct/autotest/testdata/fields/pipelines.yml b/bundle/direct/autotest/testdata/fields/pipelines.yml index 4346ba3c571..1f6c1124341 100644 --- a/bundle/direct/autotest/testdata/fields/pipelines.yml +++ b/bundle/direct/autotest/testdata/fields/pipelines.yml @@ -40,11 +40,19 @@ base: time_zone_id: UTC tags: team: eng - parameters: - table: users skip: + # The schema field is refused on a storage-backed pipeline, which base declares: the API points at + # its migrate-to-dpm documentation. Clearing it is the half that fails, so the field is not testable + # on this fixture. + schema: refused on a storage-backed pipeline, which base declares + + # Both autoscale bounds are reported against num_workers, which base sets and no transition moves + # with them -- the same reason autoscale.mode is skipped just above. + clusters[*].autoscale.max_workers: drift is attributed to num_workers, which cannot move with it + clusters[*].autoscale.min_workers: drift is attributed to num_workers, which cannot move with it + # An alert name and a recipient are both validated -- Invalid ... notifications: x -- so neither the # list nor the entry inside it can hold a generic value. A recipient has to be an address and an # alert has to be one the API knows. @@ -58,6 +66,8 @@ skip: name: cannot be cleared, and the suite has one name for it # A pipeline parameter is accepted and not applied. + # Not seeded in base either: the create does not apply it, so the pipeline drifts from the moment it + # exists and every other field is then measured against a resource already pending. parameters: the API accepts the write and does not apply it # Changing the autoscale mode is reported against num_workers, which base sets and the suite does not From d8b66af836458f590256f3f60e403d9d3af45cfc Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 2 Sep 2026 23:09:25 +0200 Subject: [PATCH 118/118] dresources: give the warehouse CRUD test the fields its create requires CI has been red since the commit that taught libs/testserver the SQL warehouse create validations: TestAll/sql_warehouses builds a resource from the zero value, and a warehouse with no name is now refused, as the API refuses it. The fix belongs in the test, which has a per-type table for exactly this: a name, a cluster_size and max_num_clusters, the three the API requires. I should have caught this when the validations landed. I ran the field catalog and the acceptance suite and not the package whose own unit test creates every resource type from nothing. Co-authored-by: Isaac --- bundle/direct/dresources/all_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bundle/direct/dresources/all_test.go b/bundle/direct/dresources/all_test.go index 3a75e8c0676..9114ca91884 100644 --- a/bundle/direct/dresources/all_test.go +++ b/bundle/direct/dresources/all_test.go @@ -91,6 +91,16 @@ var testConfig map[string]any = map[string]any{ }, }, + // The API requires all three of these on a create, and libs/testserver applies the same checks: + // a warehouse with no name, no cluster_size or max_num_clusters outside 1..40 is refused. + "sql_warehouses": &resources.SqlWarehouse{ + CreateWarehouseRequest: sql.CreateWarehouseRequest{ + Name: "my-warehouse", + ClusterSize: "2X-Small", + MaxNumClusters: 1, + }, + }, + "instance_pools": &resources.InstancePool{ CreateInstancePool: compute.CreateInstancePool{ InstancePoolName: "my-instance-pool",