Created
August 16, 2024 06:18
-
-
Save JustinGrote/e6d1d927015dcb21cabc5166ed051ecc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -module Az.Functions | |
function Write-CmdletError { | |
param($message, $cmdlet = $PSCmdlet) | |
$cmdlet.ThrowTerminatingError( | |
[Management.Automation.ErrorRecord]::new( | |
$message, | |
'CmdletError', | |
'InvalidArgument', | |
$null | |
) | |
) | |
} | |
filter Publish-AzFunctionApp { | |
<# | |
.SYNOPSIS | |
Quickly publishes a folder or prepackaged zip to a Flex Consumption Azure Function App. | |
.EXAMPLE | |
Publish-AzFunctionApp -Name 'myapp' -ResourceGroup 'myrg' | |
Publishes the function app located in the current directory. | |
.EXAMPLE | |
Get-AzFunctionApp -Name 'myapp' -ResourceGroup 'myrg' | Publish-AzFunctionApp | |
.EXAMPLE | |
Publish-AzFunctionApp -Name 'myapp' -ResourceGroup 'myrg' -Wait | |
.EXAMPLE | |
Publish-AzFunctionApp -Name 'myapp' -ResourceGroup 'myrg' -LogVariable log | |
.EXAMPLE | |
Publish-AzFunctionApp -Name 'myapp' -ResourceGroup 'myrg' -Path '/path/to/already/packaged/function.zip' | |
#> | |
[CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ByName')] | |
param( | |
[Parameter(ParameterSetName = 'ByName', Mandatory)] | |
[string]$Name, | |
[Parameter(ParameterSetName = 'ByName', Mandatory)] | |
[string]$ResourceGroupName, | |
[Parameter(ParameterSetName = 'ByObject', ValueFromPipeline, Mandatory)] | |
$InputObject, | |
#Path to either the function app or a completed zip file | |
[string]$Path = $PWD, | |
#Flex deployment waits 60 seconds to recycle workers. Specify this to wait until that completes. Note you cannot redeploy until this cycle finishes | |
[switch]$Wait, | |
#Assign the deployment log output to this variable | |
[string]$LogVariable | |
) | |
$ErrorActionPreference = 'Stop' | |
$pr = @{ | |
Id = (Get-Random) | |
Activity = 'Publish-AzFunctionApp' | |
} | |
if (-not (Get-Command -Type Application -Name func -ErrorAction SilentlyContinue)) { | |
Write-CmdletError 'Azure Functions Core Tools not found. Please install from https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local' | |
} | |
if ($PSCmdlet.ParameterSetName -eq 'ByName' ) { | |
$InputObject = Get-AzFunctionApp -Name $Name -ResourceGroupName $ResourceGroupName -WarningAction SilentlyContinue -WhatIf:$False | |
} else { | |
if (!$Name) { $Name = $InputObject.Name } | |
} | |
if (-not $InputObject.HostNameSslState -or -not $InputObject.StorageType) { throw 'This does not appear to be an App Function object' } | |
if ($InputObject.StorageType -ne 'blobContainer') { | |
Write-Warning 'This function app is not using blob storage, this has not been tested with anything but Flex Consumption Plan with blob storage backend.' | |
} | |
$scmHost = $inputobject.HostNameSslState.Where{ $_.HostType -EQ 'Repository' }.Name | |
if (-not $scmHost) { throw 'SCM URI not found for the web app. This is only supported with Flex Consupmption.' } | |
$resolvedPath = Resolve-Path $Path | |
$zipPath = if ($resolvedPath -like '*.zip') { | |
$resolvedPath | |
} else { | |
Write-Progress @pr -Status "Packing $resolvedPath to zip file using func pack" -PercentComplete 25 | |
$zipBase = (Join-Path (Get-Item Temp:) (New-Guid)) | |
func pack -o $zipBase $resolvedPath | Write-Debug | |
#Output | |
$zipBase + '.zip' | |
} | |
try { | |
$context = @{ | |
Authentication = 'Bearer' | |
Token = (Get-AzAccessToken -AsSecureString -WarningAction SilentlyContinue).Token | |
Verbose = $false | |
} | |
$iwrParams = @{ | |
Uri = "https://$scmHost/api/publish?RemoteBUild=false&Deployer=az_powershell" | |
Method = 'POST' | |
ContentType = 'application/zip' | |
InFile = $zipPath | |
} | |
if (-not $PSCmdlet.ShouldProcess($Name, "Publish $Path")) { | |
return | |
} | |
Write-Progress @pr -Status "Publishing zip to Function App $Name" -PercentComplete 50 | |
$result = Invoke-WebRequest @context @iwrParams | |
if ($result.StatusCode -ne 202) { | |
Write-CmdletError "Failed to publish zip to Function App $Name. Status code: $($result.StatusCode)" | |
} | |
Write-Verbose "Deployment accepted. Status code: $($result.StatusCode)" | |
[string]$deploymentStatusLink = $result.Headers.Location ?? (throw 'No deployment status link found in response headers') | |
if (-not $noWait) { | |
$logIndex = 0 | |
do { | |
Write-Progress @pr -Status 'Waiting for completion of deployment job. Details in -Debug' -PercentComplete 75 | |
Start-Sleep 0.1 | |
$status = Invoke-RestMethod @context -Uri $deploymentStatusLink | |
$logContent = Invoke-RestMethod @context -Uri $status.log_url | |
if ($logContent.count -gt $logIndex) { | |
$logContent[$logIndex..($logContent.count - 1)] | |
| ForEach-Object { "$($_.log_time): $($_.message)" } | |
| ForEach-Object { | |
if ($_.message -like '*Deployment is partially successful from here*' -and -not $Wait) { | |
$status.status = -777 | |
break | |
} | |
} | |
| Write-Verbose | |
$logIndex = $logContent.count | |
} | |
if ($status.status -gt 4) { | |
break | |
} | |
} until ($status.complete) | |
if ($status.status -gt 4) { | |
Write-CmdletError "Deployment failed: [Code $($status.status)] $($status.status_text)" | |
} elseif ($status.status -eq -777) { | |
Write-Verbose "Deployment is partially complete and -Wait wasn't specified. Exiting." | |
} else { | |
if ($status.status -ne 4) { | |
Write-CmdletError "Deployment had a non-success code: [Code $($status.status)] $($status.status_text)" | |
} | |
Write-Verbose "Deployment Id $($status.id) completed successfully at $($status.end_time). Total Duration: $([datetime]::Parse($status.end_time) - [datetime]::Parse($status.start_time))" | |
} | |
} | |
} finally { | |
if ($LogVariable) { | |
$logContent ??= Invoke-RestMethod @context -Uri $status.log_url | |
Set-Variable -Scope 1 -Name $LogVariable -Value $logContent | |
} | |
$LOCAL:WhatIfPreference = $false | |
Remove-Item -Path $zipPath -Force -ErrorAction SilentlyContinue | |
Write-Progress @pr -Completed | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment