I have recently been involved with a project where by I needed to deploy a logic app standard using Azure DevOps. This article will explain how I went about it.
The Build Pipeline
the below is the full YAML build pipeline. I’ve explained the important sections below this code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
trigger: - main variables: - group: logic-app-dev - name: vmImageName value: "windows-2019" - name: workingDirectory value: "$(System.DefaultWorkingDirectory)/source/" stages: - stage: Build displayName: Build stage jobs: - job: Build displayName: Build pool: vmImage: $(vmImageName) steps: - task: PowerShell@2 displayName: "Update Connections Json" inputs: filePath: "$(System.DefaultWorkingDirectory)/build/updateconnections.ps1" arguments: "-connectionsFile $(System.DefaultWorkingDirectory)/source/connections.json" - task: ArchiveFiles@2 displayName: "Create LogicApp Deployment Archive" inputs: rootFolderOrFile: "$(System.DefaultWorkingDirectory)/source/" includeRootFolder: false archiveType: zip archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip replaceExistingArchive: true - task: CopyFiles@2 displayName: "Copy AppSettings Files" inputs: SourceFolder: "deploy" contents: "**" TargetFolder: "$(Build.ArtifactStagingDirectory)/AppSettings" - publish: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip artifact: drop - publish: $(Build.ArtifactStagingDirectory)/AppSettings artifact: AppSettings - stage: PRD displayName: Deploy to DEV dependsOn: Build condition: succeeded() jobs: - deployment: Deploy displayName: Deploy environment: "development" pool: vmImage: $(vmImageName) strategy: runOnce: deploy: steps: - task: AzureFunctionApp@1 displayName: Deploy Logic App Workflows inputs: azureSubscription: "$(azureSubscription)" appType: "workflowapp" appName: $(logicAppName) package: "$(Pipeline.Workspace)/drop/$(Build.BuildId).zip" deploymentMethod: "zipDeploy" - task: PowerShell@2 displayName: "Read and Set app settings variable" inputs: targetType: "inline" script: | $jsonContent = Get-Content -Path $(Pipeline.Workspace)/AppSettings/appsettings-dev.json -Raw | ConvertFrom-Json $appsettingscontent = $jsonContent.appSettings | ConvertTo-Json -Compress Write-Host "##vso[task.setvariable variable=AppsettingsContent;]$appsettingscontent" - task: AzureAppServiceSettings@1 displayName: Deploy LogicApp Service Settings inputs: azureSubscription: "$(azureSubscription)" appName: $(logicAppName) resourceGroupName: $(resourceGroupName) appSettings: "$(AppsettingsContent)" - stage: TST displayName: Deploy to TST dependsOn: Build condition: eq(variables['Build.Reason'], 'Manual') jobs: - deployment: Deploy displayName: Deploy environment: "development" pool: vmImage: $(vmImageName) strategy: runOnce: deploy: steps: - task: PowerShell@2 displayName: "Testing Stage" inputs: targetType: "inline" script: | Write-Host "This is the tst stage" |
Update Connections
This section of the process updates the ‘connections.json’ file, from locally stored authentication to the authentication that is needed for deployment into Azure.
1 2 3 4 5 6 |
steps: - task: PowerShell@2 displayName: "Update Connections Json" inputs: filePath: "$(System.DefaultWorkingDirectory)/build/updateconnections.ps1" arguments: "-connectionsFile $(System.DefaultWorkingDirectory)/source/connections.json" |
for example this section:
1 2 3 4 5 |
"authentication": { "type": "Raw", "scheme": "Key", "parameter": "@appsetting('office365-2-connectionKey')" } |
needs to become the following
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
"managedApiConnections": { "office365": { "api": { "id": "/subscriptions/@appsetting('WORKFLOWS_SUBSCRIPTION_ID')/providers/Microsoft.Web/locations/@appsetting('WORKFLOWS_LOCATION_NAME')/managedApis/office365" }, "connection": { "id": "/subscriptions/@appsetting('WORKFLOWS_SUBSCRIPTION_ID')/resourceGroups/@appsetting('WORKFLOWS_RESOURCE_GROUP_NAME')/providers/Microsoft.Web/connections/office365" }, "connectionRuntimeUrl": "@appsetting('OFFICE365_CONNECTION_RUNTIMEURL')", "authentication": { "type": "ManagedServiceIdentity" } } } |
I have achieved this by using the following PowerShell script that I created.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
param ( [string] $connectionsFile ) #Read Connections.json file $jsonContent = Get-Content -Path $connectionsFile -Raw $jsonObject = ConvertFrom-Json -InputObject $jsonContent #Modify Authentication for Managed APIs $newAuthentication = @{ "type" = "ManagedServiceIdentity" } foreach ($property in $jsonObject.managedApiConnections.PSObject.Properties) { if ($property.Value.authentication) { $property.Value.authentication = $newAuthentication } } #Convert back to JSOn $newJson = $jsonObject | ConvertTo-Json -Depth 10 $newJson | Set-Content -Path $connectionsFile |
Deploy the zip
To deploy the logic app (standard) to the web app, I am using the task ‘AzureFunctionApp@1’. this parameter ‘appType: “workflowapp”‘ it’s important that it’s set to ‘workflowapp’ failing to set this will result in your workflows being removed the next time you deploy the logic app IaC code.
1 |
- task: AzureFunctionApp@1 displayName: Deploy Logic App Workflows inputs: azureSubscription: "$(azureSubscription)" appType: "workflowapp" appName: $(logicAppName) package: "$(Pipeline.Workspace)/drop/$(Build.BuildId).zip" deploymentMethod: "zipDeploy" |
App Settings
since Logic App (Standard) uses an app service plan, the next section is to add custom app settings. It’s reading my ‘appsettings-dev.json’ file and then storing the contents of this file into a variable called “AppsettingsContent”.
1 2 3 4 5 6 7 8 9 |
- task: PowerShell@2 displayName: "Read and Set app settings variable" inputs: targetType: "inline" script: | $jsonContent = Get-Content -Path $(Pipeline.Workspace)/AppSettings/appsettings-dev.json -Raw | ConvertFrom-Json $appsettingscontent = $jsonContent.appSettings | ConvertTo-Json -Compress Write-Host "##vso[task.setvariable variable=AppsettingsContent;]$appsettingscontent" |
the json file looks like the below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
{ "appSettings": [ { "name": "office365-connectionKey", "value": "thisteststring", "slotSetting": false }, { "name": "azureFunctionOperation_functionAppKey", "value": "test", "slotSetting": false }, { "name": "azureFunctionOperation_functionResourceURI", "value": "test", "slotSetting": false }, { "name": "azureFunctionOperation_functionTriggerURI", "value": "test", "slotSetting": false }, { "name":"AzureBlob_connectionString", "value": "@Microsoft.KeyVault(SecretUri=https://mykv.vault.azure.net/secrets/AzureBlobConnectionString)", "slotSetting": false } ] } |
as you can see from above we can use key vault references.
the below task, deploys the actual app settings that we stored in the variable ‘AppsettingsContent’
1 2 3 4 5 6 7 |
- task: AzureAppServiceSettings@1 displayName: Deploy LogicApp Service Settings inputs: azureSubscription: "$(azureSubscription)" appName: $(logicAppName) resourceGroupName: $(resourceGroupName) appSettings: "$(AppsettingsContent)" |
This concludes this article.