Azure / bicep

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

Conditional module scope is not respected #14938

Closed LockeExile closed 1 month ago

LockeExile commented 2 months ago

Bicep version 0.29.47.4906

Describe the bug If a module's scope is defined conditionally with a ternary operator or variable, the output template does not specify any resource group, so the resources in the module always deploy to the parent template's resource group.

To Reproduce The below main.bicep demonstrates the conditional scope

param storageAccountName string = ''
param storageAccountResourceGroup string = ''

// Conditionally set scope to this RG or a different one if provided
module storageModule 'OptionalScope.Module.bicep' = {
  name: '${storageAccountName}_ConditionalModule'
  scope: empty(storageAccountResourceGroup) ? resourceGroup() : resourceGroup(storageAccountResourceGroup)
  params: {
    storageAccountName: storageAccountName
  }
}

where OptionalScope.Module.bicep can be anything, e.g.

param storageAccountName string

resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
  name: toLower(storageAccountName)
  location: resourceGroup().location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_RAGRS'
  }
}

The generated template does not reference the storageAccountResourceGroup parameter at all:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "metadata": {
    "_generator": {
      "name": "bicep",
      "version": "0.29.47.4906",
      "templateHash": "9329123938399331999"
    }
  },
  "parameters": {
    "storageAccountName": {
      "type": "string",
      "defaultValue": ""
    },
    "storageAccountResourceGroup": {
      "type": "string",
      "defaultValue": ""
    }
  },
  "resources": [
    {
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2022-09-01",
      "name": "[format('{0}_ConditionalModule', parameters('storageAccountName'))]",
      "properties": {
        "expressionEvaluationOptions": {
          "scope": "inner"
        },
        "mode": "Incremental",
        "parameters": {
          "storageAccountName": {
            "value": "[parameters('storageAccountName')]"
          }
        },
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "metadata": {
            "_generator": {
              "name": "bicep",
              "version": "0.29.47.4906",
              "templateHash": "2096239661753279501"
            }
          },
          "parameters": {
            "storageAccountName": {
              "type": "string"
            }
          },
          "resources": [
            {
              "type": "Microsoft.Storage/storageAccounts",
              "apiVersion": "2022-09-01",
              "name": "[toLower(parameters('storageAccountName'))]",
              "location": "[resourceGroup().location]",
              "kind": "StorageV2",
              "sku": {
                "name": "Standard_RAGRS"
              }
            }
          ]
        }
      }
    }
  ]
}

Additional context There is a workaround:

param storageAccountName string = ''
param storageAccountResourceGroup string = ''

// If RG is provided: explicitly set scope to the provided RG
module storageModuleOtherRG 'OptionalScope.Module.bicep' = if (!empty(storageAccountResourceGroup)) {
  name: '${storageAccountName}_OtherRGModule'
  scope: resourceGroup(storageAccountResourceGroup)
  params: {
    storageAccountName: storageAccountName
  }
}

// If RG is not provided: leave scope as this RG
module storageModuleSameRG 'OptionalScope.Module.bicep' = if (empty(storageAccountResourceGroup)) {
  name: '${storageAccountName}_SameRGModule'
  params: {
    storageAccountName: storageAccountName
  }
}

The generated template contains "resourceGroup": "[parameters('storageAccountResourceGroup')]", as expected.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "metadata": {
    "_generator": {
      "name": "bicep",
      "version": "0.29.47.4906",
      "templateHash": "13325213945381455493"
    }
  },
  "parameters": {
    "storageAccountName": {
      "type": "string",
      "defaultValue": ""
    },
    "storageAccountResourceGroup": {
      "type": "string",
      "defaultValue": ""
    }
  },
  "resources": [
    {
      "condition": "[not(empty(parameters('storageAccountResourceGroup')))]",
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2022-09-01",
      "name": "[format('{0}_OtherRGModule', parameters('storageAccountName'))]",
      "resourceGroup": "[parameters('storageAccountResourceGroup')]",
      "properties": {
        "expressionEvaluationOptions": {
          "scope": "inner"
        },
        "mode": "Incremental",
        "parameters": {
          "storageAccountName": {
            "value": "[parameters('storageAccountName')]"
          }
        },
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "metadata": {
            "_generator": {
              "name": "bicep",
              "version": "0.29.47.4906",
              "templateHash": "2096239661753279501"
            }
          },
          "parameters": {
            "storageAccountName": {
              "type": "string"
            }
          },
          "resources": [
            {
              "type": "Microsoft.Storage/storageAccounts",
              "apiVersion": "2022-09-01",
              "name": "[toLower(parameters('storageAccountName'))]",
              "location": "[resourceGroup().location]",
              "kind": "StorageV2",
              "sku": {
                "name": "Standard_RAGRS"
              }
            }
          ]
        }
      }
    },
    {
      "condition": "[empty(parameters('storageAccountResourceGroup'))]",
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2022-09-01",
      "name": "[format('{0}_SameRGModule', parameters('storageAccountName'))]",
      "properties": {
        "expressionEvaluationOptions": {
          "scope": "inner"
        },
        "mode": "Incremental",
        "parameters": {
          "storageAccountName": {
            "value": "[parameters('storageAccountName')]"
          }
        },
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "metadata": {
            "_generator": {
              "name": "bicep",
              "version": "0.29.47.4906",
              "templateHash": "2096239661753279501"
            }
          },
          "parameters": {
            "storageAccountName": {
              "type": "string"
            }
          },
          "resources": [
            {
              "type": "Microsoft.Storage/storageAccounts",
              "apiVersion": "2022-09-01",
              "name": "[toLower(parameters('storageAccountName'))]",
              "location": "[resourceGroup().location]",
              "kind": "StorageV2",
              "sku": {
                "name": "Standard_RAGRS"
              }
            }
          ]
        }
      }
    }
  ]
}
GABRIELNGBTUC commented 2 months ago

+1.

This issue is quite annoying to find at first. Though the workaround can be simplified like this:

var scope = conditionToUseCustomValue ? trueValue : resourceGroup().name 

module ... = {
scope: resourceGroup(scope)
}

That way you can keep your module DRY.

anthony-c-martin commented 1 month ago

Closing as it's a duplicate of #1876