DEV Community

Cover image for Azure DevOps Recipe: Deploying Azure Logic App using Powershell Script
Igor Bertnyk
Igor Bertnyk

Posted on

4 1

Azure DevOps Recipe: Deploying Azure Logic App using Powershell Script

Azure Logic Apps is a somewhat unique cloud service that allows to connect your business-critical apps and services, automating your workflows without writing a single line of code.
There are numerous articles out there about how to deploy Azure Logic App with Azure Resource Manager templates, including official Microsoft documentation:
Automate deployment for Azure Logic Apps by using Azure Resource Manager templates
But in a corporate environment this kind of resources, especially in Production environment, are predefined and secured. Developers often do not have permission to overwrite existing resources, and deploying with ARM templates does exactly that. The template defines the infrastructure, resources, parameters, and other information for provisioning and deploying your logic app. But what we need is to leave the infrastructure alone and deploy just our logic app and its parameters.
So what to do when you dealing with this kind of restricted environment but still want to leverage the power of CI/CD pipelines?
Fortunately, when you create a Logic App using Visual Studio, a sample deployment Powershell script named "Deploy-AzureResourceGroup.ps1" is created for us too. In fact, it uses Azure PowerShell extension that simplifies the management of Azure Cloud services.
Deploy-AzureResourceGroup.ps1

Here is a beginning of this script, pay attention to the parameters that we can provide to customize it:

