Skip to content

Inline existing resources with runtime names#17904

Merged
jeskew merged 8 commits into
mainfrom
jeskew/inline-existing-resources
Aug 28, 2025
Merged

Inline existing resources with runtime names#17904
jeskew merged 8 commits into
mainfrom
jeskew/inline-existing-resources

Conversation

@jeskew
Copy link
Copy Markdown
Contributor

@jeskew jeskew commented Aug 26, 2025

Resolves #10731

Description

This PR causes existing AZ resources whose names use runtime values to be "inlined" -- no "existing": true resource declaration will be emitted in the compiled template, and references to the resource ID, name, type, or API version will not use the resourceInfo() function. Resources with an inlined ancestor or scope are themselves inlined.

Example Usage

This PR doesn't introduce any new behavior but instead makes sure that certain templates that deploy successfully with a non-symbolic name compilation target continue to deploy successfully when compiled to language version 2.0.

For example, given the following template:

resource sa1 'Microsoft.Storage/storageAccounts@2022-09-01' existing = {
  name: uniqueString('foo')
}

resource sa2 'Microsoft.Storage/storageAccounts@2022-09-01' existing = {
  name: uniqueString('foo', sa1.properties.accessTier)
}

output sa1_id string = sa1.id
output sa2_id string = sa2.id

A non-symbolic name compilation will produce:

{
  ...
  "resources": [],
  "outputs": {
    "sa1_id": {
      "type": "string",
      "value": "[resourceId('Microsoft.Storage/storageAccounts', uniqueString('foo'))]"
    },
    "sa2_id": {
      "type": "string",
      "value": "[resourceId('Microsoft.Storage/storageAccounts', uniqueString('foo', reference(resourceId('Microsoft.Storage/storageAccounts', uniqueString('foo')), '2022-09-01').accessTier))]"
    }
  }
}

whereas a symbolic name compilation will produce:

{
  ...
  "languageVersion": "2.0",
  "resources": {
    "sa1": {
      "existing": true,
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2022-09-01",
      "name": "[uniqueString('foo')]"
    },
    "sa2": {
      "existing": true,
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2022-09-01",
      "name": "[uniqueString('foo', reference('sa1').accessTier)]", // <-- causes a deployment validation failure because 'reference' function is used in a resource name
      "dependsOn": [
        "sa1"
      ]
    }
  },
  "outputs": {
    "sa1_id": {
      "type": "string",
      "value": "[resourceInfo('sa1').id]"
    },
    "sa2_id": {
      "type": "string",
      "value": "[resourceInfo('sa2').id]"
    }
  }
}

The above template will not raise any compilation errors but will fail to deploy with a validation error.

With this PR, the symbolic name compilation would instead produce the following:

{
  ...
  "languageVersion": "2.0",
  "resources": {
    "sa1": {
      "existing": true,
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2022-09-01",
      "name": "[uniqueString('foo')]"
    }
  },
  "outputs": {
    "sa1_id": {
      "type": "string",
      "value": "[resourceInfo('sa1').id]"
    },
    "sa2_id": {
      "type": "string",
      "value": "[resourceId('Microsoft.Storage/storageAccounts', uniqueString('foo', reference('sa1').accessTier))]"
    }
  }
}

This template will deploy without issue since sa2 isn't in the compiled JSON. This mirrors the "inlining" process we do with variables that contain runtime expressions since Bicep allows those but ARM does not. Only ARM existing resources can be inlined, as extensibility resources don't have resource IDs.

One user-observable behavioral difference introduced by this PR is that an error-level diagnostic will be raised if any resource or module has an explicit dependency on an inlined existing resource or if an inlined existing resource has any explicit dependencies. (See the tests added in d62fd90 for an example of what is now blocked.) I wouldn't consider this to be a backwards-incompatible change because while Bicep didn't raise any errors about these scenarios, the compiled templates would fail to deploy.

Microsoft Reviewers: Open in CodeFlow

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Aug 26, 2025

Test this change out locally with the following install scripts (Action run 17300505244)

