LibreDevOpsHelpers.AzureFunctionApps/LibreDevOpsHelpers.AzureFunctionApps.psm1
|
Set-StrictMode -Version Latest function Compress-LdoFunctionAppSource { <# .SYNOPSIS Compresses a function app source folder into a deployment zip. .PARAMETER SourcePath Path to the source folder to package. .PARAMETER ZipPath Destination path for the zip file. .PARAMETER Overwrite Overwrite the destination zip if it already exists. .EXAMPLE Compress-LdoFunctionAppSource -SourcePath ./src -ZipPath ./out.zip -Overwrite .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$SourcePath, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$ZipPath, [switch]$Overwrite ) if (Test-Path $ZipPath) { if (-not $Overwrite) { throw "ZipPath already exists: $ZipPath (use -Overwrite)." } Write-LdoLog -Level INFO -Message "Overwriting existing zip: $ZipPath" Remove-Item -Path $ZipPath -Force } Write-LdoLog -Level INFO -Message "Creating deployment package: $ZipPath" Add-Type -AssemblyName 'System.IO.Compression.FileSystem' [System.IO.Compression.ZipFile]::CreateFromDirectory($SourcePath, $ZipPath) if (-not (Test-Path $ZipPath)) { throw 'Zip creation failed.' } $sizeMb = [math]::Round((Get-Item $ZipPath).Length / 1MB, 2) Write-LdoLog -Level SUCCESS -Message "Created $ZipPath ($sizeMb MB)." } function Invoke-LdoFunctionAppZipDeploy { <# .SYNOPSIS Deploys a zip package (or a folder) to a function app via the Azure CLI. .DESCRIPTION Deploys the given zip using 'az functionapp deployment source config-zip'. When a folder is supplied it is compressed to a temporary zip first and cleaned up after. Requires the Azure CLI to be signed in. .PARAMETER ResourceGroup Resource group containing the function app. .PARAMETER FunctionAppName Name of the function app. .PARAMETER ZipPath Path to a zip file or a source folder. .PARAMETER RestartOnFinish Restart the function app after a successful deployment. .PARAMETER CliExtraArgsJson Optional JSON array of extra arguments appended to the az command. .EXAMPLE Invoke-LdoFunctionAppZipDeploy -ResourceGroup rg -FunctionAppName app -ZipPath ./out.zip .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ResourceGroup, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$FunctionAppName, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ZipPath, [switch]$RestartOnFinish, [string]$CliExtraArgsJson ) $tempZip = $null try { if (Test-Path $ZipPath -PathType Container) { $tempZip = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName() + '.zip') Compress-Archive -Path (Join-Path (Resolve-Path $ZipPath).Path '*') -DestinationPath $tempZip -Force Write-LdoLog -Level INFO -Message "Compressed folder '$ZipPath' to '$tempZip'." $ZipPath = $tempZip } $extraArgs = @() if ($CliExtraArgsJson) { $parsed = $CliExtraArgsJson | ConvertFrom-Json if ($parsed -isnot [System.Collections.IEnumerable] -or $parsed -is [string]) { throw 'CliExtraArgsJson must be a JSON array.' } $extraArgs = [string[]]$parsed } $cli = @('functionapp', 'deployment', 'source', 'config-zip', '--resource-group', $ResourceGroup, '--name', $FunctionAppName, '--src', (Resolve-Path $ZipPath).Path) if ($extraArgs) { $cli += $extraArgs } Write-LdoLog -Level INFO -Message "Deploying zip to $FunctionAppName." az @cli | Out-Null Assert-LdoLastExitCode -Operation "az functionapp deployment ($FunctionAppName)" if ($RestartOnFinish) { Write-LdoLog -Level INFO -Message "Restarting function app $FunctionAppName." az functionapp restart --resource-group $ResourceGroup --name $FunctionAppName | Out-Null if ($LASTEXITCODE -ne 0) { Write-LdoLog -Level WARN -Message "Restart of $FunctionAppName returned exit code $LASTEXITCODE." } } Write-LdoLog -Level SUCCESS -Message "Deployment complete on $FunctionAppName." } finally { if ($tempZip -and (Test-Path $tempZip)) { Remove-Item $tempZip -Force -ErrorAction SilentlyContinue Write-LdoLog -Level DEBUG -Message 'Removed temporary zip.' } } } function Get-LdoFunctionAppDefaultUrl { <# .SYNOPSIS Returns the default HTTPS URL of a function app. .PARAMETER ResourceGroup Resource group containing the function app. .PARAMETER FunctionAppName Name of the function app. .EXAMPLE Get-LdoFunctionAppDefaultUrl -ResourceGroup rg -FunctionAppName app .OUTPUTS System.String #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ResourceGroup, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$FunctionAppName ) $hostName = az functionapp show --resource-group $ResourceGroup --name $FunctionAppName --query 'defaultHostName' -o tsv Assert-LdoLastExitCode -Operation "az functionapp show ($FunctionAppName)" if (-not $hostName) { throw "Unable to retrieve the default host name for $FunctionAppName." } $url = "https://$hostName" Write-LdoLog -Level INFO -Message "Function app default URL: $url" return $url } function Set-LdoFunctionAppSetting { <# .SYNOPSIS Applies application settings to a function app from JSON. .DESCRIPTION Accepts either a JSON string or a path to a JSON file, validates it, writes it to a temporary file and applies it with 'az functionapp config appsettings set'. Requires the Azure CLI to be signed in. .PARAMETER ResourceGroup Resource group containing the function app. .PARAMETER FunctionAppName Name of the function app. .PARAMETER SettingsJsonOrPath A JSON string or a path to a .json file describing the settings. .EXAMPLE Set-LdoFunctionAppSetting -ResourceGroup rg -FunctionAppName app -SettingsJsonOrPath ./settings.json .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ResourceGroup, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$FunctionAppName, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$SettingsJsonOrPath ) $tempFile = $null try { if (Test-Path $SettingsJsonOrPath -PathType Leaf) { $json = Get-Content $SettingsJsonOrPath -Raw Write-LdoLog -Level DEBUG -Message "Using settings JSON from file: $SettingsJsonOrPath" } else { $json = $SettingsJsonOrPath Write-LdoLog -Level DEBUG -Message 'Using in-memory settings JSON.' } try { $null = $json | ConvertFrom-Json } catch { throw "SettingsJsonOrPath is not valid JSON: $($_.Exception.Message)" } $tempFile = New-TemporaryFile Set-Content -Path $tempFile -Value $json -Encoding UTF8 Write-LdoLog -Level INFO -Message "Applying app settings to $FunctionAppName." az functionapp config appsettings set --resource-group $ResourceGroup --name $FunctionAppName --settings "@$tempFile" | Out-Null Assert-LdoLastExitCode -Operation "az functionapp config appsettings set ($FunctionAppName)" Write-LdoLog -Level SUCCESS -Message "Updated app settings on $FunctionAppName." } finally { if ($tempFile -and (Test-Path $tempFile)) { Remove-Item $tempFile -Force -ErrorAction SilentlyContinue } } } function Add-LdoFunctionAppCurrentIpRule { <# .SYNOPSIS Grants the caller's public IP access to a function app. .DESCRIPTION Enables public network access, applies the default action to both the main and SCM sites, and adds an access-restriction rule for the caller's current public IP. Requires the Azure CLI to be signed in. .PARAMETER ResourceGroup Resource group containing the function app. .PARAMETER FunctionAppName Name of the function app. .PARAMETER RuleName Name of the access-restriction rule. Defaults to 'AllowCurrentIp'. .PARAMETER Priority Rule priority (100-65000). Defaults to 1000. .PARAMETER Action Allow or Deny. Defaults to Allow. .EXAMPLE Add-LdoFunctionAppCurrentIpRule -ResourceGroup rg -FunctionAppName app .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ResourceGroup, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$FunctionAppName, [string]$RuleName = 'AllowCurrentIp', [ValidateRange(100, 65000)][int]$Priority = 1000, [ValidateSet('Allow', 'Deny')][string]$Action = 'Allow' ) $ip = Get-LdoPublicIpAddress Write-LdoLog -Level INFO -Message "Current public IP: $ip" # See https://github.com/Azure/azure-cli/issues/24947 for why both flags are set. az functionapp update -g $ResourceGroup -n $FunctionAppName --set publicNetworkAccess=Enabled siteConfig.publicNetworkAccess=Enabled | Out-Null Assert-LdoLastExitCode -Operation "az functionapp update ($FunctionAppName)" az functionapp config access-restriction set -g $ResourceGroup -n $FunctionAppName --default-action $Action --scm-default-action $Action --use-same-restrictions-for-scm-site true | Out-Null Assert-LdoLastExitCode -Operation "az functionapp access-restriction set ($FunctionAppName)" az functionapp config access-restriction add -g $ResourceGroup -n $FunctionAppName --rule-name $RuleName --action $Action --priority $Priority --ip-address $ip | Out-Null Assert-LdoLastExitCode -Operation "az functionapp access-restriction add ($FunctionAppName)" Write-LdoLog -Level INFO -Message "Added access rule '$RuleName' for $ip on $FunctionAppName." } function Remove-LdoFunctionAppCurrentIpRule { <# .SYNOPSIS Removes a function app access-restriction rule and locks the app down. .DESCRIPTION Removes the named access-restriction rule, sets the default action to Deny on both the main and SCM sites, and disables public network access. Requires the Azure CLI to be signed in. .PARAMETER ResourceGroup Resource group containing the function app. .PARAMETER FunctionAppName Name of the function app. .PARAMETER RuleName Name of the access-restriction rule to remove. Defaults to 'AllowCurrentIp'. .EXAMPLE Remove-LdoFunctionAppCurrentIpRule -ResourceGroup rg -FunctionAppName app .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ResourceGroup, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$FunctionAppName, [string]$RuleName = 'AllowCurrentIp' ) az functionapp config access-restriction remove -g $ResourceGroup -n $FunctionAppName --rule-name $RuleName 2>$null | Out-Null az functionapp config access-restriction set -g $ResourceGroup -n $FunctionAppName --default-action Deny --scm-default-action Deny --use-same-restrictions-for-scm-site true | Out-Null Assert-LdoLastExitCode -Operation "az functionapp access-restriction set ($FunctionAppName)" az functionapp update -g $ResourceGroup -n $FunctionAppName --set publicNetworkAccess=Disabled siteConfig.publicNetworkAccess=Disabled | Out-Null Assert-LdoLastExitCode -Operation "az functionapp update ($FunctionAppName)" Write-LdoLog -Level INFO -Message "Removed access rule '$RuleName' and disabled public access on $FunctionAppName." } Export-ModuleMember -Function ` Compress-LdoFunctionAppSource, ` Invoke-LdoFunctionAppZipDeploy, ` Get-LdoFunctionAppDefaultUrl, ` Set-LdoFunctionAppSetting, ` Add-LdoFunctionAppCurrentIpRule, ` Remove-LdoFunctionAppCurrentIpRule |