Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[BUG] Cross-scope `existing` resource reference via symbolic name (languageVersion 2.0) resolves to a malformed Mock, breaking concat()/map() during pre-flight expansion

Chiusa
#3,920 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@oWretch ci sta già lavorando.

Dal 16/9/2026.

  • #3921 di @oWretch — aperta

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
75/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
csharp, powershell
Ambito
devops, tooling

Direzione di ricerca

Start with Arm/Symbols/*, Arm/Deployments/TemplateContext.cs, and Arm/Expressions/Functions.cs, then run the existing Arm/Deployments and Arm/Expressions unit test suites. Trace how symbolic existing resources are resolved during reference() evaluation and add coverage for cross-scope languageVersion 2.0 references; done means their properties resolve correctly through concat() and map() without malformed mocks.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Describe the bug

When a Bicep-compiled ARM template uses languageVersion: "2.0" (symbolic name codegen — triggered e.g. by any compile-time import statement) and contains a cross-scope existing resource, reference() calls against that resource use the bare symbolic name as the first argument (e.g. reference('vnet').addressSpace.addressPrefixes) instead of reference(extensionResourceId(...), apiVersion, 'full').

This is valid, standard ARM syntax for languageVersion 2.0 templates — real Azure deployments resolve it fine because ARM evaluates symbolic references against the resource's declared scope.

However, PSRule's offline expansion (used by GetBicepParamResources / pre-flight bicep(param) expansion, e.g. via ps-rule-assert@2) fails to resolve this form. Downstream use of the result in a strict-typed function such as concat()/map() then throws:

An error occurred evaluating expression '[map(concat(reference('resourceA', ...), reference('resourceB', ...)), ...)]' ... The function 'map' failed. The function 'concat' failed. The arguments for 'Concat' are not in the expected format or type.

Scalar property assignments elsewhere in the same template hit the identical broken resolution but don't surface as hard errors, since plain string/object fields tolerate an unresolved/mock value — making this easy to miss until it feeds an array function.

Root cause (traced in source)

  1. DeploymentVisitor.Emit() (src/PSRule.Rules.Azure/Arm/Deployments/DeploymentVisitor.cs) explicitly skips registering existing resources:
    protected virtual void Emit(TemplateContext context, IResourceValue resource)
    {
        if (resource == null || resource.IsExisting())
            return;
        ...
        context.AddResource(resource);
    }
    
  2. For the symbolic-name (languageVersion 2.0) resources-as-object form, DeploymentVisitor.Reference(context, symbolicName, resource) creates an ExistingResourceValue and registers it only as a symbol (context.AddSymbol(symbol)), never via context.AddResource(...). So the resource's id is never added to TemplateContext._ResourceIds.
  3. TemplateContext.TryGetResource(nameOrResourceId, ...) resolves a symbolic name to an id via symbol.GetId(0), then looks that id up in _ResourceIds — which never contains it for existing resources, so the lookup fails.
  4. Functions.Reference(context, args) then falls back to a placeholder:
    return context.TryGetResource(resourceId, out var resourceValue)
        ? GetReferenceResult(resourceValue, full)
        : full ? new Mock.MockResource(resourceId) : new Mock.MockResource(resourceId)["properties"];
    
  5. Mock.MockResource(resourceId) (src/PSRule.Rules.Azure/Arm/Mocks/Mock.cs) expects resourceId to be a full ARM resource ID and parses it via ResourceHelper.TryResourceIdComponents(...) to build id/type/name/properties. When resourceId is actually just the bare symbolic name (e.g. "vnet"), that parsing produces a malformed/incomplete mock object whose nested property values are not usable as arrays/strings by strict functions like concat()/map().

Note that ObjectDeploymentSymbol/ArrayDeploymentSymbol already retain a reference to the real IResourceValue via Configure(resource) — it's discarded after computing the id string, which is what forces the failed _ResourceIds round-trip in step 3.

To Reproduce

other.bicep

@export()
var unused = 'unused'

main.bicep

targetScope = 'resourceGroup'

import { unused } from 'other.bicep'

resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' existing = {
  scope: resourceGroup('other-rg')
  name: 'vnet1'
}

output addressPrefixes array = vnet.properties.addressSpace.addressPrefixes

bicep build main.bicep --stdout (with the import present) produces languageVersion 2.0 output using the bare symbolic form:

"addressPrefixes": {
  "type": "array",
  "value": "[reference('vnet').addressSpace.addressPrefixes]"
}

Running this through PSRule's pre-flight expansion (Assert-PSRule/ps-rule-assert@2 against the compiled template, or directly via GetBicepParamResources) fails to resolve addressPrefixes correctly and can throw when the value feeds concat()/map() elsewhere in the template.

Removing the import line removes the trigger for languageVersion 2.0 and restores the extensionResourceId(...)-based codegen, which — while still ultimately routed through the same Mock.MockResource fallback for existing resources — receives a well-formed resource ID and therefore avoids the malformed mock.

Expected behavior

PSRule's expansion of existing cross-scope resources should resolve consistently regardless of whether the compiled template uses the resourceId(...)/extensionResourceId(...) form or the bare symbolic-name form used by languageVersion 2.0 templates, so that property access (including through concat()/map()) does not fail or silently produce malformed values.

Suggested fix direction

Rather than round-tripping symbolic names through TemplateContext._ResourceIds (which by design never contains existing resources), TryGetResource could resolve the actual IResourceValue directly from the matched symbol — e.g. by extending IDeploymentSymbol with a GetResource(int index) accessor alongside the existing GetId(int index), and having ObjectDeploymentSymbol/ArrayDeploymentSymbol return the IResourceValue they already hold via Configure(...). Functions.Reference/Functions.References would then get the real ExistingResourceValue (which already computes scope-aware Id/Name) instead of falling back to Mock.MockResource with an unparsable identifier.

This looks like a contained change scoped to Arm/Symbols/*, Arm/Deployments/TemplateContext.cs, and Arm/Expressions/Functions.cs, testable with the existing Arm/Deployments/Arm/Expressions unit test suites — not a large architectural change.

Environment

  • Discovered while investigating https://github.com/Azure/bicep/issues/20321 (closed as not-a-bicep-bug; codegen for languageVersion 2.0 symbolic references is correct/standard ARM behavior).
  • Observed downstream in Azure Pipelines' ps-rule-assert@2 task.
Lingua principale
PowerShell
Stelle
449
Fork
109
Merge medio
1g 6h
PR unite (30g)
6

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di Azure/PSRule.Rules.Azure

Tutte le issue di Azure/PSRule.Rules.Azure

Issue simili

Altre issue su DevOps

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.