VSCode
  • Mac/Linux
    bash <(curl -Ls https://aka.ms/bicep/nightly-vsix.sh) --run-id 17300505244
  • Windows
    iex "& { $(irm https://aka.ms/bicep/nightly-vsix.ps1) } -RunId 17300505244"
Azure CLI
  • Mac/Linux
    bash <(curl -Ls https://aka.ms/bicep/nightly-cli.sh) --run-id 17300505244
  • Windows
    iex "& { $(irm https://aka.ms/bicep/nightly-cli.ps1) } -RunId 17300505244"

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Aug 26, 2025

Dotnet Test Results

    90 files   -     45      90 suites   - 45   38m 21s ⏱️ - 21m 16s
12 310 tests  -      2  12 310 ✅  -      2  0 💤 ±0  0 ❌ ±0 
28 365 runs   - 14 143  28 365 ✅  - 14 143  0 💤 ±0  0 ❌ ±0 

Results for commit 98ce2fa. ± Comparison against base commit 934ac5c.

This pull request removes 1923 and adds 663 tests. Note that renamed tests count towards both.

		nestedProp1: 1
		nestedProp2: 2
		prop1: true
		prop2: false
	1
	2
	\$'")
	prop1: true
	prop2: false
…
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000
�ӽ\u000e�0\u0010\u0007��>E�\u0003Ԗ�W0aw1q�\u0001*�\u0011#H\u0000\u0013\u0012�\u000b�q���ab�c�^�����6[�	�\u0015\u0003�a���x\u000b��=�\u0008e�0o�p�A\u0012B��'�q�j[���q�\u000f�\u0003j�4�HtK\u0017�\u0018�B?� �U[��j"����Q�'ذKu˻�l�}�g:\u0003>�7݊��KE��Z�\u000f\u00124�5���%����\u0007�\u000e96\u0005�5&;̎Xz\u001bz��
�K��8��L�\u0005�\u0011Y\u0012\u0000\u000c\u0000\u0000,"Value cannot be null. (Parameter 'source')")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000
��A\u000b�0\u0014\u0007��\u0014�\u0007Xon{��C�!#,\u0008���A\u0006Z��Зo\u001e�K�E+h���`oo�G�\u001b�,�NMYQ\u0014\u0012ǔ�
,\u0014��z�	E�z\u0000\u0002J"'�kz��kU�Ҷ�~P0�t��&b��\u0019(�)*\u0001\u001c��;t��л��\u0018eEj\u001az��E[�w����N���
7�w��@\u0010&Ar\u000c��$�RJ���d������d7[���r�N��g��o��8��\u000c�\u000e$�k�\u0000\u000c\u0000\u0000,"'7' is an invalid end of a number. Expected a delimiter. Path: $.INVALID_JSON | LineNumber: 0 | BytePositionInLine: 20.")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000
��K
�0\u0010\u0006�=EN���1�E�.�BЂ\u000f�J\u001fX\u0010�n�\u0010\����`�ef �	��v��\u0016�Pԍ m(\u0013ln\u0010�֣�\u0003��о\u0000\u0003kH2���'\u0019�5���(K�����=�E�a�
��F �%�U(��Jȯ�=板���Ź���_N��~e2��\u0007���O�\u0007�\u000c
\u0018E�\u0014a�?*\u0013�H&�<��G�	�$I�\u0018�{���\u0000\u000c\u0000\u0000,"The path: index.json was not found in artifact contents")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000\u0003�ӽ\u000e�0\u0010\u0007��>E��ܵ\�\u0003����(�\u001f�\u0018
������\u0001�`b�c��]s��do�]a�E텒�L\u0004�\u001b\u0004:MG�\u0007H�м\u0000\u0003C\u0004��~�IF���u\u0018e�^?Hf�6��ѐV\u0018��\u0015\u0019*��n��a�\u000e��6��uWWuN\|冂r�`�F�����\u001e��\u000f \u0019\u0012��R����T�5_$�����c�	�(��5<\u0001�K��\u0000\u000c\u0000\u0000,"The path: index.json was not found in artifact contents")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000\u0003��A\u000b�0\u0014\u0007��\u0014�\u000f0��o��C�!#*\u0008��H!\u0003-�@��7\u000f�%�\u0016��q{��x��ܭn\u0016�NҲb�\u0008��H߸!}��y\u000b|E@=q�\u0015"\u0012B��'y�Vպ4����\u0007���:��\u0008\u0014J\u000f̞B\u0016xR\u0001���\u001d�� �W]���H҆��Kі�]��~��ᵿ�z|�?\u0017>\u0001��I!A\u0001�\u0012�䑎��?��݉���*�\u001f���ڙR�\u0003�{&˲,kx\u000f�\u000eJ�\u0000\u000c\u0000\u0000,"'7' is an invalid end of a number. Expected a delimiter. Path: $.INVALID_JSON | LineNumber: 0 | BytePositionInLine: 20.")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000\u0003��K
�0\u0010\u0006�=E�\u0001b\u001eM�
ݻ\u0011�x�؎X���\u0011
��M\u0017��M\u001f������0�d�7�\u0016L\u0006uC\u0004�����QG�a�{��\u001a�o\u0014Q-%G\u0008��o���XS�U�������\u0005$LK%��SL"\u001d�8^�R:Pb1��='y�AK.ͭ캋����
�\p�\u0019��O�˿�R(��f�*)���,����?pp(�� ����8B\u001dl��\\u001bx.���y�7�\u0017��?�\u0000\u000c\u0000\u0000,"Value cannot be null. (Parameter 'source')")
Bicep.Core.IntegrationTests.DirectResourceCollectionTests ‑ DirectResourceCollectionAccess_NotAllowedWithinLoops ("output loopOutput array = [for i in range(0, 2): {
  prop: map(containerWorkers, (w) => w.properties.ipAddress.ip)
}]")
Bicep.Core.IntegrationTests.DirectResourceCollectionTests ‑ DirectResourceCollectionAccess_NotAllowedWithinLoops ("resource propertyLoop 'Microsoft.ContainerInstance/containerGroups@2022-09-01' = {
  name: 'gh9440-loop'
  location: 'westus'
  properties: {
    containers: [for i in range(0, 2): {
      name: 'gh9440-w1c-${i}'
      properties: {
        command: [
          'echo "${join(map(containerWorkers, (w) => w.properties.ipAddress.ip), ',')}"'
        ]
      }
    }]
  }
}")
Bicep.Core.IntegrationTests.DirectResourceCollectionTests ‑ DirectResourceCollectionAccess_NotAllowedWithinLoops ("var loopVar = [for i in range(0, 2): {
  prop: map(containerWorkers, (w) => w.properties.ipAddress.ip)
}]")
Bicep.Core.IntegrationTests.Emit.InliningTests ‑ Access_to_non_resourceInfo_properties_of_existing_resources_with_existing_resource_scopes_with_runtime_names_should_be_blocked
…

♻️ This comment has been updated with latest results.

# Conflicts:
#	src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs
@jeskew jeskew requested review from a team and Copilot August 27, 2025 14:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements "inlining" of existing Azure resources whose names use runtime values. When an existing resource has runtime-dependent identifiers, no "existing": true resource declaration is emitted in the compiled ARM template, and property references are resolved directly using functions like resourceId() and reference() instead of resourceInfo().

  • Extends the InlineDependencyVisitor to identify existing resources that require inlining due to runtime names
  • Modifies resource type resolution to handle inlined existing resources differently
  • Updates property access validation to allow access to id and name properties of inlined resources
  • Adds new diagnostic checks to prevent explicit dependencies on or from inlined resources

Reviewed Changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/Bicep.Core/TypeSystem/Providers/ResourceTypeResolver.cs Updates resource type resolution logic to handle inlined existing resources with topological sorting
src/Bicep.Core/TypeSystem/NestedRuntimeMemberAccessValidator.cs Expands validation to include resource id property for inlined resources
src/Bicep.Core/TypeSystem/DeployTimeConstantViolationVisitor.cs Updates property accessibility checks for Azure resources
src/Bicep.Core/Semantics/SemanticModel.cs Adds lazy initialization for symbols to inline and scope data
src/Bicep.Core/Intermediate/ExpressionBuilder.cs Filters out inlined existing resources from emitted resources
src/Bicep.Core/Emit/ScopeHelper.cs Updates scope data access to use semantic model
src/Bicep.Core/Emit/InlineDependencyVisitor.cs Extends visitor to handle existing resource inlining with dependency analysis
src/Bicep.Core/Emit/ExpressionConverter.cs Conditionally disables resourceInfo codegen for inlined resources
src/Bicep.Core/Emit/EmitterContext.cs Removes moved functionality to semantic model
src/Bicep.Core/Emit/EmitLimitationInfo.cs Removes scope data fields moved to semantic model
src/Bicep.Core/Emit/EmitLimitationCalculator.cs Adds validation for explicit dependencies on inlined resources
src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs Adds new diagnostic messages for inlined resource dependency violations
Comments suppressed due to low confidence (4)

src/Bicep.Core/Emit/InlineDependencyVisitor.cs:102

  • The variable name 'predecssor' is misspelled. It should be 'predecessor'.
            visitor.Visit(variable);

src/Bicep.Core/Emit/InlineDependencyVisitor.cs:104

  • The variable name 'predecssor' is misspelled. It should be 'predecessor'.
            if (!visitor.shouldInlineCache.TryGetValue(variableSymbol, out var shouldInline) || shouldInline != Decision.Inline)

src/Bicep.Core/Emit/InlineDependencyVisitor.cs:98

  • The variable name 'predecssor' is misspelled. It should be 'predecessor'.
                return false;

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@jeskew
Copy link
Copy Markdown
Contributor Author

jeskew commented Aug 27, 2025

This PR is a bit dense, but the substantive changes are in InlineDependencyVisitor and ResourceTypeResolver. These have been added as separate commits so they can be reviewed in isolation if desired:

jeskew added 2 commits August 27, 2025 11:58
# Conflicts:
#	src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs
@jeskew jeskew merged commit ce21813 into main Aug 28, 2025
44 checks passed
@jeskew jeskew deleted the jeskew/inline-existing-resources branch August 28, 2025 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Symbolic name templates cannot use runtime values in existing resource names

3 participants