Param(
    [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation,
    [string] $ResourceGroupName = '<YOUR RESOURCE GROUP NAME>',
    [switch] $UploadArtifacts,
    [string] $StorageAccountName,
    [string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts',
    [string] $TemplateFile = 'LogicApp.json',
    [string] $TemplateParametersFile = 'LogicApp.parameters.json',
    [string] $ArtifactStagingDirectory = '.',
    [string] $DSCSourceFolder = 'DSC',
    [switch] $ValidateOnly
)
...
Enter fullscreen mode Exit fullscreen mode

A full code for the deployment script is provided at the end of the article for those who want to use VS Code or other editor to develop a Logic App.
Let's leverage this script and create CI/CD pipelines in Azure DevOps.

Creating a build artifact in Azure DevOps pipeline

The only things that we need to create a deployable artifact are the files that are located in the project's folder: a JSON file containing the workflow, a parameters file, and a script. So a build pipeline could be extremely simple, consisting just of one step, like the one below.

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: LogicApp'
  inputs:
    PathtoPublish: 'src/LOGIC_APP_FOLDER'
    ArtifactName: 'LogicApp'
    publishLocation: 'Container'
Enter fullscreen mode Exit fullscreen mode

Execute the build pipeline to create an artifact.

Release pipeline

We can start with an empty Release pipeline, and add an artifact produced by the Build pipeline.
Release pipeline
Next, we add a stage, and a single Azure Powershell Task. It is very important to select a Task Version as "2.*" and, correspondingly, Azure PowerShell Version as "Specify Other Version" and Preferred Azure PowerShell Version as "2.1.0" as that is the version that the deployment script is using.
Azure Powershell task
Other important parameters are Script Path - you can navigate to your artifact and select a deployment script.
Also, Script Arguments is a field where we can provide parameters to the script itself. Those arguments correspond to the parameters that we see at the beginning of the script.
A good thing about Azure Powershell task is that it will automatically authenticate it to the Azure via service connection, so you do not have to worry about that.

Powershell Script

Here is a gist with a complete script's code:

#Requires -Version 3.0
Param(
[string] [Parameter(Mandatory=$true)] $ResourceGroupLocation,
[string] $ResourceGroupName = '<YOUR RESOURCE GROUP NAME>',
[switch] $UploadArtifacts,
[string] $StorageAccountName,
[string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts',
[string] $TemplateFile = 'LogicApp.json',
[string] $TemplateParametersFile = 'LogicApp.parameters.json',
[string] $ArtifactStagingDirectory = '.',
[string] $DSCSourceFolder = 'DSC',
[switch] $ValidateOnly
)
try {
[Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '3.0.0')
} catch { }
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 3
function Format-ValidationOutput {
param ($ValidationOutput, [int] $Depth = 0)
Set-StrictMode -Off
return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) })
}
$OptionalParameters = New-Object -TypeName Hashtable
$TemplateFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateFile))
$TemplateParametersFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateParametersFile))
if ($UploadArtifacts) {
# Convert relative paths to absolute paths if needed
$ArtifactStagingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $ArtifactStagingDirectory))
$DSCSourceFolder = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $DSCSourceFolder))
# Parse the parameter file and update the values of artifacts location and artifacts location SAS token if they are present
$JsonParameters = Get-Content $TemplateParametersFile -Raw | ConvertFrom-Json
if (($JsonParameters | Get-Member -Type NoteProperty 'parameters') -ne $null) {
$JsonParameters = $JsonParameters.parameters
}
$ArtifactsLocationName = '_artifactsLocation'
$ArtifactsLocationSasTokenName = '_artifactsLocationSasToken'
$OptionalParameters[$ArtifactsLocationName] = $JsonParameters | Select -Expand $ArtifactsLocationName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore
$OptionalParameters[$ArtifactsLocationSasTokenName] = $JsonParameters | Select -Expand $ArtifactsLocationSasTokenName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore
# Create DSC configuration archive
if (Test-Path $DSCSourceFolder) {
$DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName})
foreach ($DSCSourceFilePath in $DSCSourceFilePaths) {
$DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip'
Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose
}
}
# Create a storage account name if none was provided
if ($StorageAccountName -eq '') {
$StorageAccountName = 'stage' + ((Get-AzureRmContext).Subscription.SubscriptionId).Replace('-', '').substring(0, 19)
}
$StorageAccount = (Get-AzureRmStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName})
# Create the storage account if it doesn't already exist
if ($StorageAccount -eq $null) {
$StorageResourceGroupName = 'ARM_Deploy_Staging'
New-AzureRmResourceGroup -Location "$ResourceGroupLocation" -Name $StorageResourceGroupName -Force
$StorageAccount = New-AzureRmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$ResourceGroupLocation"
}
# Generate the value for artifacts location if it is not provided in the parameter file
if ($OptionalParameters[$ArtifactsLocationName] -eq $null) {
$OptionalParameters[$ArtifactsLocationName] = $StorageAccount.Context.BlobEndPoint + $StorageContainerName
}
# Copy files from the local storage staging location to the storage account container
New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1
$ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process {$_.FullName}
foreach ($SourcePath in $ArtifactFilePaths) {
Set-AzureStorageBlobContent -File $SourcePath -Blob $SourcePath.Substring($ArtifactStagingDirectory.length + 1) `
-Container $StorageContainerName -Context $StorageAccount.Context -Force
}
# Generate a 4 hour SAS token for the artifacts location if one was not provided in the parameters file
if ($OptionalParameters[$ArtifactsLocationSasTokenName] -eq $null) {
$OptionalParameters[$ArtifactsLocationSasTokenName] = ConvertTo-SecureString -AsPlainText -Force `
(New-AzureStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4))
}
}
# Create the resource group only when it doesn't already exist
if ((Get-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -ErrorAction SilentlyContinue) -eq $null) {
New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -ErrorAction Stop
}
if ($ValidateOnly) {
$ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
@OptionalParameters)
if ($ErrorMessages) {
Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.'
}
else {
Write-Output '', 'Template is valid.'
}
}
else {
New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
-ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
@OptionalParameters `
-Force -Verbose `
-ErrorVariable ErrorMessages
if ($ErrorMessages) {
Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") })
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

Conclusion

An alternative method to deploying Azure Logic app is to use Azure Powershell script that is relatively straightforward, does not require elevated permissions, and is applicable in the enterprise and restricted environments.

Well, that is all for this simple recipe. Good luck, and when developing your Logic App be sure not to use a cat logic!
Cat Logic
Photo credit: Ryozuo

Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

Top comments (1)

Collapse
 
mieel profile image
mieel

+1 for cat logic

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay