Azure / bicep

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

v0.4.1008 built templates throw `DeploymentFailed` exceptions #4943

Closed zhengchang907 closed 1 year ago

zhengchang907 commented 2 years ago

Bicep version v0.4.1008

Describe the bug v0.4.1008 built templates throw deployment exceptions: {"code":"DeploymentFailed","message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.","details":[{"code":"InvalidTemplate","message":"Unable to process template language expressions for resource '/subscriptions/05887623-95c5-4e50-a71c-6e1c738794e2/resourceGroups/zhengtest102101rg/providers/Microsoft.Resources/deployments/core' at line '43' and column '5'. 'The language expression property 'rgName' doesn't exist, available properties are 'templateHash, mode, provisioningState, timestamp, duration, correlationId, providers, dependencies, outputs, outputResources'.'"}]}

To Reproduce Steps to reproduce the behavior:

Additional context Cannot reproduce it using v0.4.613 or v0.4.451

anthony-c-martin commented 2 years ago

Thanks for raising this, it definitely looks like a regression in v0.4.1008.

One thing that's slightly unusual about your mainTemplate.bicep file is the use of the reference() function. Is there a reason for it?

Instead of the following:

var ref_preDeployment = reference(resourceId('Microsoft.Resources/deployments', 'pre'))

module pre './module/pre.bicep' = {
  name: 'pre'
}

module core './module/core.bicep' = {
  name: 'core'
  params: {
    resourceGroupName: ref_preDeployment.outputs.rgName.value
  }
  dependsOn: [
    pre
  ]
}

The more idiomatic way to do this in Bicep would be:

module pre './module/pre.bicep' = {
  name: 'pre'
}

module core './module/core.bicep' = {
  name: 'core'
  params: {
    resourceGroupName: pre.outputs.rgName
  }
}
zhengchang907 commented 2 years ago

Thanks for raising this, it definitely looks like a regression in v0.4.1008.

One thing that's slightly unusual about your mainTemplate.bicep file is the use of the reference() function. Is there a reason for it?

Instead of the following:

var ref_preDeployment = reference(resourceId('Microsoft.Resources/deployments', 'pre'))

module pre './module/pre.bicep' = {
  name: 'pre'
}

module core './module/core.bicep' = {
  name: 'core'
  params: {
    resourceGroupName: ref_preDeployment.outputs.rgName.value
  }
  dependsOn: [
    pre
  ]
}

The more idiomatic way to do this in Bicep would be:

module pre './module/pre.bicep' = {
  name: 'pre'
}

module core './module/core.bicep' = {
  name: 'core'
  params: {
    resourceGroupName: pre.outputs.rgName
  }
}

Hi Anthony, I agree with you on this example. However, in our product project we have the reference() thing up there with some other logic integrated like 'if'. And we use reference() so that the later parts can look prettier.

alex-frankel commented 2 years ago

@zhengchang907 -- can you clarify what looks prettier when you use reference()? Generally speaking, sticking with idiomatic bicep results in less characters required to express the same amount of information. Can you provide an example where you use reference() in combination with a condition/ternary operator?

zhengchang907 commented 2 years ago

@zhengchang907 -- can you clarify what looks prettier when you use reference()? Generally speaking, sticking with idiomatic bicep results in less characters required to express the same amount of information. Can you provide an example where you use reference() in combination with a condition/ternary operator?

Hi Alex, In our usage, sometime we have different deployments for the same resource, decided by user inputs. And later if we want to reference the output of the deployment, we will determine the deployment name first, then reference the deployment using reference(), and finally use output values of it. We use this practice to approach a clear code structure.

And we are surely open to better ways to do so.

Here is a sample code from our repo

// Determine the deployment name
var name_networkDeployment = enableAppGWIngress ? (appGatewayCertificateOption == const_appgwSSLCertOptionGenerateCert ? 'ds-networking-deployment-1': 'ds-networking-deployment') : 'ds-networking-deployment-2'
// Reference the deployment
var ref_networkDeployment = reference(name_networkDeployment)

