I have an existing Service Bus with one queue and event hub deployed using Azure Resource Manager.
Now I am interested to retrieve the primary key and connection string using Azure PowerShell wiithout using the ServiceBus.dll. Is it possible??
As a workaround I have created an ARM template which does not deploy anything but just query the existing resource and retrieve the information I need. The below template retrieves the connection string and primary key of an event hub/queue for a specific service bus namespace
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"serviceBusNamespace": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "The name of the service bus namespace to create."
}
},
"resourceName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "The name of the resource to be retreived."
}
},
"resourceType": {
"type": "string",
"minLength": 1,
"allowedValues": [
"queues",
"eventhubs"
],
"metadata": {
"description": "The type of the resource"
}
},
"policy": {
"type": "string",
"minLength": 1,
"defaultValue": "ManagePolicy",
"allowedValues": [
"ManagePolicy",
"SendPolicy",
"ListenPolicy"
],
"metadata": {
"description": "The type of the resource"
}
}
},
"variables": {
},
"resources": [ ],
"outputs": {
"connectionString": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/',parameters('resourceType'),'/authorizationRules'),parameters('serviceBusNamespace'),parameters('resourceName'),parameters('policy')),'2015-08-01').primaryConnectionString]"
},
"primaryKey": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/',parameters('resourceType'),'/authorizationRules'),parameters('serviceBusNamespace'),parameters('resourceName'),parameters('policy')),'2015-08-01').primaryKey]"
}
}
}
Is it abusing to use ARM template to query for a resource and not actually deploy anything?
EDIT
To capture the output of the ARM template within PowerShell use the below code
$ep = New-AzureRmResourceGroupDeployment -Name "getEventHub" -ResourceGroupName myResourceGroup -Mode Incremental -TemplateFile getEventHub.json -TemplateParameterFile getEventHub.param.json
$RuleConnString = $ep.Outputs.connectionString.value
$RulePrimaryKey = $ep.Outputs.primaryKey.value
Note that the property names connectionString and primaryKey are same as defined in my template file
EDIT 2
If I re-run the ARM template to deploy the event hub second time I get the below error.
I din't find any option other than to use the ARM template to query the details.
I don’t see what’s wrong with what you’re doing. In my view Resource Manager templates in their nature are incremental. So you could author a template to create your existing service bus with the same resources. If the properties are the same then it will leave the existing resources intact and return you the connection string and primary key of the relevant resource.
I have a need to automate the creation of a service bus and queue and separate send/listen shared access policies. You can retrieve the connection string on the service bus itself using PowerShell natively without using the .Net ServiceBus.dll assembly by using Get-AzureSBAuthorizationRule but due to a still current bug this doesn’t work at the queue level.
I tried using the ServiceBus.dll to create the shared access policies but sometimes it would randomly fail but subsequently work if you ran it a second time immediately afterwards. I also tried Resource Manager templates but previously you had to pass in the keys you’d generated yourself. Now I see Microsoft generate those for you but you’re still left trying to get the key in an automated fashion so I like your solution.
One question though, can you capture the Resource Manager template outputs and pass them back to a PowerShell script, do you know?
Cheers
Rob
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": {
"servicebusNamespace": {
"type": "string",
"metadata": {
"description": "The service bus namespace"
}
},
"notificationssmsqueue": {
"type": "string",
"metadata": {
"description": "Notifications SMS queue"
}
} }, "variables": {
"location": "[resourceGroup().location]", }, "resources": [
{
"apiVersion": "2015-08-01",
"name": "[parameters('servicebusNamespace')]",
"type": "Microsoft.ServiceBus/namespaces",
"location": "[variables('location')]",
"properties": {
"messagingSku": 2
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "[parameters('notificationssmsqueue')]",
"type": "Queues",
"dependsOn": [
"[concat('Microsoft.ServiceBus/namespaces/', parameters('servicebusNamespace'))]"
],
"properties": {
"path": "[parameters('notificationssmsqueue')]"
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "[concat(parameters('notificationssmsqueue'),'.listen')]",
"type": "AuthorizationRules",
"dependsOn": [
"[parameters('notificationssmsqueue')]"
],
"properties": {
"keyName": "[concat(parameters('notificationssmsqueue'),'.listen')]",
"claimType": "SharedAccessKey",
"claimValue": "None",
"rights": [ "Listen" ],
"revision": -1
}
},
{
"apiVersion": "2015-08-01",
"name": "[concat(parameters('notificationssmsqueue'),'.send')]",
"type": "AuthorizationRules",
"dependsOn": [
"[parameters('notificationssmsqueue')]"
],
"properties": {
"keyName": "[concat(parameters('notificationssmsqueue'),'.send')]",
"claimType": "SharedAccessKey",
"claimValue": "None",
"rights": [ "Send" ],
"revision": -1
}
}
]
}
]
} ], "outputs": {
"connectionString": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/AuthorizationRules'),parameters('serviceBusNamespace'),'RootManageSharedAccessKey'),'2015-08-01').primaryConnectionString]"
},
"smsSendPrimaryKey": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/Queues/AuthorizationRules'),parameters('serviceBusNamespace'),parameters('notificationssmsqueue'),concat(parameters('notificationssmsqueue'),'.send')),'2015-08-01').PrimaryKey]"
},
"smsListenPrimaryKey": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/Queues/AuthorizationRules'),parameters('serviceBusNamespace'),parameters('notificationssmsqueue'),concat(parameters('notificationssmsqueue'),'.listen')),'2015-08-01').PrimaryKey]"
} } }
But I call my templates like this:
New-AzureRMResourceGroupDeployment -ResourceGroupName $ResourceGroupName -TemplateFile "$scripts_folder$SB_create_script" -TemplateParameterObject `
#{ servicebusNamespace = $servicebusNamespace;
notificationssmsqueue = $NotificationSMSqueue }
This is the correct way to get the information you are seeking. The Resource Manager provides a common interface to interact with all the services. It is how the Portal access the services, and each of the language SDKs are just wrappers for similar requests to the one you have created.
I usually use the Python or java SDKs, but I have been told that NodeJS is a very easy way to wrap the Web APIs that ARM calls to construct similar calls like the one you made, if you are looking for a none ARM way to do this.
Related
When I deploy this template via Terraform and Azure Devops, I get an Invalid template error while the template deploys normally on the portal. This is the error:
'The template resource '' of type 'microsoft.insights/workbooks' at
line '1' and column '1512' is not valid. The name property cannot be
null or empty. Please see https://aka.ms/arm-template/#resources for
usage details.'."
AdditionalInfo=[{"info":{"lineNumber":1,"linePosition":1512,"path":"properties.template.resources[0]"},"type":"TemplateViolation"}]
What modification should I make to deploy via Terraform?
{
"contentVersion": "1.0.0.0",
"parameters": {
"workbookDisplayName": {
"type": "string",
"defaultValue": "Azure Firewall Workbook",
"metadata": {
"description": "The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group."
}
},
"workbookType": {
"type": "string",
"allowedValues": [
"workbook",
"sentinel"
],
"defaultValue": "workbook",
"metadata": {
"description": "The gallery that the workbook will been shown under. Supported values include workbook, tsg, etc. Usually, this is 'workbook'"
}
},
"DiagnosticsWorkspaceName": {
"type": "string",
"defaultValue": "WorkspaceName",
"metadata": {
"description": "Provide the workspace name for your Network Diagnostic logs"
}
},
"DiagnosticsWorkspaceSubscription": {
"type": "string",
"defaultValue": "WorkspaceSubscriptionID",
"metadata": {
"description": "Provide the workspace subscription GUID for your Network Diagnostic logs"
}
},
"DiagnosticsWorkspaceResourceGroup": {
"type": "string",
"defaultValue": "ResourceGroupName",
"metadata": {
"description": "Provide the workspace resourcegroupname for your Network Diagnostic logs"
}
},
"workbookId": {
"type": "string",
"defaultValue": "[newGuid()]",
"metadata": {
"description": "The unique guid for this workbook instance"
}
}
},
"variables": {
"workbookSourceId": "[concat('/subscriptions/',parameters('DiagnosticsWorkspaceSubscription'),'/resourcegroups/', parameters('DiagnosticsWorkspaceResourceGroup'), '/providers/Microsoft.OperationalInsights/workspaces/',parameters('DiagnosticsWorkspaceName'))]"
},
"resources": [
{
"name": "[parameters('workbookId')]",
"type": "microsoft.insights/workbooks",
"location": "[resourceGroup().location]",
"apiVersion": "2018-06-17-preview",
"dependsOn": [],
"kind": "shared",
"properties": {
"displayName": "[parameters('workbookDisplayName')]"}",
"version": "1.0",
"sourceId": "[variables('workbookSourceId')]",
"category": "[parameters('workbookType')]"
}
}
],
"outputs": {
"workbookId": {
"type": "string",
"value": "[resourceId( 'microsoft.insights/workbooks', parameters('workbookId'))]"
}
},
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#"
}
I don't know ARM templates but I have really good experience on Terraform AWS & Terraform Azure providers.
First of all, you better take a look Terraform resource page which is here. That would be helpful in understanding resource needs and outcomes.
I might be wrong, because your Terraform script is not visible in the question section. Nevertheless, I guess you might have an issue in Terraform side. As I understood, you are getting error from ARM templates. It is complaining about missing name parameter which is mandatory. You may forget passing parameter names from Terraform to ARM template. I might be wrong, this is just a suggestion, the correct way would be reviewing Terraform azurerm_template_deployment resource.
// ARM Template part
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_ZRS"
],
"metadata": {
"description": "Storage Account type"
}
}
},
// Terraform resource provisioning
parameters = {
"storageAccountType" = "Standard_GRS"
}
I am using ARM template in azure to get the possibleOutboundIpAddresses from an app service and use the same to create allow firewall rule in mysql server
Below is the parameter I am using to fetch the ips
"parameters": {
"webAppOutboundIpAddresses": {
"value": "[split(reference(concat('Microsoft.Web/sites/',parameters('wpsitename'))).properties.possibleOutboundIpAddresses,',')]"
and below is the piece of code using in the linked template.
"type": "Microsoft.DBforMySQL/servers/firewallRules",
"apiVersion": "2017-12-01",
"name": "[concat(parameters('sqlServerName'), '/Allow WebApp Outbound IP ',copyIndex('webAppOutboundIPAddressesCopy'))]",
"properties": {
"startIpAddress": "[parameters('webAppOutboundIpAddresses')[copyIndex('webAppOutboundIPAddressesCopy')]]",
"endIpAddress": "[parameters('webAppOutboundIpAddresses')[copyIndex('webAppOutboundIPAddressesCopy')]]"
},
"copy": {
"name": "webAppOutboundIPAddressesCopy",
"count": "[length(parameters('webAppOutboundIpAddresses'))]"
But somehow the pipeline is failing and I am getting an invalid parameter error
InvalidParameterValue",
"message": "Invalid value given for parameter '{0}'. Specify a valid parameter value."
One more thing I noticed is the output IPs are showing inside square brackets like ["192.168.1.2"]
Can someone please shed some light on this? Thanks
If the resource is in the same template, you must use the reference() function and pass it the resource id or only the name:
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appServiceName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Specifies the name of the Azure App Service"
}
},
"appServicePlanName": {
"type": "string",
"minLength": 1
}
},
"variables": {
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "[parameters('appServiceName')]",
"type": "Microsoft.Web/sites",
"kind": "app",
"location": "[resourceGroup().location]",
"dependsOn": [],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]",
"clientAffinityEnabled": false
},
"resources": [],
}
],
"outputs": {
"appServiceName": {
"type": "string",
"value": "reference(parameters('appServiceName'), '2016-03-01', 'Full').properties.inboundIpAddress"
},
"ipAddress": {
"type": "string",
"value": "whatingodsnamegoeshere"
}
}
}
OR
With resourced id:
reference(resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName')), '2016-03-01', 'Full').properties.inboundIpAddress
For the outbound ip addresses: refer here provided by AZToso.
"parameters": {
"webAppOutboundIpAddresses": {
"value": "[split(reference(concat('Microsoft.Web/sites/',variables('webAppName'))).possibleOutboundIpAddresses,',')]"
},
We must develop a loop to iterate through the list of potential outbound IP addresses for a Web App so that we can add each one to the SQL firewall. This is possible with ARM templates by utilizing the copy element. We must utilize the reference function to obtain the WebApp's object. However, the reference function has a restriction that prevents you from using it to change the value of the count property in a copy loop.
I have prepared an ARM template for deploying an Azure Eventhub instance and wonder how to access the both connection keys for returning them as output?
I would like to return a string in the form:
Endpoint=sb://my-eventhub.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=ojZMQcJD7uYifxJyGeXG6tNDdZyaC1/h5tmX6ODVfmY=
Here is my current template:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"clusterName": {
"type": "string",
"defaultValue": "eventhub",
"metadata": {
"description": "Name for the Event Hub cluster."
}
},
"namespaceName": {
"type": "string",
"defaultValue": "namespace",
"metadata": {
"description": "Name for the Namespace to be created in cluster."
}
}
},
"variables": {
"clusterName": "[concat(resourceGroup().name, '-', parameters('clusterName'))]",
"namespaceName": "[concat(resourceGroup().name, '-', parameters('namespaceName'))]"
},
"outputs": {
"MyClusterName": {
"type": "string",
"value": "[variables('clusterName')]"
},
"PrimaryConnectionString": {
"type": "string",
"value": "WHAT TO USE HERE PLEASE?"
},
"SecondaryConnectionString": {
"type": "string",
"value": "WHAT TO USE HERE PLEASE?"
}
},
"resources": [
{
"type": "Microsoft.EventHub/clusters",
"apiVersion": "2018-01-01-preview",
"name": "[variables('clusterName')]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Dedicated",
"capacity": 1
}
},
{
"type": "Microsoft.EventHub/namespaces",
"apiVersion": "2018-01-01-preview",
"name": "[variables('namespaceName')]",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.EventHub/clusters', variables('clusterName'))]"
],
"sku": {
"name": "Standard",
"tier": "Standard",
"capacity": 1
},
"properties": {
"isAutoInflateEnabled": false,
"maximumThroughputUnits": 0,
"clusterArmId": "[resourceId('Microsoft.EventHub/clusters', variables('clusterName'))]"
}
}
]
}
I have tried the following:
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/AuthorizationRules'), variables('namespaceName'), 'RootManageSharedAccessKey'),'2018-01-01-preview').primaryConnectionString]"
but get deployment error:
[error]ParentResourceNotFound: Can not perform requested operation on nested resource. Parent resource 'my-rg-namespace' not found.
UPDATE:
The following has worked for me as suggested by Jesse (thank you!):
"variables": {
"clusterName": "[concat(resourceGroup().name, '-', parameters('clusterName'))]",
"namespaceName": "[concat(resourceGroup().name, '-', parameters('namespaceName'))]",
"defaultSASKeyName": "RootManageSharedAccessKey",
"authRuleResourceId": "[resourceId('Microsoft.EventHub/namespaces/authorizationRules', variables('namespaceName'), variables('defaultSASKeyName'))]"
},
"outputs": {
"MyClusterName": {
"type": "string",
"value": "[variables('clusterName')]"
},
"PrimaryConnectionString": {
"type": "string",
"value": "[listkeys(variables('authRuleResourceId'), '2015-08-01').primaryConnectionString]"
},
"SecondaryConnectionString": {
"type": "string",
"value": "[listkeys(variables('authRuleResourceId'), '2015-08-01').secondaryConnectionString]"
}
},
UPDATE 2:
Also, Jesse has noticed that my ARM template is wrong in 2 ways, because it does not create an Event Hub, but a cluster and it is outside my namespace and provided this valuable comment:
The Event Hubs cluster is basically a way of reserving dedicated compute. It's not something that most scenarios need and it is... not cheap. Think of something on the scale of Xbox Live where you're seeing nearly 5 millions of events per second and which have higher performance needs. If you're not looking at that kind of scale or that sensitivity around timing, you probably want to rethink the need for a dedicated cluster.
Normally, you'd just provision an Event Hubs namespace which will use shared infrastructure with certain guarantees to minimize noisy neighbors and similar. This is adequate for the majority of scenarios, even those with high throughput needs. If you're not sure, this is probably the place that you want to start and then upgrade to a dedicated cluster if your needs justify the cost.
An Event Hubs namespace is the container for a set of Event Hub instances grouped together by a unique endpoint. Each Event Hub is made of a set of partitions. When you're publishing or consuming events, the partitions of an Event Hub are where the actual data is. When you're working with one of the SDKs, you'll start by telling it about the endpoint of your namespace and the Event Hub that you're interested in. You'll need a general awareness of partitions, but most of the "Getting Started" scenarios handle that detail for you, as do a fair portion of the real-world ones.... but, the concept is an important one.
It looks like you may be using an incorrect resource id, pulling from Microsoft.ServiceBus rather than Microsoft.EventHub where the failure is because there is no Service Bus namespace with the correct name.
You may want to try using a form similar to the following to identify your resource:
"variables": {
"location": "[resourceGroup().location]",
"apiVersion": "2015-08-01",
"defaultSASKeyName": "RootManageSharedAccessKey",
"authRuleResourceId": "[resourceId('Microsoft.EventHub/namespaces/authorizationRules', parameters('namespaceName'), variables('defaultSASKeyName'))]"
},
Which should allow it to be returned using listkeys as you you detailed above:
"outputs": {
"NamespaceConnectionString": {
"type": "string",
"value": "[listkeys(variables('authRuleResourceId'), variables('apiVersion')).primaryConnectionString]"
}
}
A full example for a simple deployment can be found in the Event Hubs sample template.
I'm using VSTS to deploy azure resources.
I use task "Azure Resource Group Deployment" to deploy ARM templates.
How can I, for a specific parameter, override the value with a ARM function (concat, listkeys, etc)?
Example: My ARM template has a parameter that is a storage account key and instead of providing the key directly, I want to provide it by passing [listkeys(...)]
You cannot do that, several functions (like listKeys()) are evaluated at runtime only. I don't know what you are trying to achieve, so there are probably ways of doing what you try to achieve.
If you want to hide the keys you can store them in the Key Vault and retrieve at deployment time:
"password": {
"reference": {
"keyVault": {
"id": "[resourceId('kvGroup', 'Microsoft.KeyVault/vaults', 'kvName')]"
},
"secretName": "secret"
}
},
If the storage account isn't created within the same ARM template, I'd use the parameter to supply the name of the storage account and then listkeys() within the ARM template to get at the storage account connection string.
If you're creating the storage account in a previous ARM template deployment in your pipeline you could use output parameters to make the connection string available in the pipeline. Here is an example where xxx represents your company naming prefix:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environment": {
"type": "string",
"defaultValue": "d",
"metadata": {
"description": "The deployment environment, given by develop (d), testing (t), production (p) or quality assurance (q)"
}
}
},
"variables": {
"busUnit": "vendor_name_here",
//storage account names must be lowercase and are limited to 24 alpha numeric characters
"storage_account_name": "[concat('xxx', parameters('environment'), variables('busUnit'), 'stor')]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"sku": {
"name": "Standard_LRS", //this is a hard coded SKU
"tier": "Standard" //general purpose versus blob-only
},
"kind": "Storage",
"name": "[variables('storage_account_name')]",
"apiVersion": "2017-06-01",
"location": "[resourceGroup().location]", //add it to the same region/location as the resource group
"properties": {
"encryption": {
"keySource": "Microsoft.Storage",
"services": {
"blob": {
"enabled": true
}
}
},
"networkAcls": {
"bypass": "AzureServices",
"defaultAction": "Allow",
"ipRules": [],
"virtualNetworkRules": []
}
},
"dependsOn": []
}
],
"outputs": {
"storageAccountKey": {
//"description": "This works if the storage account is in the same resource group. It returns the access key for the account",
"type": "securestring",
"value": "[listKeys(variables('storage_account_name'),'2015-05-01-preview').key1]"
},
"storageAccountName": {
//"description": "This is the computed name of the storage account, based on naming conventions in the variables",
"type": "string",
"value": "[variables('storage_account_name')]"
}
}
}
PROBLEM
How to deploy two different Azure App Services to the same App Service plan when using VSTS idempotent continuous integration / continuous deployment processes.
ENVIRONMENT
I have written two ARM TEMPLATES each of which deploy a web application to Azure App Service.
In order to deploy an App Service an Service Plan must be created first.
The ARM TEMPLATES currently create a unique Service Plan each for each Web App.
I am using VSTS Release Definitions to deploy on each successful VSTS build. i.e releases are designed to be idempotent.
Currently each web app has its own Resource Group which includes it own App Service Plan. Ideally each web app has its own Resource Group, App Service Plan can be in its own Resource Group however (if this is possible).
The template below is an example of one of the templates used to deploy the Web App service to an App Service Plan.
It shows the creation of the App Service Plan using the naming conversion:
appname-Plan-q2dkkaaaaaaaa
This is created using:
Seven Character identifier "appname" defined in the ARM parameters files.
Resource identifier "plan".
Resource Group name , which comes from random named Storage Account name "q2dkkaaaaaaaa" when it was created.
i.e
"hostingPlanName": "[concat(parameters('appName'),'-Plan-', uniqueString(resourceGroup().id))]",
EXAMPLE
{
"parameters": {
"appName": {
"type": "string",
"maxLength": 7,
"metadata": {
"description": "The name of the app that you wish to create."
}
},
"appServicePlanSku": {
"type": "string",
"defaultValue": "Standard",
"metadata": {
"description": "The Service Plan SKU"
}
},
"appServicePlanWorkerSize": {
"type": "string",
"defaultValue": "0",
"metadata": {
"description": "The App Service Plan Worker Size (?)"
}
},
"appServicePlanSkuCode": {
"type": "string",
"defaultValue": "S1",
"metadata": {
"description": "The App Service Plan SKU Code"
}
},
"appServicePlanNumWorkers": {
"type": "string",
"defaultValue": "2",
"metadata": {
"description": "The Number of App Service Workers."
}
},
"variables": {
"webAppName": "[concat(parameters('appName'),'-wa-', uniqueString(resourceGroup().id))]",
"hostingPlanName": "[concat(parameters('appName'),'-Plan-', uniqueString(resourceGroup().id))]",
"stageSlotName": "stageSlot",
"devSlotName": "devSlot"
}
},
"resources": [
{
"apiVersion": "2016-09-01",
"name": "[variables('hostingPlanName')]",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"properties": {
"name": "[variables('hostingPlanName')]",
"workerSizeId": "[parameters('appServicePlanWorkerSize')]",
"numberOfWorkers": "[parameters('appServicePlanNumWorkers')]"
},
"sku": {
"Tier": "[parameters('appServicePlanSku')]",
"Name": "[parameters('appServicePlanSkuCode')]"
},
"dependsOn": []
},
{
"apiVersion": "2015-08-01",
"type": "Microsoft.Web/sites",
"name": "[variables('webAppName')]",
"location": "[resourceGroup().location]",
"kind": "webapp",
"tags": {
"Environment": "production",
"displayName": "WebAppService"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
],
"properties": {
"name": "[variables('webAppName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms',variables('hostingPlanName'))]"
},
"resources": [
{
"name": "slotConfigNames",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('webAppName'))]"
],
"tags": {
"displayName": "slotConfigNames"
},
"properties": {
"appSettingNames": []
}
},
{
"apiVersion": "2015-08-01",
"name": "[variables('stageSlotName')]",
"type": "slots",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('webAppName'))]"],
"properties": {},
"resources": []
},
{
"apiVersion": "2015-08-01",
"name": "[variables('devSlotName')]",
"type": "slots",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('webAppName'))]"],
"properties": {},
"resources": []
}
]
}
]
}
QUESTION
I am attempting to execute two ARM TEMPLATES (similar to the above example) to deploy two different Web Apps to the same Service Plan.
Its clear that both of these Web Apps must call the same central resource to ensure they both deploy to same App Service resource name and execute any changes.
If the App Service plan exists = deploy web app.
If the App Service plan does not exist = create service plan then deploy web app.
If the App Service plan is changed = deploy the service plan change (e.g Tier change) then deploy the web app.
Taking the environmental description above into consideration , what options do I have to get this working?
VSTS Global parameter in the Release Definition maybe ?
ARM TEMPLATES call a PowerShell script that creates the app service plan ?
Keen to follow best practice.
I hope the above is described in enough detail. Sorry if something has been missed. Thank you.
SOLUTION
The solution in my case was to create three templates:
Template 1 to create the app service plan. This ARM template is stored in a BLOB container and is accessible to the release pipeline via a SAS URI.
Template 2 to create web app A. This template uses LINKED TEMPLATE features to call and execute the shared template.
Template 3 to create web app B. This template uses LINKED TEMPLATE features to call and execute the shared template.
RESULT
Both web apps are then published to the same server farm, sharing the
instances.
The idempotent nature of the deployment is maintained.
Single point of truth for any app service plan deployment and any amendments.
Money saved on number of server farms required.
EXAMPLE
Shared Service Plan - ARM Template example of a shared service plan:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"planLabel": {
"defaultValue": "shared-service-plan",
"type": "string"
}
},
"variables": {
"servicePlanName": "[concat(parameters('planLabel'),'-Plan-', uniqueString(resourceGroup().id))]"
},
"resources": [
{
"comments": "Creates an App Service Plan on the Standard (S1) SKU.",
"type": "Microsoft.Web/serverfarms",
"sku": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 2
},
"kind": "app",
"name": "[variables('servicePlanName')]",
"apiVersion": "2016-09-01",
"location": "[resourceGroup().location]",
"properties": {
"name": "[variables('servicePlanName')]"
},
"dependsOn": []
}
],
"outputs": {
"servicePlanResourceId": {
"type": "string",
"value": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]"
},
"servicePlanName":{
"type": "string",
"value": "[variables('servicePlanName')]"
},
"resourceGroupName":{
"type": "string",
"value": "[resourceGroup().name]"
}
}
}
Web App A - ARM Template Example containing LINKED TEMPLATE:
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"servicePlanLabel": {
"type": "string",
"metadata": {
"description": "The base name for the App Service Plan to be used in the linked template."
},
"defaultValue": "plan"
},
"appServicePlanResourceGroup": {
"type": "string",
"metadata": {
"Description": "The name of the Resource Group the shared App Service Plan will be deployed to."
},
"defaultValue": "group"
},
"appServicePlanTemplateUri": {
"type": "string",
"metadata": {
"description": "The URI to the App Service Plan linked template in BLOB"
}
}
},
"variables": {},
"resources": [
{
"apiVersion": "2017-05-10",
"name": "appServicePlanTemplate",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "[parameters('appServicePlanResourceGroup')]",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[parameters('appServicePlanTemplateUri')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"planLabel": {
"value": "[parameters('servicePlanLabel')]"
}
}
}
},
{
"apiVersion": "2015-08-01",
"type": "Microsoft.Web/sites",
"name": "[variables('webAppName')]",
"location": "[resourceGroup().location]",
"kind": "webapp",
"tags": {
"Environment": "production",
"displayName": "App"
},
"dependsOn": [
"[resourceId(parameters('appServicePlanResourceGroup'), 'Microsoft.Resources/deployments', 'appServicePlanTemplate')]"
],
"properties": {}
}
}
}
Hope this is useful to someone.
Thanks
Scott
REF
https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/azure-resource-manager/resource-group-linked-templates.md
Your requirement is default supported by ARM template. Basically if an ARM template encounters a resource which is exist, it will update the resource if the properties do not match. Otherwise it will create the resource using the properties which you set in ARM template.