Open Meertman opened 1 week ago
The error message could certainly use some improvement/be a diagnostic and not an unhandled exception, but I believe the root cause is using <key vault>.getSecret(...)
within a parameter value. This expression can only be used as the complete parameter value due to a limitation of the ARM platform.
@jeskew, I can confirm that the following work-around does not throw the exception:
Module:
@minLength(1)
@maxLength(50)
@description('Optional: The name of the API.')
param name string
@description('Mandatory: The location of the resource.')
param location string
@description('Optional: A custom URL for the API management service. If not provided, the API management service will not have any custom URL.')
param customUrl string?
@secure()
@description('Optional: A certificate for the custom URL of the API management service. If not provided, the API management service will not have any custom URL.')
param customUrlCertificate string?
@secure()
@description('Optional: The password of the certificate for the custom URL of the API management service. If not provided, the API management service will not have any custom URL.')
param customUrlCertificatePassword string?
@description('Optional: A custom URL of the developer portal of the API management service. If not provided, the developer portal will not have any custom URL.')
param developerPortalUrl string?
@secure()
@description('Optional: A certificate for the custom URL of the developer portal of the API management service. If not provided, the developer portal will not have any custom URL.')
param developerPortalUrlCertificate string?
@secure()
@description('Optional: The password of the certificate for the custom URL of the developer portal of the API management service. If not provided, the developer portal will not have any custom URL.')
param developerPortalUrlCertificatePassword string?
var proxyConfigurations = customUrl != null && customUrlCertificate != null && customUrlCertificatePassword != null ? [{
type: 'Proxy'
hostName: customUrl
encodedCertificate: customUrlCertificate
certificatePassword: customUrlCertificatePassword
}] : []
var portalConfigurations = developerPortalUrl != null && developerPortalUrlCertificate != null && developerPortalUrlCertificatePassword != null ? [{
type: 'DeveloperPortal'
hostName: developerPortalUrl
encodedCertificate: developerPortalUrlCertificate
certificatePassword: developerPortalUrlCertificatePassword
}] : []
var hostnameConfigurations = union(proxyConfigurations, portalConfigurations)
resource apim 'Microsoft.ApiManagement/service@2022-08-01' = {
name: name
location: location
sku: {
name: 'Developer'
capacity: 1
}
properties: {
publisherEmail: 'issue@github.com'
publisherName: 'Github'
hostnameConfigurations: hostnameConfigurations
virtualNetworkType: 'External'
disableGateway: false
natGatewayState: 'Disabled'
publicNetworkAccess: 'Disabled'
}
}
@description('The name of the API management service (APIM).')
output name string = apim.name
@description('The identifier of the service principal of the system-assigned managed identity of APIM.')
output principalId string = apim.identity.principalId
Usage.
resource KeyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
#disable-next-line use-stable-resource-identifiers
name: 'some-existing-key-vault-deployment-name'
}
module APIM './minimal-apim.bicep' = {
name: 'some-apim-deployment-name'
params: {
name: 'some-apim-name'
location: 'some-location'
customUrl: 'https://api-dev.github.com'
customUrlCertificate: KeyVault.getSecret('api-dev.github.com-ssl-certificate')
customUrlCertificatePassword: KeyVault.getSecret('api-dev.github.com-ssl-certificate-password')
developerPortalUrl: 'https://apiportal-dev.github.com'
developerPortalUrlCertificate: KeyVault.getSecret('apiportal-dev.github.com-ssl-certificate')
developerPortalUrlCertificatePassword: KeyVault.getSecret('apiportal-dev.github.com-ssl-certificate')
}
}
output apim object = APIM.outputs
But it feels less clear from a consumer perspective in using the module. You need to know that you need to provide the multiple parameters.
Additionaly, I've also found that it is impossible to define multiple custom URLs with a different certificate each in the bicep module, so if you define this in the usage of the original module (supposing that the customUrlInfo is an array):
...
customUrlInfo: [for customUrl in customUrls : {
url: customUrl.url
sslInfo: {
certificate: KeyVault.getSecret(customUrl.sslInfo.certificateName)
certificatePassword: KeyVault.getSecret(customUrl.sslInfo.certificatePasswordName)
}
}]
...
It would complain that the output of the KeyVault.getSecret method can only be used for properties that have the secure attribute on them and because we are just creating JSON objects here (that match the type definition of urlInfo, but are not instances of that type) the secure attribute is not on them.
Do you know if there is a better way for providing multiple custom URLs to APIM with different certificates?
Bicep version Bicep CLI version 0.28.1 (ba1e9f8c1e)
Describe the bug When building the bicep template in which an existing KeyVault is used in order to retrieve secrets that are passed to a module via parameters, an exception with the following message: Unhandled exception. System.NotImplementedException: Cannot emit unexpected expression of type ResourceFunctionCallExpression is thrown.
To Reproduce
@description('Mandatory: The location of the resource.') param location string
@description('Optional: The URL and SSL certificate of the API management service. If not provided, the API management service will not have any custom URL.') param customUrlInfo urlInfo?
@description('Optional: The URL and SSL certificate of the developer portal of the API management service. If not provided, the developer portal will not have any custom URL.') param developerPortalUrlInfo urlInfo?
var proxyConfigurations = customUrlInfo != null ? [{ type: 'Proxy' hostName: customUrlInfo!.url encodedCertificate: customUrlInfo!.sslInfo.certificate certificatePassword: customUrlInfo!.sslInfo.certificatePassword }] : [] var portalConfigurations = developerPortalUrlInfo != null ? [{ type: 'DeveloperPortal' hostName: developerPortalUrlInfo!.url encodedCertificate: developerPortalUrlInfo!.sslInfo.certificate certificatePassword: developerPortalUrlInfo!.sslInfo.certificatePassword }] : [] var hostnameConfigurations = union(proxyConfigurations, portalConfigurations)
resource apim 'Microsoft.ApiManagement/service@2022-08-01' = { name: name location: location sku: { name: 'Developer' capacity: 1 } properties: { publisherEmail: 'issue@github.com' publisherName: 'Github' hostnameConfigurations: hostnameConfigurations virtualNetworkType: 'External' disableGateway: false natGatewayState: 'Disabled' publicNetworkAccess: 'Disabled' } }
@description('The name of the API management service (APIM).') output name string = apim.name @description('The identifier of the service principal of the system-assigned managed identity of APIM.') output principalId string = apim.identity.principalId
@export() type urlInfo = { url: string sslInfo: certificateInfo }
type certificateInfo = { @secure() @description('Base64 encoded certificate') certificate: string @secure() certificatePassword: string }
Additional context Exception with message and complete stacktrace: