bicep icon indicating copy to clipboard operation
bicep copied to clipboard

copyIndex error "The provided copy name '' doesn't exist in the resource" when using triple FOR-loops for the same array

Open doubleyouvdb opened this issue 3 years ago • 16 comments

Bicep version 0.4.63

Describe the bug When you are using in a variable with FOR-loop a copyIndex to refer to a certain child resource reference in another FOR-loop for the same array of objects which also uses an index to retrieve its parent that is also in another FOR-loop for the same set of objects, then in the autogenerated ARM template, all the translated copyIndexes for the parent in the copy variable get blanked out.

To Reproduce Deploy AKS Cluster with the following code for agent pool profiles. For example purpose, only 'name' and the here relevant property 'vnetSubnetID' are listed. The properties of both the resource references and of the variable 'agentPoolProfiles' are all populated using the same array [for agentPool in aksCluster.agentPools].

// Get existing vnets
resource virtualNetworks 'Microsoft.Network/virtualNetworks@2021-02-01' existing = [for agentPool in aksCluster.agentPools: {
  name: union(defaultAgentPoolObject, agentPool).virtualNetwork.virtualNetworkName
  scope: resourceGroup(union(defaultAgentPoolObject, agentPool).virtualNetwork.subscriptionId, union(defaultAgentPoolObject, agentPool).virtualNetwork.resourceGroup)
}]

// Get existing subnets
resource subnets 'Microsoft.Network/virtualNetworks/subnets@2021-02-01' existing = [for (agentPool, i) in aksCluster.agentPools: {
  name: union(defaultAgentPoolObject, agentPool).virtualNetwork.subnetName
  parent: virtualNetworks[i]
}]

var agentPoolProfiles = [for (agentPool, i) in aksCluster.agentPools: {
  name: agentPool.name
  vnetSubnetID: aksCluster.network.plugin =~ 'azure' ? subnets[i].id : null
}]

When deploying, this generates error:

An error occurred while deploying this offering. Error: 
InvalidTemplate - Long running operation failed with status
'Failed'. Additional Info:'Deployment template language
expression evaluation failed: 'The template language function
'copyIndex' has an invalid argument. The provided copy name ''
doesn't exist in the resource. Please see
https://aka.ms/arm-copy for usage details.'. Please see
https://aka.ms/arm-template-expressions for usage details.'

When examining the autogenerated ARM template, the cause is clear: in the variable, the copyIndex() where a property of the parent resource is called, is always empty. It should be copyIndex('agentPoolProfiles').

