Azure / bicep

Bicep is a declarative language for describing and deploying Azure resources
MIT License
3.23k stars 748 forks source link

"The template function 'reference' is not expected" - compiler does not catch error when iterating over runtime array #2394

Closed KaiWalter closed 3 years ago

KaiWalter commented 3 years ago

Bicep version Bicep CLI version 0.3.126 (a5e4c2e567)

Describe the bug (Stackoverflow reference : https://stackoverflow.com/questions/67255232/the-template-function-reference-is-not-expected-when-feeding-api-management-pr)

I want to create DNS A records for my internal API Management instance in a private DNS zone azure-api.net along with the API Management deployment:

var privateDnsZoneName = 'azure-api.net'
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: privateDnsZoneName
  location: 'Global'
}

resource privateDnsZoneEntry 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: apim.name
  parent: privateDnsZone
  properties: {
    aRecords: [for addr in apim.properties.privateIPAddresses: {
      ipv4Address: addr
    }]
    ttl: 3600
  }
}

However when deploying it results into this error:

Line |
  57 |  New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroupName `
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | 17:12:13 - Error: Code=InvalidTemplate; Message=Deployment template validation failed: 'The template resource
     | '[format('{0}/{1}', variables('privateDnsZoneName'), parameters('apimName'))]' at line '221' and column '9' is not
     | valid: The template function 'reference' is not expected at this location. Please see
     | https://aka.ms/arm-template-expressions for usage details.. Please see https://aka.ms/arm-template-expressions for
     | usage details.'.

It seems that reference() function is not supported at this place (in ARM):

{
  "type": "Microsoft.Network/privateDnsZones/A",
  "apiVersion": "2020-06-01",
  "name": "[format('{0}/{1}', variables('privateDnsZoneName'), parameters('apimName'))]",
  "properties": {
    "copy": [
      {
        "name": "aRecords",
        "count": "[length(reference(resourceId('Microsoft.ApiManagement/service', parameters('apimName'))).privateIPAddresses)]",
        "input": {
          "ipv4Address": "[reference(resourceId('Microsoft.ApiManagement/service', parameters('apimName'))).privateIPAddresses[copyIndex('aRecords')]]"
        }
      }
    ],
    "ttl": 3600
  },
  "dependsOn": [
    "[resourceId('Microsoft.ApiManagement/service', parameters('apimName'))]",
    "[resourceId('Microsoft.Network/privateDnsZones', variables('privateDnsZoneName'))]"
  ]
}

Trying with a variable in between results in the same error - as practically the same ARM JSON is generated.

var privateDnsZoneName = 'azure-api.net'
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: privateDnsZoneName
  location: 'Global'
}

var apimPrivateIPAddresses = apim.properties.privateIPAddresses

resource privateDnsZoneEntry 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: apim.name
  parent: privateDnsZone
  properties: {
    aRecords: [for addr in apimPrivateIPAddresses: {
      ipv4Address: addr
    }]
    ttl: 3600
  }
}

I found no other way than to split private DNS zone + record creation into a Bicep module and with that getting rid of the reference() function:

param privateDnsZoneName string = 'azure-api.net'
param privateDnsARecordName string
param privateDnsARecordAddresses array

resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: privateDnsZoneName
  location: 'Global'
}

resource privateDnsZoneEntry 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: privateDnsARecordName
  parent: privateDnsZone
  properties: {
    aRecords: [for addr in privateDnsARecordAddresses: {
      ipv4Address: addr
    }]
    ttl: 3600
  }
}

and passing the privateIPAddresses array to the module.

module privateDnsEntry './private-dns.bicep' = {
  name: 'apim-private-dns'
  params: {
    privateDnsZoneName: 'azure-api.net'
    privateDnsARecordName: apim.name
    privateDnsARecordAddresses: apim.properties.privateIPAddresses
  }
}
miqm commented 3 years ago

Thanks @KaiWalter for catching this one.

As I wrote on SO, expressions in loops need to be deployment-time constants and we should catch that.

Additional question is - can the runtime itself be altered to allow such operations?

alex-frankel commented 3 years ago

@shenglol - will this be fixed by your current PR?

edit: when I say "fixed", I mean this error should have been caught at authoring-time. To @miqm's point, ideally this is something we could eventually allow in the runtime.

shenglol commented 3 years ago

No, that PR solves a different problem. I can send a separate PR to fix this once the current one is merged.

alex-frankel commented 3 years ago

Closing as dup of #2090