Azure / bicep-types-az

Bicep type definitions for ARM resources
MIT License
86 stars 27 forks source link

[Microsoft.Network/applicationGateways]: types that are not available to us should not display warnings. #1896

Closed robkendrick closed 1 year ago

robkendrick commented 1 year ago

Resource Type

Microsoft.Network/applicationGateways

Api Version

2021-08-01

Issue Type

Inaccurate property type(s)

Other Notes

Setting up an appGateway (bicep for this is in the "bicep repro" section) for scalablity with multiple apps--each of which have multiple hostnames--requires module deployment; this isn't an issue and was accomplished by setting up a bicep template to iterate over the necessary properties:

// AGPropertyIterator - for generating the iterative properties for each app and/or hostname.

param app object
param appGatewayName string
param appGatewayId string

var suffix = take(toLower(uniqueString(resourceGroup().id)), 10)

var backendAddresses = [for (instance, i) in app.instances: {
  fqdn: i < 10 ? '${app.appName}0${i}${app.type}${suffix}.azurewebsites.net' : '${app.appName}${i}${app.type}${suffix}.azurewebsites.net'
}]

var backendHttpSettingsCollection = [for hostName in app.hostNames: {
  name: 'backendHttpSettings_${hostName}'
  properties: {
    affinityCookieName: 'cookie_${hostName}'
    cookieBasedAffinity: 'Enabled'
    hostName: '${hostName}.${app.domain}'
    pickHostNameFromBackendAddress: false
    port: 443        
    protocol: 'Https'
    requestTimeout: 30
    probe: { id: resourceId('Microsoft.Network/applicationGateways/probes', appGatewayName, 'healthProbe_${app.appName}') }
  }
}]

var httpListeners = [for hostName in app.hostNames: {
  name: 'httpListener_${hostName}'
  properties: {
    frontendIPConfiguration: { Id: '${appGatewayId}/frontendIPConfigurations/appGatewayFrontendIP' }
    frontendPort: { Id: '${appGatewayId}/frontendPorts/frontEndPort80' }
    protocol: 'Http'
    hostName: hostName
    requireServerNameIndication: false
  }
}]

var httpsListeners = [for hostName in app.hostNames: {
  name: 'httpsListener_${hostName}'
  properties: {
    frontendIPConfiguration: { Id: '${appGatewayId}/frontendIPConfigurations/appGatewayFrontendIP' }
    frontendPort: { Id: '${appGatewayId}/frontendPorts/frontEndPort443' }
    protocol: 'Https'
    sslCertificate: { Id: '${appGatewayId}/sslCertificates/${app.ssl.certName}' }
    hostName: hostName
    requireServerNameIndication: true
  }
}]

var httpRequestRoutingRules = [for (hostName,i) in app.hostNames: {
  name: 'httpRouting_${hostName}'
  properties: {
    priority: (i + 99)
    RuleType: 'Basic'
    httpListener: { id: '${appGatewayId}/httpListeners/httpListener_${hostName}' }
    redirectConfiguration: { id: '${appGatewayId}/redirectConfigurations/httpsRedirect_${app.appName}' }
  }
}]
var httpsRequestRoutingRules = [for (hostName, i) in app.hostNames: {
  name: 'httpsRouting_${hostName}'
  properties: {
    priority: (i + 199)
    RuleType: 'Basic'
    httpListener: { id: '${appGatewayId}/httpListeners/httpsListener_${hostName}' }
    backendAddressPool: { id: '${appGatewayId}/backendAddressPools/${app.addressPool.name}' }
    backendHttpSettings: { id: '${appGatewayId}/backendHttpSettingsCollection/backendHttpSettings_${app.appName}' }
  }
}]
var probes = [for (hostName, i) in app.hostNames: {
      name: 'healthProbe_${hostName}'
      properties: {
        protocol: 'Https'
        host: hostName
        path: app.probe
        interval: 30
        timeout: 30
        unhealthyThreshold: 3
        pickHostNameFromBackendHttpSettings: false
        minServers: 0
        match: { statusCodes: [ '200' ] }
      }
    }]