"copy": [
      {
        "name": "agentPoolProfiles",
        "count": "[length(parameters('aksCluster').agentPools)]",
        "input": {
          "name": "[parameters('aksCluster').agentPools[copyIndex('agentPoolProfiles')].name]",
          "vnetSubnetID": "[if(equals(toLower(parameters('aksCluster').network.plugin), toLower('azure')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', union(variables('defaultAgentPoolObject'), parameters('aksCluster').agentPools[copyIndex()]).virtualNetwork.subscriptionId, union(variables('defaultAgentPoolObject'), parameters('aksCluster').agentPools[copyIndex()]).virtualNetwork.resourceGroup), 'Microsoft.Network/virtualNetworks/subnets', union(variables('defaultAgentPoolObject'), parameters('aksCluster').agentPools[copyIndex()]).virtualNetwork.virtualNetworkName, union(variables('defaultAgentPoolObject'), parameters('aksCluster').agentPools[copyIndex('agentPoolProfiles')]).virtualNetwork.subnetName), null())]"
        }

Additional context N/A

doubleyouvdb avatar Sep 16 '21 08:09 doubleyouvdb

Btw, found the following workaround to be valid:

// Get existing subnets
resource subnets 'Microsoft.Network/virtualNetworks/subnets@2021-02-01' existing = [for agentPool in aksCluster.agentPools: if (aksCluster.network.plugin =~ 'azure') {
  name: '${agentPool.virtualNetwork.virtualNetworkName}/${agentPool.virtualNetwork.subnetName}'
}]

doubleyouvdb avatar Sep 16 '21 08:09 doubleyouvdb

+1

slavizh avatar Sep 16 '21 08:09 slavizh

here is the simple (standalone) repro for this... removing any complexity of the agentPools or AKS.

  • Looping through VNETS and then looping through Subnets while referencing the VNET (loop) for the parent.
var agentPools = [
  {
    name: 'NodePool1'
    virtualNetwork: {
      virtualNetworkName: 'vnet1'
      subnetName: 'subnet1'
    }
  }
]

resource virtualNetworks 'Microsoft.Network/virtualNetworks@2021-02-01' existing = [for agentPool in agentPools: {
  name: agentPool.virtualNetwork.virtualNetworkName
}]

resource subnets 'Microsoft.Network/virtualNetworks/subnets@2021-02-01' existing = [for (agentPool, index) in agentPools: {
  name: agentPool.virtualNetwork.subnetName
  parent: virtualNetworks[index]
}]

var agentPoolProfiles = [for (agentPool, index) in agentPools: {
  name: agentPool.name
  vnetSubnetID: subnets[index].id
}]

output agentPoolProfiles array = agentPoolProfiles

Expected compiled:

"vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('agentPools')[copyIndex('agentPoolProfiles')].virtualNetwork.virtualNetworkName, variables('agentPools')[copyIndex('agentPoolProfiles')].virtualNetwork.subnetName)]"

Actually compiled:

"vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('agentPools')[copyIndex()].virtualNetwork.virtualNetworkName, variables('agentPools')[copyIndex('agentPoolProfiles')].virtualNetwork.subnetName)]"

brwilkinson avatar Sep 25 '21 00:09 brwilkinson

Thanks for the self-contained repro @brwilkinson. It looks like the root cause is that we're not doing index replacement in a way that preserves the local loop name.

majastrz avatar Mar 31 '22 06:03 majastrz

what is the status of this? I'm facing this issue as well.

santo2 avatar May 19 '22 08:05 santo2

very annoying bug that for me shouldn't be hard to fix. It quite common among many resources as in many cases you have to reference subnet IDs

slavizh avatar May 19 '22 10:05 slavizh

@alex-frankel I saw the mention on the community call as well, that's already good news ;-) Thanks

santo2 avatar May 29 '22 09:05 santo2

this seems to keep moving to vnext, any update on this?

santo2 avatar Aug 03 '22 10:08 santo2

I'm currently investigating this issue. There's a deeper problem with index expression substitution, which makes this more complicated to fix unfortunately.

majastrz avatar Aug 03 '22 18:08 majastrz

could we possibly get an update on this during the community call? it's quite an essential part of using references to another resource which is now not working for certain resources in our scripts. we are awaiting a fix to be able to use bicep for some of our script deployments.

  • this issue is almost a year old now

santo2 avatar Aug 22 '22 12:08 santo2

We are working on a new JSON format that exposes the concepts of symbolic names and existing resources in the JSON. You can enable it in Bicep by setting the BICEP_SYMBOLIC_NAME_CODEGEN_EXPERIMENTAL environment variable to true. For best experience, should be enabled for both Bicep CLI and VS code. One of the goals of the new JSON format is to simplify the code generation of Bicep and eliminate bugs like this one.

I tested it with the @brwilkinson's template and it seemed to produce correct JSON, but I haven't tried deploying it. Can you try turning in on and see if it helps?

majastrz avatar Aug 25 '22 18:08 majastrz

@majastrz can you point me to the document how to set environment variable. I have not done this before and I have not found such reference on internet.

slavizh avatar Aug 29 '22 12:08 slavizh

We don't have any docs that cover this specifically for Bicep. These are just normal system environment variables. Depending on how you're invoking Bicep, the following options may work for you. In a Windows .bat or .cmd file:

set BICEP_SYMBOLIC_NAME_CODEGEN_EXPERIMENTAL=true

In PowerShell:

$env:BICEP_SYMBOLIC_NAME_CODEGEN_EXPERIMENTAL = "true"

In GitHub actions or ADO yaml:

env:
  BICEP_SYMBOLIC_NAME_CODEGEN_EXPERIMENTAL: true

Overtime, we will be adding more experimental features that can be enabled. To make it easier to use them, @jeskew will expose settings to turn on experimental Bicep features in .bicepconfig, but it's not available yet.

For best experience, you should set the env var before opening VS code and before running any Bicep CLI commands. This way the experience will be consistent.

majastrz avatar Aug 29 '22 18:08 majastrz

@majastrz thanks for the help. I was suspecting that might be the way but I wanted to be sure. I have managed to enable the setting but unfortunately without result. Bellow error is generated.

WARNING: Symbolic name support in ARM is experimental, and should be enabled for testing purposes only. Do not enable this setting for any production usage, or you may be unexpectedly broken at any time!
D:\dev\Sentia\lz\lz-kubernetes\test.bicep(25,5) : Warning no-unused-vars: Variable "agentPoolProfiles" is declared but never used. [https://aka.ms/bicep/linter/no-unused-vars]
Unhandled exception. System.NotImplementedException: Mismatch between count of index expressions and inaccessible symbols during array access index replacement.
   at Bicep.Core.Emit.ExpressionConverter.CreateConverterForIndexReplacement(SyntaxBase nameSyntax, SyntaxBase indexExpression, SyntaxBase newContext) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\ExpressionConverter.cs:line 207
   at Bicep.Core.Emit.ExpressionEmitter.EmitExpression(SyntaxBase resourceNameSyntax, SyntaxBase indexExpression, SyntaxBase newContext) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\ExpressionEmitter.cs:line 98
   at Bicep.Core.Emit.ScopeHelper.<>c__DisplayClass7_0.<EmitResourceOrModuleScopeProperties>b__0() in C:\__w\1\s\bicep\src\Bicep.Core\Emit\ScopeHelper.cs:line 367
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WritePropertyWithPosition(SyntaxBase keyPosition, String name, Action valueFunc) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\PositionTrackingJsonTextWriter.cs:line 108
   at Bicep.Core.Emit.ExpressionEmitter.EmitPropertyInternal(LanguageExpression expressionKey, Action valueFunc, SyntaxBase location, Boolean skipCopyCheck) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\ExpressionEmitter.cs:line 451
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(String name, Action valueFunc) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\ExpressionEmitter.cs:line 429
   at Bicep.Core.Emit.ScopeHelper.EmitResourceOrModuleScopeProperties(SemanticModel semanticModel, ScopeData scopeData, ExpressionEmitter expressionEmitter, SyntaxBase newContext) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\ScopeHelper.cs:line 367
   at Bicep.Core.Emit.ScopeHelper.EmitResourceScopeProperties(SemanticModel semanticModel, ScopeData scopeData, ExpressionEmitter expressionEmitter, SyntaxBase newContext) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\ScopeHelper.cs:line 327
   at Bicep.Core.Emit.TemplateWriter.<>c__DisplayClass21_0.<EmitResource>b__0() in C:\__w\1\s\bicep\src\Bicep.Core\Emit\TemplateWriter.cs:line 509
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WriteObjectWithPosition(SyntaxBase sourcePosition, Action propertiesFunc) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\PositionTrackingJsonTextWriter.cs:line 97
   at Bicep.Core.Emit.TemplateWriter.EmitResource(PositionTrackingJsonTextWriter jsonWriter, DeclaredResourceMetadata resource, ExpressionEmitter emitter) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\TemplateWriter.cs:line 400
   at Bicep.Core.Emit.TemplateWriter.EmitResources(PositionTrackingJsonTextWriter jsonWriter, ExpressionEmitter emitter) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\TemplateWriter.cs:line 358
   at Bicep.Core.Emit.TemplateWriter.GenerateTemplateWithoutHash(PositionTrackingJsonTextWriter jsonWriter) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\TemplateWriter.cs:line 137
   at Bicep.Core.Emit.TemplateWriter.Write(SourceAwareJsonTextWriter writer) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\TemplateWriter.cs:line 103
   at Bicep.Core.Emit.TemplateEmitter.<>c__DisplayClass8_0.<Emit>b__0() in C:\__w\1\s\bicep\src\Bicep.Core\Emit\TemplateEmitter.cs:line 68
   at Bicep.Core.Emit.TemplateEmitter.EmitOrFail(Func`1 write) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\TemplateEmitter.cs:line 123
   at Bicep.Core.Emit.TemplateEmitter.Emit(Stream stream) in C:\__w\1\s\bicep\src\Bicep.Core\Emit\TemplateEmitter.cs:line 59
   at Bicep.Cli.Services.CompilationWriter.ToFile(Compilation compilation, String outputPath) in C:\__w\1\s\bicep\src\Bicep.Cli\Services\CompilationWriter.cs:line 27
   at Bicep.Cli.Commands.BuildCommand.RunAsync(BuildArguments args) in C:\__w\1\s\bicep\src\Bicep.Cli\Commands\BuildCommand.cs:line 69
   at Bicep.Cli.Program.RunAsync(String[] args) in C:\__w\1\s\bicep\src\Bicep.Cli\Program.cs:line 78
   at Bicep.Cli.Program.Main(String[] args) in C:\__w\1\s\bicep\src\Bicep.Cli\Program.cs:line 65
   at Bicep.Cli.Program.<Main>(String[] args)

The minimum code to reproduce it:


param aksCluster object

var defaultAgentPool = {
  virtualNetwork: {
    subscriptionId: subscription().subscriptionId
    resourceGroup: 'dummy'
    virtualNetworkName: ''
    subnetName: ''
  }
}

resource virtualNetworks 'Microsoft.Network/virtualNetworks@2021-08-01' existing = [for agentPool in aksCluster.agentPools: {
  name: union(defaultAgentPool, agentPool).virtualNetwork.virtualNetworkName
  scope: resourceGroup(union(defaultAgentPool, agentPool).virtualNetwork.subscriptionId, agentPool.virtualNetwork.resourceGroup)
}]

// // Get existing subnets
resource subnets 'Microsoft.Network/virtualNetworks/subnets@2021-08-01' existing = [for (agentPool, i) in aksCluster.agentPools: {
  name: union(defaultAgentPool, agentPool).virtualNetwork.subnetName
  parent: virtualNetworks[i]
}]


var agentPoolProfiles = [for (agentPool, i) in aksCluster.agentPools: {
  // only agentPoolProfiles has name as property, agentPools do not have it
  nameProperty: {
    name: agentPool.name
  }
  commonProperties: {
    vnetSubnetID: empty(union(defaultAgentPool, agentPool).virtualNetwork.subnetName) ? null : subnets[i].id
  }
}]

slavizh avatar Aug 30 '22 07:08 slavizh

@majastrz have you managed to look why the new method gives error on this case?

slavizh avatar Sep 13 '22 06:09 slavizh

Yes, we're not picking up the index expression from the parent property which fails an assertion in loop code generation. I'm working on the fix.

majastrz avatar Sep 13 '22 07:09 majastrz

+1

Kaloszer avatar Jan 25 '23 13:01 Kaloszer

@majastrz was anything done in recent versions of Bicep to fix this? When I use the latest sample for reproduction and generate the ARM template it seems that copyIndex() has parameter now so it seems fixed. I am yet to test it with actual deployment but if the ARM code is correct there should not be a problem.

slavizh avatar Jun 30 '23 06:06 slavizh

I think @jeskew made a change that fixed loop codegen with indexed scopes. I will double check.

majastrz avatar Jul 11 '23 00:07 majastrz

I have done some tests and they show that it is fixed now.

slavizh avatar Jul 11 '23 05:07 slavizh

Thanks for confirming. I will also add your repro as a test case, so it doesn't regress in the future.

majastrz avatar Jul 11 '23 06:07 majastrz

Closing since it appears this is resolved.

alex-frankel avatar Jul 11 '23 16:07 alex-frankel

Hello, I am reproducing a very issue today with Bicep CLI version 0.28.1 Bicep CLI version 0.28.1 (ba1e9f8c1e)

I define variables with the objects for the private endpoints that the AVM modules require. The "subnetResourceId" property adequately takes the copyIndex('storageAccountPrivateEndpoints') value but the privateDnsZoneResourceIds only says copyIndex() so I get "The provided copy name '' doesn't exist in the resource" error when deploying.

Attaching reproduction files. simple.bicep.txt simple.json.txt

Bicep:

var storageAccountPrivateEndpoints = [
  for (config, i) in stampConfigs: {
    subnetResourceId: privateEndpointSubnets[i].id
    location: config.location
    service: 'blob'
    privateDnsZoneGroupName: 'privatelink.blob.${environment().suffixes.storage}'
    privateDnsZoneResourceIds: [
      storageBlobPrivateDnsZones[i].id
    ]
  }
]

Expected:

"variables": {
    "copy": [
      {
        "name": "storageAccountPrivateEndpoints",
        "count": "[length(parameters('stampConfigs'))]",
        "input": {
          "subnetResourceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, format('rg-{0}-{1}-{2}', parameters('networkWorkloadName'), parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].location, parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].stamp)), 'Microsoft.Network/virtualNetworks/subnets', split(format('vnet-apps-{0}-{1}-{2}/{3}', parameters('networkWorkloadName'), parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].shortLocation, parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].stamp, variables('privateEndpointsSnetName')), '/')[0], split(format('vnet-apps-{0}-{1}-{2}/{3}', parameters('networkWorkloadName'), parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].shortLocation, parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].stamp, variables('privateEndpointsSnetName')), '/')[1])]",
          "location": "[parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].location]",
          "service": "blob",
          "privateDnsZoneGroupName": "[format('privatelink.blob.{0}', environment().suffixes.storage)]",
          "privateDnsZoneResourceIds": [
            "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, format('rg-{0}-{1}-{2}', parameters('networkWorkloadName'), parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].location, parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].stamp)), 'Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]"
          ]
        }
      }
    ],
...

Actual:

"variables": {
    "copy": [
      {
        "name": "storageAccountPrivateEndpoints",
        "count": "[length(parameters('stampConfigs'))]",
        "input": {
          "subnetResourceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, format('rg-{0}-{1}-{2}', parameters('networkWorkloadName'), parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].location, parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].stamp)), 'Microsoft.Network/virtualNetworks/subnets', split(format('vnet-apps-{0}-{1}-{2}/{3}', parameters('networkWorkloadName'), parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].shortLocation, parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].stamp, variables('privateEndpointsSnetName')), '/')[0], split(format('vnet-apps-{0}-{1}-{2}/{3}', parameters('networkWorkloadName'), parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].shortLocation, parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].stamp, variables('privateEndpointsSnetName')), '/')[1])]",
          "location": "[parameters('stampConfigs')[copyIndex('storageAccountPrivateEndpoints')].location]",
          "service": "blob",
          "privateDnsZoneGroupName": "[format('privatelink.blob.{0}', environment().suffixes.storage)]",
          "privateDnsZoneResourceIds": [
            "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, format('rg-{0}-{1}-{2}', parameters('networkWorkloadName'), parameters('stampConfigs')[copyIndex()].location, parameters('stampConfigs')[copyIndex()].stamp)), 'Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]"
          ]
        }
      }
    ],

jupacaza avatar Jul 02 '24 18:07 jupacaza

Update:

it seems my issue stemps from using fixed values for the private DNS zone names. The copyIndex('...') value is not populate when I do not reference the objects of the iterated array in the name.

This does not work:

resource storageBlobPrivateDnsZones 'Microsoft.Network/privateDnsZones@2020-06-01' existing = [
  for (config, i) in stampConfigs: {
    name: 'privatelink.blob.${environment().suffixes.storage}' // NOTE THE FIXED VALUE FOR ALL ITEMS IN ARRAY
    scope: resourceGroup('rg-${networkWorkloadName}-${config.location}-${config.stamp}')
  }
]

This works:

var networkResources = [for (config, i) in stampConfigs: {
  rg: 'rg-${networkWorkloadName}-${config.location}-${config.stamp}'
  storageBlobPrivateDnsZoneName: 'privatelink.blob.${environment().suffixes.storage}'
}]

resource storageBlobPrivateDnsZones 'Microsoft.Network/privateDnsZones@2020-06-01' existing = [
  for r in networkResources: {
    name: r.storageBlobPrivateDnsZoneName // NOTE NAME REFERENCES SOMETHING IN THE ITERATED ARRAY
    scope: resourceGroup(r.rg)
  }
]

jupacaza avatar Jul 03 '24 19:07 jupacaza