Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unhandled exception. System.NotImplementedException: Cannot emit unexpected expression of type ResourceFunctionCallExpression #14357

Open
Meertman opened this issue Jun 17, 2024 · 4 comments
Labels
bug Something isn't working Quality Sprint: Yes
Milestone

Comments

@Meertman
Copy link

Meertman commented Jun 17, 2024

Bicep version
Bicep CLI version 0.28.1 (ba1e9f8)

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

  1. Define the module, for example the module in zipped attachment (for a deployment of APIM).
@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: 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: '[email protected]'
    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
}
  1. Use that module, for example the bicep template in zipped attachment.
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'
    customUrlInfo: {
      url: 'https://api-dev.github.com'
      sslInfo: {
        certificate: KeyVault.getSecret('api-dev.github.com-ssl-certificate')
        certificatePassword: KeyVault.getSecret('api-dev.github.com-ssl-certificate-password')
      }
    }
    developerPortalUrlInfo: {
      url: 'https://apiportal-dev.github.com'
      sslInfo: {
        certificate: KeyVault.getSecret('apiportal-dev.github.com-ssl-certificate')
        certificatePassword: KeyVault.getSecret('apiportal-dev.github.com-ssl-certificate')
      }
    }
  }
}

output apim object = APIM.outputs
  1. Execute the command az bicep build -f .bicep

Additional context
Exception with message and complete stacktrace:

Unhandled exception. System.NotImplementedException: Cannot emit unexpected expression of type ResourceFunctionCallExpression
   at Bicep.Core.Emit.ExpressionConverter.ConvertExpression(Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.EmitLanguageExpression(Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.EmitExpression(Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.<>c__DisplayClass22_0.<EmitProperty>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WritePropertyWithPosition(IPositionable keyPosition, String name, Action valueFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitPropertyInternal(LanguageExpression expressionKey, Action valueFunc, IPositionable location, Boolean skipCopyCheck)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(Expression name, Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(String name, Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.EmitObjectProperties(ObjectExpression object)
   at Bicep.Core.Emit.ExpressionEmitter.EmitExpression(Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.<>c__DisplayClass22_0.<EmitProperty>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WritePropertyWithPosition(IPositionable keyPosition, String name, Action valueFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitPropertyInternal(LanguageExpression expressionKey, Action valueFunc, IPositionable location, Boolean skipCopyCheck)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(Expression name, Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(String name, Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.EmitObjectProperties(ObjectExpression object)
   at Bicep.Core.Emit.ExpressionEmitter.EmitExpression(Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.<>c__DisplayClass22_0.<EmitProperty>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WritePropertyWithPosition(IPositionable keyPosition, String name, Action valueFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitPropertyInternal(LanguageExpression expressionKey, Action valueFunc, IPositionable location, Boolean skipCopyCheck)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(Expression name, Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(String name, Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.EmitObjectProperties(ObjectExpression object)
   at Bicep.Core.Emit.ExpressionEmitter.EmitExpression(Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.<>c__DisplayClass22_0.<EmitProperty>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WritePropertyWithPosition(IPositionable keyPosition, String name, Action valueFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitPropertyInternal(LanguageExpression expressionKey, Action valueFunc, IPositionable location, Boolean skipCopyCheck)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(Expression name, Expression expression)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(String name, Expression expression)
   at Bicep.Core.Emit.TemplateWriter.<>c__DisplayClass67_0.<EmitModuleParameters>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WriteObjectWithPosition(IPositionable sourcePosition, Action propertiesFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitObject(Action writePropertiesFunc, IPositionable position)
   at Bicep.Core.Emit.ExpressionEmitter.<>c__DisplayClass32_0.<EmitObjectProperty>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WritePropertyWithPosition(IPositionable keyPosition, String name, Action valueFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(String propertyName, Action writeValueFunc, IPositionable position)
   at Bicep.Core.Emit.ExpressionEmitter.EmitObjectProperty(String propertyName, Action writePropertiesFunc, IPositionable position)
   at Bicep.Core.Emit.TemplateWriter.EmitModuleParameters(ExpressionEmitter emitter, DeclaredModuleExpression module)
   at Bicep.Core.Emit.TemplateWriter.<>c__DisplayClass68_0.<EmitModule>b__2()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WriteObjectWithPosition(IPositionable sourcePosition, Action propertiesFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitObject(Action writePropertiesFunc, IPositionable position)
   at Bicep.Core.Emit.ExpressionEmitter.<>c__DisplayClass32_0.<EmitObjectProperty>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WritePropertyWithPosition(IPositionable keyPosition, String name, Action valueFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(String propertyName, Action writeValueFunc, IPositionable position)
   at Bicep.Core.Emit.ExpressionEmitter.EmitObjectProperty(String propertyName, Action writePropertiesFunc, IPositionable position)
   at Bicep.Core.Emit.TemplateWriter.<>c__DisplayClass68_0.<EmitModule>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WriteObjectWithPosition(IPositionable sourcePosition, Action propertiesFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitObject(Action writePropertiesFunc, IPositionable position)
   at Bicep.Core.Emit.TemplateWriter.EmitModule(PositionTrackingJsonTextWriter jsonWriter, DeclaredModuleExpression module, ExpressionEmitter emitter)
   at Bicep.Core.Emit.TemplateWriter.<>c__DisplayClass64_0.<EmitResources>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WriteArrayWithPosition(IPositionable sourcePosition, Action itemsFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitArray(Action writeItemsFunc, IPositionable position)
   at Bicep.Core.Emit.ExpressionEmitter.<>c__DisplayClass33_0.<EmitArrayProperty>b__0()
   at Bicep.Core.Emit.PositionTrackingJsonTextWriter.WritePropertyWithPosition(IPositionable keyPosition, String name, Action valueFunc)
   at Bicep.Core.Emit.ExpressionEmitter.EmitProperty(String propertyName, Action writeValueFunc, IPositionable position)
   at Bicep.Core.Emit.ExpressionEmitter.EmitArrayProperty(String propertyName, Action writeItemsFunc, IPositionable position)
   at Bicep.Core.Emit.TemplateWriter.EmitResources(PositionTrackingJsonTextWriter jsonWriter, ExpressionEmitter emitter, ImmutableArray`1 resources, ImmutableArray`1 modules)
   at Bicep.Core.Emit.TemplateWriter.GenerateTemplateWithoutHash(PositionTrackingJsonTextWriter jsonWriter)
   at Bicep.Core.Emit.TemplateWriter.Write(SourceAwareJsonTextWriter writer)
   at Bicep.Core.Emit.TemplateEmitter.<>c__DisplayClass8_0.<Emit>b__0()
   at Bicep.Core.Emit.TemplateEmitter.EmitOrFail(Func`1 write)
   at Bicep.Core.Emit.TemplateEmitter.Emit(TextWriter textWriter)
   at Bicep.Core.Emit.CompilationEmitter.Template(SemanticModel model)
   at Bicep.Core.Emit.CompilationEmitter.Template()
   at Bicep.Cli.Services.OutputWriter.TemplateToFile(Compilation compilation, Uri outputUri)
   at Bicep.Cli.Commands.BuildCommand.RunAsync(BuildArguments args)
   at Bicep.Cli.Program.RunAsync(String[] args, CancellationToken cancellationToken)
   at Bicep.Cli.Program.<>c__DisplayClass3_0.<<Main>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Bicep.Cli.Program.RunWithCancellationAsync(Func`2 runFunc)
   at Bicep.Cli.Program.Main(String[] args)
   at Bicep.Cli.Program.<Main>(String[] args)
@jeskew
Copy link
Contributor

jeskew commented Jun 18, 2024

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.

@anthony-c-martin anthony-c-martin added bug Something isn't working and removed Needs: Triage 🔍 labels Jun 19, 2024
@anthony-c-martin anthony-c-martin added this to the v1.0 milestone Jun 19, 2024
@Meertman
Copy link
Author

@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: '[email protected]'
    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.

@Meertman
Copy link
Author

Meertman commented Jun 21, 2024

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?

@corwestermaniddink
Copy link

corwestermaniddink commented Aug 9, 2024

But please give this also back in the validation in Bicep language service that a "KeyVault.getSecret" isn't allowed in a params type object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Quality Sprint: Yes
Projects
Status: Todo
Development

No branches or pull requests

5 participants