Related
I am developing an ARM template for Azure Data Factory with managed private endpoints to SQL Server and Azure Datalake. However, when the ARM template completes execution, the managed private endpoints are in "Pending" state. How can I provision the managed private endpoint so that it gets provisioned as "Approved" once ADF is completely provisioned using ARM Template. Following is my template.json file:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environment": {
"type": "string",
"metadata": {
"description": "name of environment for deployment"
}
},
"project": {
"type": "string",
"metadata": {
"description": "name of the project for building the name of resources"
}
},
"location": {
"defaultValue": "eastus",
"type": "string"
},
"adfFactoryName": {
"type": "string"
},
"adfVersion": {
"type": "string"
},
"tags": {
"type": "object",
"metadata": {
"description": "tags to add to resources"
}
},
"adfVNetEnabled": {
"type": "bool"
},
"adfPublicNetworkAccess": {
"type": "bool"
},
"adfAzureDatabricksDomain": {
"type": "string",
"metadata": "Azure Databricks existing cluster Id"
},
"adfAzureDatabricksExistingClusterId": {
"type": "string",
"metadata": "Azure Databricks existing cluster Id"
},
"adfDataLakeConnectionString": {
"type": "string",
"metadata": "Azure Data Lake connection string"
},
"adfDataFactoryIdentity": {
"type": "string",
"metadata": "Identity type for data factory"
},
"adfLSASDBConnectionString": {
"type": "string",
"metadata": "SQL DB connection string"
},
"adfKVBaseURL": {
"type": "string",
"metadata": "Keyvault connection string"
},
"adfDataLakeStorageName": {
"type": "string",
"metadata": "Azure Data lake Storage Name"
},
"adfDataLakeStorageGroupId": {
"type": "string",
"metadata": "Azure Data lake Storage Group ID"
},
"adfSqlServerName": {
"type": "string",
"metadata": "Azure SQL Server name"
},
"adfSqlServerGroupId": {
"type": "string",
"metadata": "Azure SQL Server Group ID"
}
},
"variables": {
"factoryId": "[concat('Microsoft.DataFactory/factories/', parameters('adfFactoryName'))]",
"managedVirtualNetworkName": "[concat(parameters('adfFactoryName'), '/default')]"
},
"resources": [
{
"condition": "[equals(parameters('adfVersion'), 'V2')]",
"type": "Microsoft.DataFactory/factories",
"apiVersion": "2018-06-01",
"name": "[parameters('adfFactoryName')]",
"location": "[parameters('location')]",
"identity": {
"type": "[parameters('adfDataFactoryIdentity')]"
},
"properties": {
"publicNetworkAccess": "[if(bool(parameters('adfPublicNetworkAccess')), 'Enabled', 'Disabled')]"
},
"tags": "[parameters('tags')]",
"resources": [
{
"condition": "[and(equals(parameters('adfVersion'), 'V2'), parameters('adfVNetEnabled'))]",
"name": "[concat(parameters('adfFactoryName'), '/default')]",
"type": "Microsoft.DataFactory/factories/managedVirtualNetworks",
"apiVersion": "2018-06-01",
"properties": {},
"dependsOn": [
"[concat('Microsoft.DataFactory/factories/', parameters('adfFactoryName'))]"
]
},
{
"condition": "[and(equals(parameters('adfVersion'), 'V2'), parameters('adfVNetEnabled'))]",
"name": "[concat(parameters('adfFactoryName'), '/DDIR')]",
"type": "Microsoft.DataFactory/factories/integrationRuntimes",
"apiVersion": "2018-06-01",
"properties": {
"type": "Managed",
"managedVirtualNetwork": {
"referenceName": "default",
"type": "ManagedVirtualNetworkReference"
},
"typeProperties": {
"computeProperties": {
"location": "[parameters('location')]"
}
}
},
"dependsOn": [
"[concat('Microsoft.DataFactory/factories/', parameters('adfFactoryName'))]",
"[concat('Microsoft.DataFactory/factories/', parameters('adfFactoryName'), '/managedVirtualNetworks/default')]"
]
},
{
"name": "[concat(parameters('adfFactoryName'), '/AzureKeyVault')]",
"type": "Microsoft.DataFactory/factories/linkedServices",
"apiVersion": "2018-06-01",
"properties": {
"annotations": [],
"type": "AzureKeyVault",
"typeProperties": {
"baseUrl": "[parameters('adfKVBaseURL')]"
}
},
"dependsOn": [
"[parameters('adfFactoryName')]"
]
},
{
"name": "[concat(parameters('adfFactoryName'), '/AzureDatabricks_LinkedService')]",
"type": "Microsoft.DataFactory/factories/linkedServices",
"apiVersion": "2018-06-01",
"properties": {
"annotations": [],
"type": "AzureDatabricks",
"typeProperties": {
"domain": "[parameters('adfAzureDatabricksDomain')]",
"accessToken": {
"type": "AzureKeyVaultSecret",
"store": {
"referenceName": "AzureKeyVault",
"type": "LinkedServiceReference"
},
"secretName": "[concat('kvs-databricks-',parameters('environment'), 'aue', parameters('project'))]"
},
"existingClusterId": "[parameters('adfAzureDatabricksExistingClusterId')]"
},
"connectVia": {
"referenceName": "DDIR",
"type": "IntegrationRuntimeReference"
}
},
"dependsOn": [
"[parameters('adfFactoryName')]",
"[concat(variables('factoryId'), '/integrationRuntimes/DDIR')]",
"[concat(variables('factoryId'), '/linkedServices/AzureKeyVault')]"
]
},
{
"name": "[concat(parameters('adfFactoryName'), '/AzureDatalake_DDIR')]",
"type": "Microsoft.DataFactory/factories/linkedServices",
"apiVersion": "2018-06-01",
"properties": {
"annotations": [],
"type": "AzureBlobFS",
"typeProperties": {
"url": "[parameters('adfDataLakeConnectionString')]"
},
"connectVia": {
"referenceName": "DDIR",
"type": "IntegrationRuntimeReference"
}
},
"dependsOn": [
"[parameters('adfFactoryName')]",
"[concat(variables('factoryId'), '/integrationRuntimes/DDIR')]"
]
},
{
"name": "[concat(parameters('adfFactoryName'), '/LS_ASDB')]",
"type": "Microsoft.DataFactory/factories/linkedServices",
"apiVersion": "2018-06-01",
"properties": {
"annotations": [],
"type": "AzureSqlDatabase",
"typeProperties": {
"connectionString": "[parameters('adfLSASDBConnectionString')]",
"password": {
"type": "AzureKeyVaultSecret",
"store": {
"referenceName": "AzureKeyVault",
"type": "LinkedServiceReference"
},
"secretName": "kvs-synapsepwd-devauegteng"
}
},
"connectVia": {
"referenceName": "DDIR",
"type": "IntegrationRuntimeReference"
}
},
"dependsOn": [
"[parameters('adfFactoryName')]",
"[concat(variables('factoryId'), '/integrationRuntimes/DDIR')]",
"[concat(variables('factoryId'), '/linkedServices/AzureKeyVault')]"
]
}
]
},
{
"name": "[concat(parameters('adfFactoryName'), '/default/',parameters('adfDataLakeStorageName'))]",
"type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints",
"apiVersion": "2018-06-01",
"properties": {
"privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('adfDataLakeStorageName'))]",
"groupId": "[parameters('adfDataLakeStorageGroupId')]"
},
"dependsOn": [
"[concat('Microsoft.DataFactory/factories/', parameters('adfFactoryName'), '/managedVirtualNetworks/default')]"
]
},
{
"name": "[concat(parameters('adfFactoryName'), '/default/',parameters('adfSqlServerName'))]",
"type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints",
"apiVersion": "2018-06-01",
"properties": {
"privateLinkResourceId": "[resourceId('Microsoft.Sql/servers', parameters('adfSqlServerName'))]",
"groupId": "[parameters('adfSqlServerGroupId')]"
},
"dependsOn": [
"[concat('Microsoft.DataFactory/factories/', parameters('adfFactoryName'), '/managedVirtualNetworks/default')]"
]
}
]
}
I ran in the same problem and created a Az Powershell script function to help me out.
function ApprovePrivateEndpointConnections {
param (
[string] $ResourceGroupName,
[string] $ResourceName
)
$Resource = Get-AzResource -Name $ResourceName -ResourceGroupName $ResourceGroup
if ($Resource) {
$ResourcePendingConnection = Get-AzPrivateEndpointConnection -PrivatelinkResourceId $Resource.ResourceId | Where-Object -FilterScript {$_.PrivateLinkServiceConnectionState.Status -EQ 'Pending'}
if ($ResourcePendingConnection) {
foreach ($Connection in $ResourcePendingConnection)
{
Approve-AzPrivateEndpointConnection -ResourceId $Connection.Id
}
}
else
{
Write-Output 'No pending connections for Resource: ' $ResourceName
}
}
else
{
Write-Output 'No resource found with name: ' $ResourceName
}
}
I found Toofle's script an excellent start here, but was slightly nervous about the security risk of simply approving all pending requests.
I have therefore modified the script to take additional parameters relating to the private endpoint I am expecting, and to ignore any endpoints which do not match.
param(
[Parameter(Mandatory)]
[string] $DataFactoryName,
[Parameter(Mandatory)]
[string] $PrivateEndpointName,
[Parameter(Mandatory)]
[string] $TargetResourceId
)
$Resource = Get-AzResource -ResourceId $TargetResourceId
if ($Resource) {
$ResourcePendingConnection = Get-AzPrivateEndpointConnection -PrivatelinkResourceId $Resource.ResourceId `
| Where-Object -FilterScript {$_.PrivateLinkServiceConnectionState.Status -EQ 'Pending'} `
| Where-Object -FilterScript {$_.PrivateEndpoint.Id -like "*/providers/Microsoft.Network/privateEndpoints/$DataFactoryName.$PrivateEndpointName"}
if ($ResourcePendingConnection) {
foreach ($Connection in $ResourcePendingConnection)
{
Approve-AzPrivateEndpointConnection -ResourceId $Connection.Id
}
}
else
{
Write-Output 'No pending connections for Resource: ' $TargetResourceId
}
}
else
{
Write-Output 'No resource found with ID: ' $TargetResourceId
}
Create private endpoint resource using Azure portal or using ARM template with the following JSON template and mention status as Approved in ConnectionState.
Example:
{
"name": "string",
"type": "Microsoft.Network/privateEndpoints",
"apiVersion": "2020-07-01",
"location": "string",
"tags": {},
"properties": {
"subnet": {
"id": "string",
"name": "string"
},
"privateLinkServiceConnections": [
{
"id": "string",
"properties": {
"privateLinkServiceId": "string",
"groupIds": [
"string"
],
"requestMessage": "string",
"privateLinkServiceConnectionState": {
"status": "string",
"description": "string",
"actionsRequired": "string"
}
},
"name": "string"
}
],
"manualPrivateLinkServiceConnections": [
{
"id": "string",
"properties": {
"privateLinkServiceId": "string",
"groupIds": [
"string"
],
"requestMessage": "string",
"privateLinkServiceConnectionState": {
"status": "string",
"description": "string",
"actionsRequired": "string"
}
},
"name": "string"
}
],
"customDnsConfigs": [
{
"fqdn": "string",
"ipAddresses": [
"string"
]
}
]
},
"resources": []
}
Refer - privateendpoints for string property values.
I couldn't find a way to get it done in ARM itself. My feeling that if it's possible to do so, you have to be on the receiving side of the link (storage account, database etc.), whether it's ARM, Bicep or CLI.
What we are doing now is:
az network private-endpoint-connection list -g <RESOURCE_GROUP> -n <SA_NAME> --type Microsoft.Storage/storageAccounts --query "[?properties.privateLinkServiceConnectionState.status == 'Pending'].name" > output.json
jq -c '.[]' output.json | xargs -r -L 1 az network private-endpoint-connection approve -g <RESOURCE_GROUP> --resource-name <SA_NAME> --type Microsoft.Storage/storageAccounts --description "Approved" -n
You may put this in an Azure CLI step of the release pipeline.
Approval needs to be automated with a script, there is a good example in the official templates.
The following command creates a container in Azure that is mapped to a file share/volume:
az container create -g MyResourceGroup --name myapp --image myimage:latest
--azure-file-volume-share-name myshare --azure-file-volume-account-name mystorageacct
--azure-file-volume-account-key mystoragekey --azure-file-volume-mount-path /mnt/azfile
But I need my container to be mapped to two volumes, not just one. Is this possible?
I do not know if it is possible to do this via azure cli. I do know that you can do this through Azure Resource Manager Templates.
In this example, see how the container group has an array of volumes, while each container can have an array of volume mounts.
{
"type": "Microsoft.ContainerInstance/containerGroups",
"apiVersion": "2018-10-01",
"name": "[parameters('ContainerGroupName')]",
"location": "australiaeast",
"identity": {
"type": "UserAssigned",
"userAssignedIdentities": {
"[variables('managedIdentityId')]": {}
}
},
"dependsOn": [
"[variables('managedIdentityId')]"
],
"properties": {
"containers": [
{
"name": "[parameters('ContainerGroupName')]",
"properties": {
"image": "[parameters('SourceImage')]",
"ports": [{"port": 80},{"port": 443}],
"environmentVariables": [],
"resources": { "requests": { "memoryInGB": 1.5, "cpu": 1 } },
"volumeMounts": [
{
"name": "httpscertificatevolume",
"mountPath": "/https"
},
{
"name": "videofoldervolume",
"mountPath": "[variables('videoFolderMountPath')]"
}
]
}
}
],
"volumes": [{
"name": "httpscertificatevolume",
"azureFile": {
"shareName": "[parameters('HttpsCertificateFileShare')]",
"storageAccountName": "[parameters('StorageAccountName')]",
"storageAccountKey" : "[parameters('StorageAccountKey')]"
}
},
{
"name": "videofoldervolume",
"azureFile": {
"shareName": "[parameters('VideoFileShare')]",
"storageAccountName": "[parameters('StorageAccountName')]",
"storageAccountKey" : "[parameters('StorageAccountKey')]"
}
}
]
}
}
I'm trying to execute a script in my template using Microsoft.Resources/deploymentScripts, but also I'm trying to declare de user assigned identity in the same template
{
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"name": "scriptIdentity",
"apiVersion": "2018-11-30",
"location": "[resourceGroup().location]"
},
{
"type": "Microsoft.Resources/deploymentScripts",
"apiVersion": "2019-10-01-preview",
"name": "updateAppServiceConfigMountPointScript",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/config', parameters('appservice_name'), 'web')]",
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'scriptIdentity')]",
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_name'), 'default')]"
],
"location": "[resourceGroup().location]",
"kind": "AzurePowerShell",
"identity": {
"type": "userAssigned",
"userAssignedIdentities": {
"principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'scriptIdentity'), '2019-08-01', 'full').identity.principalId]",
"clientId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'scriptIdentity'), '2019-08-01', 'full').identity.clientId]"
}
},
"properties": {
"environmentVariables": [
{
"name": "account_name",
"value": "[parameters('storageAccounts_name')]"
},
{
"name": "app_name",
"value": "[parameters('appservice_name')]"
},
{
"name": "resource_group_name",
"value": "[resourceGroup().name]"
}
],
"scriptContent": "$access_key = ((az storage account keys list --account-name $account_name) | ConvertFrom-JSON).value[0]; az webapp config storage-account add --name \\\"$app_name\\\" --resource-group \\\"$resource_group_name\\\" --custom-id \\\"frontend\\\" --storage-type \\\"AzureBlob\\\" --account-name \\\"stelckstorageaccount\\\" --share-name \\\"frontend\\\" --mount-path \\\"/home/site/wwwroot/frontend\\\" --access-key \\\"$access_key\\\"",
"timeout": "PT1M",
"cleanupPreference": "OnSuccess"
}
The template fails in this part:
"userAssignedIdentities": {
"principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'scriptIdentity'), '2019-08-01', 'full').identity.principalId]",
"clientId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'scriptIdentity'), '2019-08-01', 'full').identity.clientId]"
}
With the following error:
Deployment template validation failed: 'The template resource 'updateAppServiceConfigMountPointScript' at line '930' and column '9' is not valid: The template function 'reference' is not expected at this location. Please see https://aka.ms/arm-template-expressions for usage details.. Please see https://aka.ms/arm-template-expressions for usage details.'. (Code: InvalidTemplate)
How can I reference the assigned identity in the deploymentscript without the reference function?
RELATED: 'Microsoft.Web/sites/config' azureStorageAccounts fails due to 500 InternalError
Use the resourceId of the identity, e.g.
"userAssignedIdentities": {
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'scriptIdentity')]": {}
}
See https://github.com/Azure/azure-quickstart-templates/blob/master/201-deployment-script-ssh-key-gen/azuredeploy.json for a full sample.
I had the same issue in a Bicep template, and struggled to figure out how to implement bmoore-msft's answer. While it's not an answer to the original question, I'll post it here in case others like me stumble across this page.
The trick was to use string interpolation to get the identity id on the left side of the colon
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${scriptIdentity.id}': {}
}
}
I am developing ARM template to deploy an App Service Environment v2 configured with an Internal Load Balancer (ILB ASE). Is there a way to grab the Virtual IP (VIP) address that the Internal Load Balancer gets from the vnet it is attached to as an output? When I look at the properties of the ASE via PowerShell after it is provisioned, I do not see a property for the IP address, or for the load balancer.
After much research and testing...there is currently no way to do this as an output from the ARM template. Here are the ways that the value can be collected:
Via Resource Explorer...although this is not very helpful for doing it programmatically but it did help me figure out the other 2 ways
Using PowerShell to query the management.azure.com API but you have to publish an app with the appropriate permissions and assign the app to have permissions in the subscription you are trying to query resources from
Using Azure CLI. This method turned out to be the easiest.
I needed this value to fully automate the deployment of an App Gateway sitting in front of an ILB ASE. I use Terraform for deployment automation and I run the Terraform configs from Azure Cloud Shell. I kick off my deployments with a shell script where I dynamically get the storage account key to the storage account where I store state files. I then query the ILB ASE to get the IP address and set it to a variable that I then pass into Terraform
Below is a copy of the shell script I use:
#!/bin/bash
set -eo pipefail
# The block below will grab the access key for the storage account that is used
# to store state files
subscription_name="<my_subscription_name>"
tfstate_storage_resource_group="terraform-state-rg"
tfstate_storage_account="<name_of_statefile_storage_account>"
subscription_id="my_subscription_id>"
ilbase_rg_name="<name_of_resourcegroup_where_ase_is_deployed>"
ilbase_name="<name_of_ase>"
az account set --subscription "$subscription_name"
tfstate_storage_access_key=$(
az storage account keys list \
--resource-group "$tfstate_storage_resource_group" \
--account-name "$tfstate_storage_account" \
--query '[0].value' -o tsv
)
echo ""
echo "Terraform state storage account access key:"
echo $tfstate_storage_access_key
echo ""
# The block below will get the Virtual IP of the ASE Internal Load Balancer
# which will be used to create the App GW
ilbase_virtual_ip=$(
az resource show \
--ids "/subscriptions/$subscription_id/resourceGroups/$ilbase_rg_name/providers/Microsoft.Web/hostingEnvironments/$ilbase_name/capacities/virtualip" \
--query "additionalProperties.internalIpAddress"
)
echo ""
echo "ASE internal load balancer IP:"
echo $ilbase_virtual_ip
echo ""
terraform plan \
-var "tfstate_access_key=$tfstate_storage_access_key" \
-var "ilbase_virtual_ip=$ilbase_virtual_ip"
You can use output like this:
"outputs": {
"privateIp": {
"type": "string",
"value": "[reference(parameters('lbname')).frontendIPConfigurations[0].properties.privateIPAddress]"
}
}
Here is my template, create one Vnet and one internal load balancer:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vnetName": {
"type": "string",
"defaultValue": "VNet1",
"metadata": {
"description": "VNet name"
}
},
"vnetAddressPrefix": {
"type": "string",
"defaultValue": "10.0.0.0/16",
"metadata": {
"description": "Address prefix"
}
},
"subnet1Prefix": {
"type": "string",
"defaultValue": "10.0.0.0/24",
"metadata": {
"description": "Subnet 1 Prefix"
}
},
"subnet1Name": {
"type": "string",
"defaultValue": "Subnet1",
"metadata": {
"description": "Subnet 1 Name"
}
},
"subnet2Prefix": {
"type": "string",
"defaultValue": "10.0.1.0/24",
"metadata": {
"description": "Subnet 2 Prefix"
}
},
"subnet2Name": {
"type": "string",
"defaultValue": "Subnet2",
"metadata": {
"description": "Subnet 2 Name"
}
},
"lbname": {
"defaultValue": "jasonlbb",
"type": "String"
}
},
"variables": {
"virtualnetworkname" : "vnet1",
"apiVersion": "2015-06-15",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualnetworkname'))]",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('subnet1Name'))]"
},
"resources": [
{
"apiVersion": "2015-06-15",
"type": "Microsoft.Network/virtualNetworks",
"name": "[parameters('vnetName')]",
"location": "[resourceGroup().location]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[parameters('vnetAddressPrefix')]"
]
},
"subnets": [
{
"name": "[parameters('subnet1Name')]",
"properties": {
"addressPrefix": "[parameters('subnet1Prefix')]"
}
},
{
"name": "[parameters('subnet2Name')]",
"properties": {
"addressPrefix": "[parameters('subnet2Prefix')]"
}
}
]
}
},
{
"apiVersion": "2015-05-01-preview",
"type": "Microsoft.Network/loadBalancers",
"name": "[parameters('lbname')]",
"location": "[resourceGroup().location]",
"dependsOn": [
"[variables('vnetID')]"
],
"properties": {
"frontendIPConfigurations": [
{
"properties": {
"subnet": {
"id": "[variables('subnetRef')]"
},
"privateIPAllocationMethod": "Dynamic"
},
"name": "LoadBalancerFrontend"
}
],
"backendAddressPools": [
{
"name": "BackendPool1"
}
],
"loadBalancingRules": [
{
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('lbname')), '/frontendIpConfigurations/LoadBalancerFrontend')]"
},
"backendAddressPool": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('lbname')), '/backendAddressPools/BackendPool1')]"
},
"probe": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('lbname')), '/probes/lbprobe')]"
},
"protocol": "Tcp",
"frontendPort": 80,
"backendPort": 80,
"idleTimeoutInMinutes": 15
},
"Name": "lbrule"
}
],
"probes": [
{
"properties": {
"protocol": "Tcp",
"port": 80,
"intervalInSeconds": 15,
"numberOfProbes": 2
},
"name": "lbprobe"
}
]
}
}
],
"outputs": {
"privateIp": {
"type": "string",
"value": "[reference(parameters('lbname')).frontendIPConfigurations[0].properties.privateIPAddress]"
}
}
}
Here is the screenshot about the result:
Hope this helps.
If you’re using Terraform, here’s how I got it working. Had to use the external data source in Terraform coupled with Azure CLI and jq to get around the bugs in Azure and the Terraform External Data provider.
# As of writing, the ASE ARM deployment don’t return the IP address of the ILB
# ASE. This workaround querys Azure’s API to get the values we need for use
# elsewhere in the script.
# See this https://stackoverflow.com/a/49436100
data “external” “app_service_environment_ilb_ase_ip_address” {
# This calls the Azure CLI then passes the value to jq to return JSON as a single
# string so that external provider can parse it properly. Otherwise you get an
# error. See this bug https://github.com/terraform-providers/terraform-provider-external/issues/23
program = [“bash”, “-c”, “az resource show --ids ${local.app_service_environment_id}/capacities/virtualip --query ‘{internalIpAddress: internalIpAddress}’ | jq -c”]
# Explicit dependency on the ASE ARM deployment because this command will fail
# if that resource isn’t built yet.
depends_on = [azurerm_template_deployment.ase]
}
I have problem deploying more than one azure vms from custom image (aka ami) built using packer.
Here's my packer script to create the base image:
{
"builders": [
{
"type": "azure-arm",
"client_id": "CHANGE_ME",
"client_secret": "CHANGE_ME",
"object_id": "CHANGE_ME",
"subscription_id": "CHANGE_ME",
"tenant_id": "CHANGE_ME",
"resource_group_name": "packerrgvm",
"storage_account": "packerrgvm",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "16.04.0-LTS",
"azure_tags": {
"dept": "engineering"
},
"location": "westeurope",
"vm_size": "Standard_A2"
}
],
"provisioners": [
{
"type": "shell",
"inline": ["do sth interesting here"]
},
{
"type": "shell",
"inline": [
"sudo /usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
]
}
]
}
Now, I'm trying to deploy new vm using ARM templates. My template contains imageName, imageUri and vhdUri provided by packer after successfull build. Vnets, network interfaces etc ommited:
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Compute/virtualMachines",
"name": "[variables('workerVM').machine.name]",
"location": "[resourceGroup().location]",
"properties": {
"hardwareProfile": {
"vmSize": "[variables('workerVM').machine.size]"
},
"storageProfile": {
"osDisk": {
"osType": "Linux",
"name": "[variables('workerVM').machine.imageName]",
"createOption": "FromImage",
"image": {
"uri": "[variables('workerVM').machine.imageUri]"
},
"vhd": {
"uri": "[variables('workerVM').machine.vhdUri]"
},
"caching": "ReadWrite"
}
},
"osProfile": {
"computerName": "[variables('workerVM').machine.name]",
"adminUsername": "[variables('workerVM').machine.adminUsername]",
"adminPassword": "[variables('workerVM').machine.adminPassword]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('workerVM').network.nicName)]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"provisioningState": 0
}
}
When I deploy it for the first time, it works. Hovewer, even if I remove my resource group completely and try to deploy the vm once again, I get the following error:
error: The resource operation completed with terminal provisioning state 'Failed'.
error: Blob https://packerrgvm.blob.core.windows.net/vmcontainera1ba96d3-a593-44b9-8c71-1d345ef67a2d/osDisk.a1ba96d3-a593-44b9-8c71-1d345ef67a2d.vhd already exists. Please provide a different blob URI as target for disk 'packer-osDisk.49359f62-5c49-44c1-aed8-4ea1613ab2e9.vhd'.
Is it possible to use custom ami built by packer this way?
how do i create more than 1 vm from single base image built with backer
You could change the value vhdUri variable before re-deploy the ARM template.
"vhd": {
"uri": "[variables('workerVM').machine.vhdUri]"
}
The vhdUri variable in your ARM template could be like this,
"variables": {
"workerVM": {
"machine": {
"vhdUri": "reset this uri"
}
}
In ARM template add below storage resource which uses image uri generated by packer
{
"type": "Microsoft.Compute/images",
"apiVersion": "2016-04-30-preview",
"name": "[variables('imageName')]",
"location": "[resourceGroup().location]",
"properties": {
"storageProfile": {
"osDisk": {
"osType": "Windows",
"osState": "Generalized",
"blobUri": "[parameters('your_image_uri_generated_by_packer')]",
"storageAccountType": "Standard_LRS"
}
}
}
},
And modify storage profile property in Microsoft.Compute/virtualMachines to use this resource
"storageProfile": {
"imageReference": {
"id": "[resourceId('Microsoft.Compute/images', variables('imageName'))]"
}
},