var redirectConfigurations = [for (hostName, i) in app.hostNames: {
  name: 'httpsRedirect_${hostName}'
  properties: {
    redirectType: 'Permanent'
    targetListener: { id: resourceId('Microsoft.Network/applicationGateways/httpListeners', appGatewayName, 'httpsListener_${hostName}') }
    includePath: true
    includeQueryString: true
  }
}]

output backendAddresses array = backendAddresses
output backendSettings array = backendHttpSettingsCollection
output listeners array = concat(httpListeners, httpsListeners)
output probes array = probes
output redirects array = redirectConfigurations
output routingRules array = concat(httpRequestRoutingRules, httpsRequestRoutingRules)

However, when deploying, I get the following warnings:

C:\repos\VisualSolutions\visual-armt-web\appgateway.bicep(103,70) : Warning BCP036: The property "backendHttpSettingsCollection" expected a value of type "ApplicationGatewayBackendHttpSettings" but the provided value is of type "array". If this is an inaccuracy in the documentation, please report it to the Bicep Team. [https://aka.ms/bicep-type-issues]
C:\repos\VisualSolutions\visual-armt-web\appgateway.bicep(104,54) : Warning BCP036: The property "httpListeners" expected a value of type "ApplicationGatewayHttpListener" but the provided value is of type "array". If this is an inaccuracy in the documentation, please report it to the Bicep Team. [https://aka.ms/bicep-type-issues]
C:\repos\VisualSolutions\visual-armt-web\appgateway.bicep(105,47) : Warning BCP036: The property "probes" expected a value of type "ApplicationGatewayProbe" but the provided value is of type "array". If this is an inaccuracy in the documentation, please report it to the Bicep Team. [https://aka.ms/bicep-type-issues]
C:\repos\VisualSolutions\visual-armt-web\appgateway.bicep(106,63) : Warning BCP036: The property "redirectConfigurations" expected a value of type "ApplicationGatewayRedirectConfiguration" but the provided value is of type "array". If this is an inaccuracy in the documentation, please report it to the Bicep Team. [https://aka.ms/bicep-type-issues]
C:\repos\VisualSolutions\visual-armt-web\appgateway.bicep(107,60) : Warning BCP036: The property "requestRoutingRules" expected a value of type "ApplicationGatewayRequestRoutingRule" but the provided value is of type "array". If this is an inaccuracy in the documentation, please report it to the Bicep Team. [https://aka.ms/bicep-type-issues]

I am unable to set these as output types so they are properly parsed: image

I was unable to find any other issues pertaining to this: image

Bicep Repro

// Application Gateway Template: For deploying an AG that protects multiple apps with multiple hostnames.

// Parameters.
param appGateway object = {
  name: '(appGateway.name)00ag(suffix)'
  location: resourceGroup().location
  pipName: '(namePrefixes.appGateway)00pip(suffix)'
  identityName: '(namePrefixes.appGateway)00mid(suffix)'
  frontEndPorts: [ 80, 443 ]
  sku: { name: 'WAF_v2', tier: 'WAF_v2' }
  waf: { enabled: true, firewallMode: 'Detection', ruleSetType: 'OWASP', ruleSetVersion: '3.0' }
}
param agAppProperties array = [{
  appName: '(app.appName)'
  appType: 'web'
  domain: '(app.domain)'
  hostNames: ['www', 'otherHostName']
  instances: 2
  probe: '/'
  ssl: {
    certName: '(app.cert)'
    certId: '(Secret URI for certificate in keyVault)'
  }
}]
param subnetId string = ''

// Variables.
var appGatewayId = resourceId('Microsoft.Network/applicationGateways', appGateway.name)

// FrontEnd resources are named after the hostName, while backend resources are named after the app.
module hostSpecificProperties 'agPropertyIterator.bicep' = [for (app, i) in agAppProperties: {
  name: 'hostSpecificProperties_${app.appName}'
  params: {
    app: app
    appGatewayName: appGateway.name
    appGatewayId: appGatewayId
  }
}]

resource PublicIPAddress 'Microsoft.Network/publicIPAddresses@2020-08-01' = {
  name: appGateway.pipName
  location: appGateway.location
  sku: {
    name: 'Standard'
    tier: 'Regional'
  }
  properties: {
    publicIPAllocationMethod: 'Static'
    dnsSettings: {
      domainNameLabel: toLower(appGateway.pipName)
    }
  }
}

resource ManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
  name: appGateway.identityName
  location: appGateway.location
}

resource ApplicationGateway 'Microsoft.Network/applicationGateways@2021-08-01' = {
  name: appGateway.name
  location: appGateway.location
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: { '${ManagedIdentity.id}': {} }
  }
  properties: {
    sku: appGateway.sku
    autoscaleConfiguration: {
      maxCapacity: 10
      minCapacity: 2
    }
    sslPolicy: {
      policyType: 'Predefined'
      policyName: 'AppGwSslPolicy20220101S'
    }
    sslCertificates: [ for app in agAppProperties: {
      name: app.ssl.certName
      properties: { keyVaultSecretId: app.ssl.certId }
    }]
    gatewayIPConfigurations: !empty(subnetId) ? [] : [
      {
        name: 'appGatewayIpConfig'
        properties: { subnet: { id: subnetId } }
      }
    ]
    frontendIPConfigurations: [
      {
        name: 'appGatewayFrontendIP'
        properties: { publicIPAddress: { id: PublicIPAddress.id } }
      }
    ]
    frontendPorts: [for port in appGateway.frontEndPorts: {
      name: 'frontEndPort${port}'
      properties: { port: port }
    }]
    backendAddressPools: [for (app, i) in agAppProperties: {
      name: '${app.appName}BackendAdresses'
      properties: { backendAddresses: hostSpecificProperties[i].outputs.backendAddresses }
    }]
    backendHttpSettingsCollection: [for (app, i) in agAppProperties: hostSpecificProperties[i].outputs.backendSettings]
    httpListeners: [for (app, i) in agAppProperties: hostSpecificProperties[i].outputs.listeners]
    probes: [for (app, i) in agAppProperties: hostSpecificProperties[i].outputs.probes]
    redirectConfigurations: [for (app, i) in agAppProperties: hostSpecificProperties[i].outputs.redirects]
    requestRoutingRules: [for (app, i) in agAppProperties: hostSpecificProperties[i].outputs.routingRules]
    webApplicationFirewallConfiguration: appGateway.waf
  }
}

output appGatewayPublicIP string = PublicIPAddress.properties.ipAddress
output appGatewayFqdn string = PublicIPAddress.properties.dnsSettings.fqdn

Confirm

robkendrick commented 1 year ago

Update: I actually ended up resolving this by adding a Property Aggregator module. I hate that I had to do this, but for now, it's a cleaner solution than hardcoding/manual iteration on fixed values which would defeat the purpose of a template.

For those interested, here is the updated agPropertyIterator.bicep file:

// AGPropertyIterator - for generating the iterative properties for each app and/or hostname.

param ruleNumberSeed int = 0 // i*(i+1)*length(app.hostNames) -- This prevents overlapping rule numbers when iterating over iterations.
param app object = {
  appName: '(app.appName)'
  type: 'web'
  domain: '(app.domain)'
  hostNames: ['www', 'otherHostName']
  instances: 2
  probe: '/'
  ssl: {
    certName: '(app.cert)'
    certId: '(Secret URI for certificate in keyVault)'
  }
}
param appGatewayName string
param appGatewayId string

var suffix = take(toLower(uniqueString(resourceGroup().id)), 10)
var backendAddresses = [for i in range(1, app.instances): {
  fqdn: i < 10 ? '${app.appName}0${i}${app.type}${suffix}.azurewebsites.net' : '${app.appName}${i}${app.type}${suffix}.azurewebsites.net'
}]
var backendAddressPools = {
  name: '${app.appName}BackendAddresses'
  properties: {
    backendAddresses: backendAddresses
  }
}
var backendHttpSettingsCollection = [for hostName in app.hostNames: {
  name: 'backendHttpSettings_${hostName}'
  properties: {
    affinityCookieName: 'cookie_${hostName}'
    cookieBasedAffinity: 'Enabled'
    hostName: '${hostName}.${app.domain}'
    pickHostNameFromBackendAddress: false
    port: 443        
    protocol: 'Https'
    requestTimeout: 30
    probe: { id: resourceId('Microsoft.Network/applicationGateways/probes', appGatewayName, 'healthProbe_${hostName}') }
  }
}]
var httpListeners = [for hostName in app.hostNames: {
  name: 'httpListener_${hostName}'
  properties: {
    frontendIPConfiguration: { Id: '${appGatewayId}/frontendIPConfigurations/appGatewayFrontendIP' }
    frontendPort: { Id: '${appGatewayId}/frontendPorts/frontEndPort80' }
    protocol: 'Http'
    hostName: '${hostName}.${app.domain}'
    requireServerNameIndication: false
  }
}]
var httpsListeners = [for hostName in app.hostNames: {
  name: 'httpsListener_${hostName}'
  properties: {
    frontendIPConfiguration: { Id: '${appGatewayId}/frontendIPConfigurations/appGatewayFrontendIP' }
    frontendPort: { Id: '${appGatewayId}/frontendPorts/frontEndPort443' }
    protocol: 'Https'
    sslCertificate: { Id: '${appGatewayId}/sslCertificates/${app.ssl.certName}' }
    hostName: '${hostName}.${app.domain}'
    requireServerNameIndication: true
  }
}]
var httpRequestRoutingRules = [for (hostName,i) in app.hostNames: {
  name: 'httpRouting_${hostName}'
  properties: {
    priority: (i + ruleNumberSeed + 99 )
    RuleType: 'Basic'
    httpListener: { id: '${appGatewayId}/httpListeners/httpListener_${hostName}' }
    redirectConfiguration: { id: '${appGatewayId}/redirectConfigurations/httpsRedirect_${hostName}' }
  }
}]
var httpsRequestRoutingRules = [for (hostName, i) in app.hostNames: {
  name: 'httpsRouting_${hostName}'
  properties: {
    priority: (i + ruleNumberSeed + 500)
    RuleType: 'Basic'
    httpListener: { id: '${appGatewayId}/httpListeners/httpsListener_${hostName}' }
    backendAddressPool: { id: '${appGatewayId}/backendAddressPools/${app.appName}BackendAddresses' }
    backendHttpSettings: { id: '${appGatewayId}/backendHttpSettingsCollection/backendHttpSettings_${hostName}' }
  }
}]
var probes = [for (hostName, i) in app.hostNames: {
  name: 'healthProbe_${hostName}'
  properties: {
    protocol: 'Https'
    host: '${hostName}.${app.domain}'
    path: app.probe
    interval: 30
    timeout: 30
    unhealthyThreshold: 3
    pickHostNameFromBackendHttpSettings: false
    minServers: 0
    match: { statusCodes: [ '200' ] }
  }
}]
var redirectConfigurations = [for (hostName, i) in app.hostNames: {
  name: 'httpsRedirect_${hostName}'
  properties: {
    redirectType: 'Permanent'
    targetListener: { id: resourceId('Microsoft.Network/applicationGateways/httpListeners', appGatewayName, 'httpsListener_${hostName}') }
    includePath: true
    includeQueryString: true
  }
}]

output backendAddresses object = backendAddressPools
output backendSettings array = backendHttpSettingsCollection
output listeners array = concat(httpListeners, httpsListeners)
output probes array = probes
output redirects array = redirectConfigurations
output routingRules array = concat(httpRequestRoutingRules, httpsRequestRoutingRules)

...and here is the agPropertyAggregator.bicep file:

param backendSettings array
param listeners array
param probes array
param redirects array
param routingRules array

// We cannot flatten() at the start of a for loop, so we must do so here.
output backendHttpSettingsCollection array = flatten(backendSettings)
output httpListeners array = flatten(listeners)
output probes array = flatten(probes)
output redirectConfigurations array = flatten(redirects)
output requestRoutingRules array = flatten(routingRules)

I'm sure there's a cleaner solution to this problem, but for now, this works.