Search code examples
azureazure-resource-managerazure-biceppulumi

Can I merge existing state of an Azure resource with an Azure Bicep or Pulumi file?


I'm currently working with a solution that uses an Azure Application Gateway deployed with ARM/Bicep. Over time, other applications are deployed that use this AppGw, so rules/backend pools/listeners are created for those applications at their deployment time via Az CLI (outside of the central infra IaC pipeline/process). When it comes to redeploying/updating the central AppGw, I have the classic problem of the ARM/Bicep template overwriting all of these extra additions, as the AppGw is a single resource and as the changes are not in the ARM/Bicep file they are removed.

I have worked around this problem in the past by checking for AppGw existence, outputting the existing rules/pools/etc. and then incorporating them into the ARM/Bicep JSON before it is redeployed. This has worked fine but the AppGw is now getting so large/complex that I am hitting Bash character limits when deploying updates via Azure Devops build pipelines. As such, I am looking for a better way to handle this issue. I have also tried outputting the existing config to file and ingesting via file load in Azure Bicep, but I need to deploy multiple AppGws across the globe with different configs so due to compile time file reference restrictions in Bicep, this doesn't work for me.

I need to ensure that my baseline template file for the AppGw, which sets core things like TLS level or diagnostic settings, is honoured in some way while not overwriting the amendments that happen from separate deployment processes.

My question is whether I can incorporate/merge the state of this existing AppGw with my baseline template, either using Azure Bicep or retooling to something like Pulumi/Terraform if this exposes the functionality. The kind of approach I was thinking of would be:

  • Pipeline CLI task checks if AppGw already exists
  • If NO, then deploy using baseline template with bare bones requirements
  • If YES, then fetch existing backend pools/listeners/etc. (or fetch overall state)
  • Compare to template IaC file
  • Merge state, ensuring core settings from IaC file are applied (i.e. diagnostic settings, TLS level, etc.), while existing backend pools/listeners/etc. are retained

I am aware, but not experienced with, Pulumi's concept of ignoreChanges and transformations. I wasn't sure if that covered the use case here. What I am trying to achieve here may be in conflict with the purpose of these declarative languages, but just thought I'd ask to see if someone else had any thoughts.

Thanks very much in advance!


Solution

  • Using Bicep, you could always retrieve existing configuration if the app gateway already exists. Here is a sample using httpListeners.

    You could define a app-gateway.bicep module like that:

    param appGatewayName string
    param location string = resourceGroup().location
    ...
    param httpListeners array
    
    resource appGateway 'Microsoft.Network/applicationGateways@2020-11-01' = {
      name: appGatewayName
      location: location
      ...
      properties: {
        ...    
        httpListeners: httpListeners
      }
    }
    

    Then from your main.bicep file, use the default or existing config:

    param appGateWayExists bool
    param appGatewayName string
    ...
    
    // Get existing app gateway if existing
    resource existing 'Microsoft.Network/applicationGateways@2020-11-01' existing = if (appGateWayExists) {
      name: appGatewayName
    }
    
    // Deploy app gateway
    module appgateway './app-gateway.bicep' = {
      name: 'app-gateway'
      params: {
        appGatewayName: appGatewayName
        ...
        // Use existing configuration if exists
        httpListeners: appGateWayExists ? existing.properties.httpListeners : [
          {
            // default listener configuration
          }
        ]
      }
    }
    

    Then you could invoke it like that:

    $appGatewayName = "<app-gateway-name>"
    $appGatewayExists = (az resource list --name "$appGatewayName" --query '[].[id]' | ConvertFrom-Json).Length -gt 0
    az deployment group create `
      --resource-group "<resource-group-name>" `
      ...
      --parameters `
      appGateWayExists=$appGatewayExists `
      appGatewayName="$appGatewayName" `
      ...