Search code examples
jsonazureazure-resource-managerazure-deployment

Provisioning resource group and azure function at deployment level


I've written up the script below to do the following:

  • Provision a resource group
  • In a separate deployment:
    • Provision a storage account
    • Provision a server farm
    • Provision a function app

The problem lies in the setting of the app settings in the function app, when I'm setting up the AzureWebJobsStorage. The resourceId function fails to resolve the storage account. When looking at the documention for the resourceId function, it states:

When used with a subscription-level deployment, the resourceId() function can only retrieve the ID of resources deployed at that level. [docs]

But now I don't know how to resolve this!

Template:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "resourceGroupName": {
      "type": "string"
    },
    "functionName": {
      "type": "string"
    },
    "storageAccName": {
      "type": "string"
    },
    "namingPrefix": {
      "type": "string"
    }
  },
  "variables": {
    "resourceGroupLocation": "North Europe",
    "planName": "[replace(concat(variables('resourceGroupLocation'), 'Plan'),' ','')]",
    "resourceGroupName": "[concat(parameters('namingPrefix'), '-', parameters('resourceGroupName'))]",
    "functionName": "[concat(parameters('namingPrefix'), '-', parameters('functionName'))]",
    "storageAccName": "[toLower(concat(parameters('namingPrefix'), parameters('storageAccName')))]"
  },
  "resources": [
    {
      "type": "Microsoft.Resources/resourceGroups",
      "apiVersion": "2018-05-01",
      "location": "[variables('resourceGroupLocation')]",
      "name": "[variables('resourceGroupName')]",
      "properties": {}
    },
    {
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2019-05-01",
      "name": "NestedTemplate",
      "resourceGroup": "[variables('resourceGroupName')]",
      "dependsOn": [
        "[variables('resourceGroupName')]"
      ],
      "properties": {
        "mode": "Incremental",
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "resources": [
            {
              "type": "Microsoft.Storage/storageAccounts",
              "apiVersion": "2019-04-01",
              "name": "[variables('storageAccName')]",
              "location": "[variables('resourceGroupLocation')]",
              "sku": {
                "name": "Standard_LRS",
                "tier": "Standard"
              },
              "kind": "Storage",
              "properties": {
                "networkAcls": {
                  "bypass": "AzureServices",
                  "virtualNetworkRules": [],
                  "ipRules": [],
                  "defaultAction": "Allow"
                },
                "supportsHttpsTrafficOnly": true,
                "encryption": {
                  "services": {
                    "file": {
                      "enabled": true
                    },
                    "blob": {
                      "enabled": true
                    }
                  },
                  "keySource": "Microsoft.Storage"
                }
              }
            },
            {
              "type": "Microsoft.Web/serverfarms",
              "apiVersion": "2016-09-01",
              "name": "[variables('planName')]",
              "location": "[variables('resourceGroupLocation')]",
              "sku": {
                "name": "Y1",
                "tier": "Dynamic",
                "size": "Y1",
                "family": "Y",
                "capacity": 0
              },
              "kind": "functionapp",
              "properties": {
                "name": "[variables('planName')]",
                "computeMode": "Dynamic",
                "perSiteScaling": false,
                "reserved": false,
                "targetWorkerCount": 0,
                "targetWorkerSizeId": 0
              }
            },
            {
              "type": "Microsoft.Web/sites",
              "apiVersion": "2016-08-01",
              "name": "[variables('functionName')]",
              "location": "[variables('resourceGroupLocation')]",
              "dependsOn": [
                "[variables('planName')]",
                "[variables('appInsightsName')]",
                "[variables('storageAccName')]"
              ],
              "kind": "functionapp",
              "identity": {
                "type": "SystemAssigned"
              },
              "properties": {
                "enabled": true,
                "hostNameSslStates": [
                  {
                    "name": "[concat(variables('functionName'), '.azurewebsites.net')]",
                    "sslState": "Disabled",
                    "hostType": "Standard"
                  },
                  {
                    "name": "[concat(variables('functionName'), '.scm.azurewebsites.net')]",
                    "sslState": "Disabled",
                    "hostType": "Repository"
                  }
                ],
                "siteConfig": {
                  "appSettings": [
                    {
                      "name": "AzureWebJobsStorage",
                      "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).key1)]"
                    },
                    {
                      "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                      "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).key1)]"
                    },
                    {
                      "name": "WEBSITE_CONTENTSHARE",
                      "value": "[variables('functionName')]"
                    },
                    {
                      "name": "FUNCTIONS_WORKER_RUNTIME",
                      "value": "node"
                    },
                    {
                      "name": "WEBSITE_NODE_DEFAULT_VERSION",
                      "value": "10.14.1"
                    },
                    {
                      "name": "FUNCTIONS_EXTENSION_VERSION",
                      "value": "~2"
                    }
                  ]
                },
                "serverFarmId": "[variables('planName')]",
                "reserved": false
              }
            }
          ]
        }
      }
    }
  ]
}

Executed using following line:

New-AzDeployment -Location "North Europe" -TemplateFile $TemplateFilePath -TemplateParameterFile $ParametersFilePath -namingPrefix $namingPrefix;

Output

 Resource Microsoft.Storage/storageAccounts 'testStorageAccount' failed with message '{
  "error": {
    "code": "ResourceNotFound",
    "message": "The Resource 'Microsoft.Storage/storageAccounts/testStorageAccount' under resource group '<null>'
was not found."
  }
}'

Solution

  • For anyone who runs into this issue at a later date (probably myself) I was forced to create the resource group in my powershell script, and then use new-AzResourceGroupDeployment instead.

    To accommodate this, the changes to the deployment template were minimal (I remove the resource group and brought the nested template one level up). However, I was also accessing the storage account key incorrectly. This has been updated in the code below.

    $resourceGroup = Get-AzResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
        if(!$resourceGroup)
        {
            Write-Host "Creating resource group '$resourceGroupName' in location '$resourceGroupLocation'";
            New-AzResourceGroup -Name $resourceGroupName -Location $resourceGroupLocation
        }
        else{
            Write-Host "Using existing resource group '$resourceGroupName'";
        }
    
        # Start the deployment
        Write-Host "Starting deployment...";
        if(Test-Path $parametersFilePath) {
            New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $TemplateFilePath -TemplateParameterFile $parametersFilePath;
        }
    
    {
      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "resourceGroupName": {
          "type": "string"
        },
        "functionName": {
          "type": "string"
        },
        "storageAccName": {
          "type": "string"
        },
        "namingPrefix": {
          "type": "string"
        }
      },
      "variables": {
        "resourceGroupLocation": "North Europe",
        "planName": "[replace(concat(variables('resourceGroupLocation'), 'Plan'),' ','')]",
        "resourceGroupName": "[concat(parameters('namingPrefix'), '-', parameters('resourceGroupName'))]",
        "functionName": "[concat(parameters('namingPrefix'), '-', parameters('functionName'))]",
        "storageAccName": "[toLower(concat(parameters('namingPrefix'), parameters('storageAccName')))]"
      },
      "resources": [
        {
          "type": "Microsoft.Storage/storageAccounts",
          "apiVersion": "2019-04-01",
          "name": "[variables('storageAccName')]",
          "location": "[variables('resourceGroupLocation')]",
          "sku": {
            "name": "Standard_LRS",
            "tier": "Standard"
          },
          "kind": "Storage",
          "properties": {
            "networkAcls": {
              "bypass": "AzureServices",
              "virtualNetworkRules": [],
              "ipRules": [],
              "defaultAction": "Allow"
            },
            "supportsHttpsTrafficOnly": true,
            "encryption": {
              "services": {
                "file": {
                  "enabled": true
                },
                "blob": {
                  "enabled": true
                }
              },
              "keySource": "Microsoft.Storage"
            }
          }
        },
        {
          "type": "Microsoft.Web/serverfarms",
          "apiVersion": "2016-09-01",
          "name": "[variables('planName')]",
          "location": "[variables('resourceGroupLocation')]",
          "sku": {
            "name": "Y1",
            "tier": "Dynamic",
            "size": "Y1",
            "family": "Y",
            "capacity": 0
          },
          "kind": "functionapp",
          "properties": {
            "name": "[variables('planName')]",
            "computeMode": "Dynamic",
            "perSiteScaling": false,
            "reserved": false,
            "targetWorkerCount": 0,
            "targetWorkerSizeId": 0
          }
        },
        {
          "type": "Microsoft.Web/sites",
          "apiVersion": "2016-08-01",
          "name": "[variables('functionName')]",
          "location": "[variables('resourceGroupLocation')]",
          "dependsOn": [
            "[variables('planName')]",
            "[variables('appInsightsName')]",
            "[variables('storageAccName')]"
          ],
          "kind": "functionapp",
          "identity": {
            "type": "SystemAssigned"
          },
          "properties": {
            "enabled": true,
            "hostNameSslStates": [
              {
                "name": "[concat(variables('functionName'), '.azurewebsites.net')]",
                "sslState": "Disabled",
                "hostType": "Standard"
              },
              {
                "name": "[concat(variables('functionName'), '.scm.azurewebsites.net')]",
                "sslState": "Disabled",
                "hostType": "Repository"
              }
            ],
            "siteConfig": {
              "appSettings": [
                {
                  "name": "AzureWebJobsStorage",
                  "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]"
                },
                {
                  "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                  "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]"
                },
                {
                  "name": "WEBSITE_CONTENTSHARE",
                  "value": "[variables('functionName')]"
                },
                {
                  "name": "FUNCTIONS_WORKER_RUNTIME",
                  "value": "node"
                },
                {
                  "name": "WEBSITE_NODE_DEFAULT_VERSION",
                  "value": "10.14.1"
                },
                {
                  "name": "FUNCTIONS_EXTENSION_VERSION",
                  "value": "~2"
                }
              ]
            },
            "serverFarmId": "[variables('planName')]",
            "reserved": false
          }
        }
      ]
    }