diff --git a/.nextchanges/bundles/direct-nested-reused-types.md b/.nextchanges/bundles/direct-nested-reused-types.md new file mode 100644 index 00000000000..23e1fa16722 --- /dev/null +++ b/.nextchanges/bundles/direct-nested-reused-types.md @@ -0,0 +1 @@ +* Direct-engine bundles no longer flag phantom drift on server-populated nested fields under reused config types (e.g. `external_locations` file-event-queue resource IDs, `database_instances` parent-instance refs, `apps` git credential ID). ([#6618](https://github.com/databricks/cli/pull/6618)) diff --git a/bundle/direct/dresources/resources.generated.yml b/bundle/direct/dresources/resources.generated.yml index c297a90cba9..0e4937e28aa 100644 --- a/bundle/direct/dresources/resources.generated.yml +++ b/bundle/direct/dresources/resources.generated.yml @@ -27,6 +27,8 @@ resources: apps: ignore_remote_changes: + - field: git_repository.caller_credential_id + reason: spec:input_only - field: git_source reason: spec:input_only - field: source_code_path @@ -129,6 +131,8 @@ resources: reason: spec:input_only - field: node_count reason: spec:input_only + - field: parent_instance_ref.lsn + reason: spec:input_only - field: retention_window_in_days reason: spec:input_only - field: stopped @@ -156,6 +160,10 @@ resources: reason: spec:output_only - field: effective_custom_tags reason: spec:output_only + - field: effective_custom_tags[*].key + reason: spec:output_only + - field: effective_custom_tags[*].value + reason: spec:output_only - field: effective_enable_pg_native_login reason: spec:output_only - field: effective_enable_readable_secondaries @@ -168,6 +176,10 @@ resources: reason: spec:output_only - field: effective_usage_policy_id reason: spec:output_only + - field: parent_instance_ref.effective_lsn + reason: spec:output_only + - field: parent_instance_ref.uid + reason: spec:output_only - field: pg_version reason: spec:output_only - field: read_only_dns @@ -196,6 +208,18 @@ resources: reason: spec:output_only - field: effective_file_event_queue reason: spec:output_only + - field: file_event_queue.managed_aqs.managed_resource_id + reason: spec:output_only + - field: file_event_queue.managed_pubsub.managed_resource_id + reason: spec:output_only + - field: file_event_queue.managed_sqs.managed_resource_id + reason: spec:output_only + - field: file_event_queue.provided_aqs.managed_resource_id + reason: spec:output_only + - field: file_event_queue.provided_pubsub.managed_resource_id + reason: spec:output_only + - field: file_event_queue.provided_sqs.managed_resource_id + reason: spec:output_only genie_spaces: diff --git a/bundle/direct/tools/generate_resources.py b/bundle/direct/tools/generate_resources.py index 45996b5fa27..edf9ce5910f 100644 --- a/bundle/direct/tools/generate_resources.py +++ b/bundle/direct/tools/generate_resources.py @@ -73,10 +73,14 @@ def get_field_behaviors(schemas, type_name, resource_name=None, array_element_ty array_element_types = {} def extract(schema, prefix, visited, depth, inherited): - # Bound recursion as a runaway guard only; `visited` already guarantees - # termination (each type is expanded at most once). Real fields reach depth 5 - # (e.g. external_model.custom_provider_config.bearer_token_auth.token_plaintext), - # so keep ample headroom above that. + # `visited` holds the ref types on the path from the root to here, so a type + # that transitively references itself terminates the recursion. It is scoped + # to the current path (each branch gets its own copy via `visited | {ref}`), + # not global, so a type reused at sibling paths is expanded at each one — e.g. + # ModelServiceConfigDestinationConfig under both config.routing.destinations[*] + # and config.routing.fallback.destinations[*]. max_depth is a runaway guard + # only; real fields reach depth 5 (e.g. + # external_model.custom_provider_config.bearer_token_auth.token_plaintext). max_depth = 10 if depth > max_depth: raise Exception(f"Nested field found at depth {depth} ({max_depth=})") @@ -92,17 +96,17 @@ def extract(schema, prefix, visited, depth, inherited): if "ref" in prop: ref = prop["ref"] if ref in schemas and ref not in visited: - visited.add(ref) propagate = [b for b in behaviors if b in ("INPUT_ONLY", "OUTPUT_ONLY")] - results.update(extract(schemas[ref], path, visited, depth + 1, propagate)) + results.update(extract(schemas[ref], path, visited | {ref}, depth + 1, propagate)) elif resource_name is not None: # For array fields with no ref in the schema, use the element type from # out.fields.txt (e.g. App.resources[] -> AppResource). elem_type = array_element_types.get((resource_name, path)) if elem_type and elem_type in schemas and elem_type not in visited: - visited.add(elem_type) propagate = [b for b in behaviors if b in ("INPUT_ONLY", "OUTPUT_ONLY")] - results.update(extract(schemas[elem_type], f"{path}[*]", visited, depth + 1, propagate)) + results.update( + extract(schemas[elem_type], f"{path}[*]", visited | {elem_type}, depth + 1, propagate) + ) return results # Find INPUT_ONLY/OUTPUT_ONLY from container types that reference this type