Azure / bicep

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

Bicep build generates Object instead of Array for resources when using user-defined functions #15268

Open galiacheng opened 20 hours ago

galiacheng commented 20 hours ago

Bicep version

0.30.23

Describe the bug

I am using Bicep to develop and maintain the source code for the Azure WebLogic on AKS marketplace offer. The marketplace requires converting Bicep scripts to ARM templates, following this ARM structure, where the resources section must be an array.

However, when building with bicep build or az bicep build, I receive an object instead of an array for the resources section, leading to a Partner Center error: Package acceptance validation error: JsonSchemaValidationError The json file 'mainTemplate.json' is invalid for the provided schema. Line Number: '0', Error: 'Invalid type. Expected Array but got Object.'

I traced this issue to user-defined functions in the Bicep script. Without them, the resources section builds correctly as an array. You can view the built ARM template and error in this GitHub action.

To Reproduce

  1. Bicep script without user defined function. Run az bicep build -f main.bicep to build the following content.

    param location string
    
    resource stg 'Microsoft.Storage/storageAccounts@2021-04-01' = {
    name: 'hellotest20241008001'
    location: location
    sku: {
      name: 'Standard_LRS'
    }
    kind: 'StorageV2'
    properties: {
      supportsHttpsTrafficOnly: true
    }
    }
    
    output storageEndpoint object = stg.properties.primaryEndpoints
    

    Got ARM template as following, type of resources is Array.

    {
      "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
      "contentVersion": "1.0.0.0",
      "metadata": {
        "_generator": {
          "name": "bicep",
          "version": "0.30.23.60470",
          "templateHash": "5107046056470376467"
        }
      },
      "parameters": {
        "location": {
          "type": "string"
        }
      },
      "resources": [
        {
          "type": "Microsoft.Storage/storageAccounts",
          "apiVersion": "2021-04-01",
          "name": "hellotest20241008001",
          "location": "[parameters('location')]",
          "sku": {
            "name": "Standard_LRS"
          },
          "kind": "StorageV2",
          "properties": {
            "supportsHttpsTrafficOnly": true
          }
        }
      ],
      "outputs": {
        "storageEndpoint": {
          "type": "object",
          "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', 'hellotest20241008001'), '2021-04-01').primaryEndpoints]"
        }
      }
    }
  2. Bicep script with user defined function. Run az bicep build -f main.bicep to build the following content.

    param location string
    
    var uniqueStorageName = abc(resourceGroup().id)
    
    func abc(resourceId string) string => string(resourceId)
    
    resource stg 'Microsoft.Storage/storageAccounts@2021-04-01' = {
      name: uniqueStorageName
      location: location
      sku: {
        name: 'Standard_LRS'
      }
      kind: 'StorageV2'
      properties: {
        supportsHttpsTrafficOnly: true
      }
    }
    
    output storageEndpoint object = stg.properties.primaryEndpoints

    Got ARM template as following, type of resources is Object.

    {
      "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
      "languageVersion": "2.0",
      "contentVersion": "1.0.0.0",
      "metadata": {
        "_generator": {
          "name": "bicep",
          "version": "0.30.23.60470",
          "templateHash": "8515690428638717928"
        }
      },
      "functions": [
        {
          "namespace": "__bicep",
          "members": {
            "abc": {
              "parameters": [
                {
                  "type": "string",
                  "name": "resourceId"
                }
              ],
              "output": {
                "type": "string",
                "value": "[string(parameters('resourceId'))]"
              }
            }
          }
        }
      ],
      "parameters": {
        "location": {
          "type": "string"
        }
      },
      "variables": {
        "uniqueStorageName": "[__bicep.abc(resourceGroup().id)]"
      },
      "resources": {
        "stg": {
          "type": "Microsoft.Storage/storageAccounts",
          "apiVersion": "2021-04-01",
          "name": "[variables('uniqueStorageName')]",
          "location": "[parameters('location')]",
          "sku": {
            "name": "Standard_LRS"
          },
          "kind": "StorageV2",
          "properties": {
            "supportsHttpsTrafficOnly": true
          }
        }
      },
      "outputs": {
        "storageEndpoint": {
          "type": "object",
          "value": "[reference('stg').primaryEndpoints]"
        }
      }
    }

Additional context

The expected outcome is that, in both cases, the resources section should be an array, consistent with the ARM template definition.

anthony-c-martin commented 12 hours ago

The reason for this is that usage of user-defined functions depends on language version 2.0.

The change from array -> object is explained here.