Prerequisites:
- Create a storage account for deployment (to push your ARM templates)
- Create an Azure service connection of type: Azure Resource Manager
- Create the Logic App Standard ARM Template
However before we can start with the yaml pipeline we need to create the main ARM template and the parameter file which will depending on which logic app you want to deploy invoke the correct sub template (LogicApp ARM Template via TemplateLink). Which is why we need a storage account for the deployment in order to be able to reference each template that we want to deploy. This enables you to be able to deploy multiple logic apps with one pipeline. As we have added a checkbox in yaml for the logic app we can provide the tool values to the main template (true or false).
(Picture 1: YAML Parameters)
(Code snippet 1: azuredeploy.json, main arm template)
(Code snippet 2: azuredeploy.parameters.json, main parameter file)
One thing to add here is that for every relevant file which is required for the deployment of the Logic App the ‘Build Action’ will have to be set to ‘Content’. Otherwise the arm templates and workflows or any other file will not be accessible to the yaml pipeline because it will not be loaded into the ArtifactsStagingDirectory. Another important thing here is that when we use the ‘AzureResourceManagaerTemplateDeployment’ task later on to deploy the logic app templates. You need to make sure that in the main template under ‘variables’ the “linkedTemplates” property mirrors the structure that you have in the storage account (it’s basically similar to your code structure in GIT) otherwise you will get an error indicating that the template could not be found.
Now as we have set up our main arm template lets head on to the yaml pipeline. After we’ve setup our parameters we need to initialize some variables before we head up to the stages:
(Code snippet 3: yaml file, variable section)
This pipeline that we are about to build contains two stages, the first one is the Publish Stage where we build the solution/ or project depending on how you’d like to set your code up and then publish it. The second stage is split into two parts/jobs as there are two possibilities to deploy a standard logic app:
- Job: initialDeployment:
You deploy your logic app initially: StorageAccount, Logic App Standard, Api Connections, appInsights, etc .. and then your source code. - Job: updateWorkflowCode:
You deploy your workflows onto your already existing logic app
Therefore we have two jobs depending on the deploymentType. Where the deploymentJob will be included or excluded.
Lets start with the first stage: Publish
(Code snippet 4: yaml file, 1st Stage: Publish)
First we build the solution or the .csproj to make sure that there are no compile errors. Next we overwrite our yaml parameter values to the parameter file in order to invoke the correct arm template of the specific logic app we want to deploy. As a last step we publish our artifacts (code structure).
Second stage: Deploy, 1st Job: DeploymentJob
(Code snippet 5: yaml file, 2nd Stage: Deploy, LogicApp deployment)
This stage depends on the ‘Stage_Publish’ and will only start if the stage ran successfully otherwise it will break. The first task is moving all of our ‘uploaded’ artifacts which contains our ARM templates to the deployment storage account. Now as already mentioned above for the ‘AzureResourceManagerTemplateDeployment’ – task you need to mirror the structure.
Check the parameters in the task: csmFile & csmParametersfile there we are referencing where to find the ‘Main’ – ARM Templates. After these tasks are done you can check in the portal and already see that everything for your Standard Logic App has been deployed as you have specified in the ARM template (note: I have enabled the System assigned identity as we will need that later. With that we are able to grant permissions to the managed identity using RBAC).
After deploying our Standard Logic App we can finally concentrate on how to deploy our relevant API connection in this case our service bus api connection.
(Code snippet 6: yaml file, 2nd Stage: Deploy, Api Connection deployment)
First we are retrieving our object Id from our system assigned logic app identity via an azure cli command. As we need to provide access to our logic app to be able to use the service bus API connection. You can see that the next task is basically the same as for our previous deployment only difference is that we are directly referencing our ‘connectors’ template. Here we are also overriding our ‘logicAppObjectId’ parameter with the actual value that we have retrieved right before.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"serviceBusConnectionName": {
"type": "string"
},
"servicebus_resourceGroup": {
"type": "string"
},
"serviceBusName": {
"type": "string"
},
"logicAppObjectId": {
"type": "string"
},
"logicAppName": {
"type": "string"
}
},
"variables": {
},
"resources": [
{
"type": "Microsoft.Web/connections",
"apiVersion": "2018-07-01-preview",
"name": "[parameters('serviceBusConnectionName')]",
"location": "[resourceGroup().location]",
"kind": "V2",
"properties": {
"api": {
"id": "[concat(subscription().id, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/servicebus')]"
},
"displayName": "apicon-sb-premium",
"parameterValues": {
"connectionString": "[listKeys(resourceId(parameters('servicebus_resourceGroup'),'Microsoft.ServiceBus/namespaces/AuthorizationRules', parameters('serviceBusName'), 'RootManageSharedAccessKey'),'2015-08-01').primaryConnectionString]"
}
}
},
{
"type": "Microsoft.Web/connections/accessPolicies",
"apiVersion": "2016-06-01",
"name": "[concat(parameters('serviceBusConnectionName'),'/', parameters('logicAppObjectId'))]",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/connections', parameters('serviceBusConnectionName'))]"
],
"properties": {
"principal": {
"type": "ActiveDirectory",
"identity": {
"tenantId": "[subscription().tenantid]",
"objectId": "[parameters('logicAppObjectId')]"
}
}
}
}
],
"outputs": {
"serviceBusEndpointurl": {
"type": "string",
"value": "[reference(resourceId('Microsoft.Web/connections', parameters('serviceBusConnectionName')),'2016-06-01', 'full').properties.connectionRuntimeUrl]"
}
}
}
(Code snippet 7: connectors arm template)
As you can basically see in the arm template we are first deploying the actual API connection and then we are setting the relevant access policy for the logic app via our provided objectId so that the logic app can use the API connection. At the end we need to output the connectionRuntimeUrl which gets generated on API connection deployment as this url is also necessary to properly configure the API connection with the logic app. It is necessary to have this value (connectionRuntimeUrl) referenced in the connections.json file.
{
"managedApiConnections": {
"servicebus_1": {
"api": {
"id": "/subscriptions/@appsetting('subscriptionId')/providers/Microsoft.Web/locations/@appsetting('location_name')/managedApis/servicebus"
},
"authentication": {
"type": "ManagedServiceIdentity"
},
"connection":
"id": "/subscriptions/@appsetting('subscriptionId')/resourceGroups/@appsetting('resourceGroup_name')/providers/Microsoft.Web/connections/@appsetting('serviceBusConnectionName')"
},
"connectionRuntimeUrl": "@appsetting('sbConnectionRuntimeURL')"
}
}
}
(Code snippet 8: connections.json)
This connections.json file contains metadata, endpoint and keys for any managed connections that your workflows use. So basically this file is your configuration for your API connections it is important to parameterize this file as you might need different connections for each environment. Here you can use the appsettings to accomplish that. However as the runtimeUrl gets generated on deployment we cant define this appsetting in our logic app ARM template. We will need to add this appsetting separately. Therefore we will let the pipeline handle that.
Now when you go back to our pipeline (Code snippet: 6) we see that there are two tasks left for our deploymentJob to finish. Using a powershell task we are writing the connectionRuntimeUrl to a variable we can use in our pipeline. That is possible because we wrote the ‘outputs’ of our arm template to a pipeline variable ‘connectionOutput’ via the deploymentTask.
At the end we create our additional appsetting variable using an azure cli command and provide the runtimeUrl to finish the API connection configuration. This concludes the first job of the second stage. Now all that is left is to deploy our code (workflows) to the empty Logic App.
Second stage: Deploy, 2nd Job: WorkflowDeployment
(Code snippet 9: yaml file, 2nd Stage: WorkflowDeployment)
As this is a new job and the ArtifactsStaginDirectory has been cleaned up we need to download our artifacts again which we have published in the first stage that contains all of our files (workflow.json, ARM templates, etc .. ). Once we did that we can build our project folder containing our workflows, connections and other artifacts (may contain liquid mappings for example). The next step is to zip our newly created directory and finally push it to the portal via zipDeployment.
I hope that this article helped you in creating your standard logic app ci/cd yaml pipeline.
Best regards, Armin
Links:
https://learn.microsoft.com/en-us/azure/logic-apps/devops-deployment-single-tenant-azure-logic-apps