Azure / bicep

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

Referencing Output of Modules results in Error. BCP178, BCP120 #7328

Closed yks0000 closed 2 years ago

yks0000 commented 2 years ago

Bicep version Bicep CLI version 0.7.4 (5afc312467)

Describe the bug

Unable to use output of a module in a resource block declared in child bicep file. Getting error as below

  1. This expression is being used in the for-expression, which requires a value that can be calculated at the start of the deployment. Properties of static_stg which can be calculated at the start include "name".bicep(BCP178)

  2. This expression is being used in an assignment to the "name" property of the "Microsoft.Cdn/profiles/afdEndpoints/routes" type, which requires a value that can be calculated at the start of the deployment. Properties of static_stg which can be calculated at the start include "name".bicep(BCP120)

To summarize, I am trying to create a CDN profile with some custom domains. Later using that module in child file (say, stage.bicep and prod.bicep) and adding routes, rules to it. While referring to output in name and within the property in child bicep, getting errors as above.

To Reproduce

Create a module with file profile.bicep

@description('Name of Azure CDN SKU')
var sku_name = 'Premium_AzureFrontDoor'

@description('Name of  CDN Profile')
param cdn_profile_name string

@description('Tags to identify resource owner')
param resource_owner  string = 'cloud@example.com'

@description('KeyVault external Id for Custom certificate for custom domains')
param key_vault_external_id string

@description('KeyVault secret name for custom domain')
param key_vault_secret_name string

@description('Custom Domain Array')
param customDomains array = [
  {
    hostname: 'static-afd-test.example.com'
  }
]

@description('Create CDN Profile')
resource cdn_profile_name_resource 'Microsoft.Cdn/profiles@2021-06-01' = {
  name: cdn_profile_name
  location: 'Global'
  tags: {
    contactPerson: resource_owner
  }
  sku: {
    name: sku_name
  }
  properties: {
    originResponseTimeoutSeconds: 60
  }
}

@description('Attach Custom Certificate from Key Vault')
resource cdn_custom_cert_secret_resource 'Microsoft.Cdn/profiles/secrets@2021-06-01' = {
  name: '${cdn_profile_name}/cdn_byoc_secret_resource'
  properties: {
    parameters: {
      type: 'CustomerCertificate'
      secretSource: {
        id: '${key_vault_external_id}/secrets/${key_vault_secret_name}'
      }
      useLatestVersion: true
    }
  }
}

@description('Create Custom Domains to be used for CDN profile')
resource cdn_custom_domains_resource 'Microsoft.Cdn/profiles/customdomains@2021-06-01' = [for (customdomain, index) in customDomains: {
  name: '${cdn_profile_name}/${replace(customdomain.hostname, '.', '-')}'
  properties: {
    hostName: customdomain.hostname
    tlsSettings: {
      certificateType: 'CustomerCertificate'
      minimumTlsVersion: 'TLS12'
      secret: {
        id: cdn_custom_cert_secret_resource.id
      }
    }
  }
}]

output cdn_profile_id string = cdn_profile_name_resource.id
output custom_domain_ids array = [for (customdomain, index) in customDomains: {
  custom_domain_id: cdn_custom_domains_resource[index].id
}]

Try to use the Module in stage.bicep

module static_stg 'profile.bicep' = {
  name: 'static.stg'
  params: {
    cdn_profile_name: 'static'
    key_vault_secret_name: 'some_value'
    key_vault_external_id: 'some_value'
  }
}

resource static_stg_aero_routes 'Microsoft.Cdn/profiles/afdendpoints/routes@2021-06-01' = {
  name: '${static_stg.outputs.cdn_profile_id}/aero-assets-route'
  properties: {
    cacheConfiguration: {
      compressionSettings: {
        isCompressionEnabled: true
        contentTypesToCompress: [
          'application/eot'
          'application/font'
          'application/font-sfnt'
          'application/javascript'
          'application/json'
          'application/opentype'
          'application/otf'
          'application/pkcs7-mime'
          'application/truetype'
          'application/ttf'
          'application/vnd.ms-fontobject'
          'application/xhtml+xml'
          'application/xml'
          'application/xml+rss'
          'application/x-font-opentype'
          'application/x-font-truetype'
          'application/x-font-ttf'
          'application/x-httpd-cgi'
          'application/x-javascript'
          'application/x-mpegurl'
          'application/x-opentype'
          'application/x-otf'
          'application/x-perl'
          'application/x-ttf'
          'font/eot'
          'font/ttf'
          'font/otf'
          'font/opentype'
          'image/svg+xml'
          'text/css'
          'text/csv'
          'text/html'
          'text/javascript'
          'text/js'
          'text/plain'
          'text/richtext'
          'text/tab-separated-values'
          'text/xml'
          'text/x-script'
          'text/x-component'
          'text/x-java-source'
        ]
      }
      queryStringCachingBehavior: 'UseQueryString'
    }
    customDomains: [for (domain, index) in static_stg.outputs.custom_domain_ids: {
      id: domain.custom_domain_id
    }]
    originGroup: {
      id: 'some_id'
    }
    ruleSets: [
      {
        id: 'some_rules'
      }
    ]
    supportedProtocols: [
      'Http'
      'Https'
    ]
    patternsToMatch: [
      '/assets/*'
    ]
    forwardingProtocol: 'MatchRequest'
    linkToDefaultDomain: 'Enabled'
    httpsRedirect: 'Enabled'
    enabledState: 'Enabled'
  }
}

To help find where exactly the error is, please check the snippet as below:

customDomains: [for (domain, index) in static_stg.outputs.custom_domain_ids: {
      id: domain.custom_domain_id
    }]

and

name: '${static_stg.outputs.cdn_profile_id}/aero-assets-route'

image

image

anthony-c-martin commented 2 years ago

As you've found, we have some limitations in the deployments engine, which makes using module outputs tricky.

  1. The name property for every resource has to be known at the start of the deployment - meaning it cannot be calculated based on an output from a module. This is because the full deployment graph must be calculable when the file is first deployed. The best workaround I can suggest here is to re-establish the reference to the CDN resource using existing, rather than using the module to output the ID. In your parent file, this would look something like like:

    var cdnName = 'static'
    
    module static_stg 'profile.bicep' = {
      name: 'static.stg'
      params: {
        cdn_profile_name: cdnName
        ...
      }
    }
    
    resource cdn 'Microsoft.Cdn/profiles@2021-06-01' existing = {
      name: cdnName
    }
    
    resource afdEndpoint 'Microsoft.Cdn/profiles/afdendpoints@2021-06-01' existing = {
      parent: cdn
      name: // what goes here?
    }
    
    resource static_stg_aero_routes 'Microsoft.Cdn/profiles/afdendpoints/routes@2021-06-01' = {
      parent: afdEndpoint
      name: 'aero-assets-route'
      properties: {
        ...
      }
    }
  2. Again, you'll need to do something creative to workaround this limitation that for-loops must be evaluated at deploy-time:

    var cdnName = 'static'
    var customDomainConfig = [
      { hostname: 'static-afd-test.example.com' }
    ]
    
    module static_stg 'profile.bicep' = {
      name: 'static.stg'
      params: {
        cdn_profile_name: cdnName
        customDomainConfig: customDomainConfig
        ...
      }
    }
    
    resource cdn 'Microsoft.Cdn/profiles@2021-06-01' existing = {
      name: cdnName
    }
    
    resource customDomains 'Microsoft.Cdn/profiles/customdomains@2021-06-01' existing = [for config in customDomainConfig: {
      parent: cdn
      name: replace(config.hostname, '.', '-')
    }]
    
    resource afdEndpoint 'Microsoft.Cdn/profiles/afdendpoints@2021-06-01' existing = {
      parent: cdn
      name: // what goes here?
    }
    
    resource static_stg_aero_routes 'Microsoft.Cdn/profiles/afdendpoints/routes@2021-06-01' = {
      parent: afdEndpoint
      name: 'aero-assets-route'
      properties: {
        ...
        customDomains: [for (config, index) in customDomainConfig: {
          id: customDomains[index]
        }]
        ...
      }
    }

    Once #7083 has been merged, you should be able to do this with the map() function instead which would not have this limitation.

yks0000 commented 2 years ago

Thank you @anthony-c-martin. Will watch for https://github.com/Azure/bicep/pull/7083

prajaybasu commented 2 years ago

Any examples with the map function?