module networkingDeployment '_deployment-scripts/_ds-create-networking.bicep' = if (enableAppGWIngress && appGatewayCertificateOption != const_appgwSSLCertOptionGenerateCert) {
  name: 'ds-networking-deployment'
  params: {
    // ...
    appgwFrontendSSLCertData: existingKeyvault.getSecret(keyVaultSSLCertDataSecretName)
    appgwFrontendSSLCertPsw: existingKeyvault.getSecret(keyVaultSSLCertPasswordSecretName)
    // ...
  }
  dependsOn: [
    // ...
  ]
}

// Wrokaround for "Error BCP180: Function "getSecret" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator."
module networkingDeployment2 '_deployment-scripts/_ds-create-networking.bicep' = if (enableAppGWIngress && appGatewayCertificateOption == const_appgwSSLCertOptionGenerateCert) {
  name: 'ds-networking-deployment-1'
  params: {
    // ...
    appgwFrontendSSLCertData: existingKeyvault.getSecret(keyVaultSSLCertDataSecretName)
    appgwFrontendSSLCertPsw: 'null'
    // ...
  }
  dependsOn: [
    // ...
  ]
}

module networkingDeployment3 '_deployment-scripts/_ds-create-networking.bicep' = if (!enableAppGWIngress) {
  name: 'ds-networking-deployment-2'
  params: {
    // ...
    appgwFrontendSSLCertData: 'null'
    appgwFrontendSSLCertPsw: 'null'
    // ...
  }
  dependsOn: [
    // ...
  ]
}

// Use the deployment output to format values we need here
output adminConsoleExternalUrl string = enableAppGWIngress ? (enableDNSConfiguration ? format('http://{0}console', const_appgwAdminCustomDNSAlias) : format('http://{0}/console', appgwDeployment.outputs.appGatewayAlias)) : ref_networkDeployment.outputs.adminConsoleLBUrl.value

Origin code: https://github.com/oracle/weblogic-azure/blob/main/weblogic-azure-aks/src/main/bicep/modules/networking.bicep

slapointe commented 2 years ago

Hi @zhengchang907

Until this is fixed, here is a workaround that respect what you are doing and is a more "bicep native" instead of calling the ARM reference() function yourself.

If I start from your code in mainTemplate.bicep in your zip file. I replaced the reference() call and changed it for an existing resource reference to your prior deployment. You'll need to add a .properties member before the .outputs member for the output references you are doing.

Before

var ref_preDeployment = reference(resourceId('Microsoft.Resources/deployments', 'pre'))
...
resourceGroupName: ref_preDeployment.outputs.rgName.value

After

resource ref_preDeployment 'Microsoft.Resources/deployments@2021-04-01' existing = {
  name: 'pre'
}
...
resourceGroupName: ref_preDeployment.properties.outputs.rgName.value

Full example:

//var ref_preDeployment = reference(resourceId('Microsoft.Resources/deployments', 'pre'))

resource ref_preDeployment 'Microsoft.Resources/deployments@2021-04-01' existing = {
  name: 'pre'
}

module pre './module/pre.bicep' = {
  name: 'pre'
}

module core './module/core.bicep' = {
  name: 'core'
  params: {
    resourceGroupName: ref_preDeployment.properties.outputs.rgName.value
  }
  dependsOn: [
    pre
  ]
}

I've tested this on Bicep CLI version 0.4.1124 (66c84c8ee5)

Hope it helps.

edburns commented 2 years ago

Hello @stephaniezyen , any update on this? We're still tracking it as a dependency for our Deployment Script resources for Java EE on Azure.

alex-frankel commented 2 years ago

@edburns -- does the workaround that @slapointe provided work for your team? Personally, I think this is safer than using the reference() function directly.

alex-frankel commented 1 year ago

I'm closing this due to no activity since it is in Top10. Feel free to correct me it it's still an urgent issue.