From 3e69c7c8f73f37e0cddcd49cb7a6444bcaba92e5 Mon Sep 17 00:00:00 2001 From: picccard <7443949+picccard@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:37:06 +0200 Subject: [PATCH 1/6] format document in vscode --- deploy/deploy.ps1 | 387 ++++++++++++++++++++++++---------------------- 1 file changed, 200 insertions(+), 187 deletions(-) diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index 5da035f..11a10c9 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -75,7 +75,7 @@ param( [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $false, ParameterSetName = 'FunctionContainer')] - [ValidateLength(1,7)] + [ValidateLength(1, 7)] [string] $NamePrefix, @@ -134,7 +134,7 @@ param( Mandatory = $false, ParameterSetName = 'AppsOnly')] [ValidatePattern('^([\x21-\x7E]*)(?" $field -ForegroundColor Red - } + foreach ($field in $invalidFields) { + Write-Host "ERROR: Invalid Field ->" $field -ForegroundColor Red + } - foreach ($field in $missingFields) { - Write-Host "ERROR: Missing Field ->" $field -ForegroundColor Red - } + foreach ($field in $missingFields) { + Write-Host "ERROR: Missing Field ->" $field -ForegroundColor Red + } - Write-Host "ERROR: Please refer to the 'Naming Rules and Restrictions for Azure Resources'" -ForegroundColor Red - Write-Host "ERROR: " -ForegroundColor Red -NoNewline - Write-Host "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules" -ForegroundColor Yellow - Write-Host + Write-Host "ERROR: Please refer to the 'Naming Rules and Restrictions for Azure Resources'" -ForegroundColor Red + Write-Host "ERROR: " -ForegroundColor Red -NoNewline + Write-Host "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules" -ForegroundColor Yellow + Write-Host - throw [System.ArgumentException]::New("One of the required resource names is missing or invalid.") - } + throw [System.ArgumentException]::New("One of the required resource names is missing or invalid.") + } - return -not ($invalidFields -or $missingFields) - }) + return -not ($invalidFields -or $missingFields) + }) $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attributeCollection.Add($attrApp) @@ -343,7 +344,7 @@ process { # Set Log File Location $logPath = Join-Path -Path $ROOT_DIR -ChildPath "logs" - New-Item -ItemType Directory -Path $logpath -Force| Out-Null + New-Item -ItemType Directory -Path $logpath -Force | Out-Null $debugLog = Join-Path -Path $logPath -ChildPath "debug_$(get-date -format `"yyyyMMddhhmmsstt`").log" $errorLog = Join-Path -Path $logPath -ChildPath "error_$(get-date -format `"yyyyMMddhhmmsstt`").log" @@ -358,7 +359,7 @@ process { Function Test-Location { Param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$Location ) @@ -369,22 +370,22 @@ process { Function Get-BuildLogs { Param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$SubscriptionId, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$ResourceGroupName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$RegistryName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$BuildId ) $msArmMap = @{ - AZURE_PUBLIC = "management.azure.com" - AZURE_US_GOV = "management.usgovcloudapi.net" - AZURE_US_GOV_SECRET = "management.azure.microsoft.scloud" - AZURE_GERMANY = "management.microsoftazure.de" - AZURE_CHINA = "management.chinacloudapi.cn" + AZURE_PUBLIC = "management.azure.com" + AZURE_US_GOV = "management.usgovcloudapi.net" + AZURE_US_GOV_SECRET = "management.azure.microsoft.scloud" + AZURE_GERMANY = "management.microsoftazure.de" + AZURE_CHINA = "management.chinacloudapi.cn" }; $accessToken = (Get-AzAccessToken).Token | ConvertTo-SecureString -AsPlainText @@ -406,40 +407,40 @@ process { Function Deploy-IPAMApplications { Param( - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$EngineAppName = 'ipam-engine-app', - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$UIAppName = 'ipam-ui-app', - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$MgmtGroupId, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$AzureCloud, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [bool]$DisableUI = $false ) $uiResourceAccess = [System.Collections.ArrayList]@( @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; # Microsoft Graph + ResourceAppId = "00000003-0000-0000-c000-000000000000"; # Microsoft Graph ResourceAccess = @( @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; # openid + Id = "37f7f235-527c-4136-accd-4a02d197296e"; # openid Type = "Scope" }, @{ - Id = "14dad69e-099b-42c9-810b-d002981feec1"; # profile + Id = "14dad69e-099b-42c9-810b-d002981feec1"; # profile Type = "Scope" }, @{ - Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; # offline_access + Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; # offline_access Type = "Scope" }, @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; # User.Read + Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; # User.Read Type = "Scope" }, @{ - Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; # Directory.Read.All + Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; # Directory.Read.All Type = "Scope" } ) @@ -456,24 +457,24 @@ process { } $engineResourceMap = @{ - "AZURE_PUBLIC" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + "AZURE_PUBLIC" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation } - "AZURE_US_GOV" = @{ - ResourceAppId = "40a69793-8fe6-4db1-9591-dbc5c57b17d8" # Azure Service Management + "AZURE_US_GOV" = @{ + ResourceAppId = "40a69793-8fe6-4db1-9591-dbc5c57b17d8" # Azure Service Management ResourceAccessIds = @("8eb49ffc-05ac-454c-9027-8648349217dd", "e59ee429-1fb1-4054-b99f-f542e8dc9b95") # user_impersonation } "AZURE_US_GOV_SECRET" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation } - "AZURE_GERMANY" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + "AZURE_GERMANY" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation } - "AZURE_CHINA" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + "AZURE_CHINA" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation } } @@ -504,25 +505,25 @@ process { ) $engineApiSettings = @{ - Oauth2PermissionScope = @( + Oauth2PermissionScope = @( @{ AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." AdminConsentDisplayName = "Access IPAM Engine API" - Id = $engineApiGuid - IsEnabled = $true - Type = "User" - UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." - UserConsentDisplayName = "Access IPAM Engine API" - Value = "access_as_user" + Id = $engineApiGuid + IsEnabled = $true + Type = "User" + UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." + UserConsentDisplayName = "Access IPAM Engine API" + Value = "access_as_user" } ) - PreAuthorizedApplication = @( # Allow Azure PowerShell/CLI to obtain access tokens + PreAuthorizedApplication = @( # Allow Azure PowerShell/CLI to obtain access tokens @{ - AppId = "1950a258-227b-4e31-a9cf-717495945fc2" # Azure PowerShell + AppId = "1950a258-227b-4e31-a9cf-717495945fc2" # Azure PowerShell DelegatedPermissionId = @( $engineApiGuid ) }, @{ - AppId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" # Azure CLI + AppId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" # Azure CLI DelegatedPermissionId = @( $engineApiGuid ) } ) @@ -547,11 +548,11 @@ process { # Update IPAM Engine API Endpoint Update-AzADApplication -ApplicationId $engineApp.AppId -IdentifierUri "api://$($engineApp.AppId)" - $uiEngineApiAccess =@{ - ResourceAppId = $engineApp.AppId + $uiEngineApiAccess = @{ + ResourceAppId = $engineApp.AppId ResourceAccess = @( @{ - Id = $engineApiGuid + Id = $engineApiGuid Type = "Scope" } ) @@ -583,9 +584,9 @@ process { # Create IPAM Engine Service Principal New-AzADServicePrincipal -ApplicationObject $engineObject ` - -Role "Reader" ` - -Scope $scope ` - | Out-Null + -Role "Reader" ` + -Scope $scope ` + | Out-Null Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor Green @@ -594,7 +595,8 @@ process { if (-not $DisableUI) { Write-Host "INFO: Azure IPAM Engine & UI Applications/Service Principals created successfully" -ForegroundColor Green - } else { + } + else { Write-Host "INFO: Azure IPAM Engine Application/Service Principal created successfully" -ForegroundColor Green } @@ -613,34 +615,34 @@ process { Function Grant-AdminConsent { Param( - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$UIAppId = [GUID]::Empty, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$EngineAppId, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$AzureCloud, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [bool]$DisableUI = $false ) $msGraphMap = @{ - AZURE_PUBLIC = @{ + AZURE_PUBLIC = @{ Endpoint = "graph.microsoft.com" Environment = "Global" } - AZURE_US_GOV = @{ + AZURE_US_GOV = @{ Endpoint = "graph.microsoft.us" Environment = "USGov" } - AZURE_US_GOV_SECRET = @{ + AZURE_US_GOV_SECRET = @{ Endpoint = "graph.cloudapi.microsoft.scloud" Environment = "USSec" } - AZURE_GERMANY = @{ + AZURE_GERMANY = @{ Endpoint = "graph.microsoft.de" Environment = "Germany" } - AZURE_CHINA = @{ + AZURE_CHINA = @{ Endpoint = "microsoftgraph.chinacloudapi.cn" Environment = "China" } @@ -649,14 +651,14 @@ process { $uiGraphScopes = [System.Collections.ArrayList]@( @{ scopeId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph - scopes = " openid profile offline_access User.Read Directory.Read.All" + scopes = " openid profile offline_access User.Read Directory.Read.All" } ) $engineGraphScopes = [System.Collections.ArrayList]@( @{ scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - scopes = "user_impersonation" + scopes = "user_impersonation" } ) @@ -690,7 +692,7 @@ process { if (-not $DisableUI) { Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI application" -ForegroundColor Green - foreach($scope in $uiGraphScopes) { + foreach ($scope in $uiGraphScopes) { $msGraphId = Get-AzADServicePrincipal ` -ApplicationId $scope.scopeId @@ -699,7 +701,7 @@ process { -Scope $scope.scopes ` -ClientId $uiSpn.Id ` -ConsentType AllPrincipals ` - | Out-Null + | Out-Null } Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor Green @@ -714,7 +716,7 @@ process { -Scope "access_as_user" ` -ClientId $uiSpn.Id ` -ConsentType AllPrincipals ` - | Out-Null + | Out-Null Write-Host "INFO: Admin consent for IPAM Engine exposed API granted successfully" -ForegroundColor Green } @@ -722,7 +724,7 @@ process { Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine application" -ForegroundColor Green # Grant admin consent for Azure Service Management API permissions assigned to IPAM Engine application - foreach($scope in $engineGraphScopes) { + foreach ($scope in $engineGraphScopes) { $msGraphId = Get-AzADServicePrincipal ` -ApplicationId $scope.scopeId @@ -731,7 +733,7 @@ process { -Scope $scope.scopes ` -ClientId $engineSpn.Id ` -ConsentType AllPrincipals ` - | Out-Null + | Out-Null } Write-Host "INFO: Admin consent for Azure Service Management API permissions granted successfully" -ForegroundColor Green @@ -739,13 +741,13 @@ process { Function Save-Parameters { Param( - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$UIAppId = [GUID]::Empty, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$EngineAppId, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$EngineSecret, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [bool]$DisableUI = $false ) @@ -761,7 +763,8 @@ process { if (-not $DisableUI) { $parametersObject.parameters.uiAppId.value = $UIAppId $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property uiAppId, engineAppId, engineAppSecret - } else { + } + else { $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property engineAppId, engineAppSecret } @@ -773,7 +776,7 @@ process { Function Import-Parameters { Param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.IO.FileInfo]$ParameterFile ) @@ -816,25 +819,25 @@ process { Function Deploy-Bicep { Param( - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$UIAppId = [GUID]::Empty, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$EngineAppId, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$EngineSecret, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$NamePrefix, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$AzureCloud, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [bool]$Function, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [bool]$Native, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [bool]$PrivateAcr, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [hashtable]$Tags, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [hashtable]$ResourceNames ) @@ -847,44 +850,44 @@ process { uiAppId = $UiAppId } - if($NamePrefix) { + if ($NamePrefix) { $deploymentParameters.Add('namePrefix', $NamePrefix) } - if($AzureCloud) { + if ($AzureCloud) { $deploymentParameters.Add('azureCloud', $AzureCloud) } - if($Function) { + if ($Function) { $deploymentParameters.Add('deployAsFunc', $Function) } - if(-not $Native) { + if (-not $Native) { $deploymentParameters.Add('deployAsContainer', !$Native) } - if($PrivateAcr) { + if ($PrivateAcr) { $deploymentParameters.Add('privateAcr', $PrivateAcr) } - if($Tags) { + if ($Tags) { $deploymentParameters.Add('tags', $Tags) } - if($ResourceNames) { + if ($ResourceNames) { $deploymentParameters.Add('resourceNames', $ResourceNames) } $DebugPreference = $debugSetting # Deploy IPAM bicep template - $deployment = &{ + $deployment = & { New-AzSubscriptionDeployment ` - -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` - -Location $location ` - -TemplateFile main.bicep ` - -TemplateParameterObject $deploymentParameters ` - 5>$($DEBUG_MODE ? $debugLog : $null) + -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` + -Location $location ` + -TemplateFile main.bicep ` + -TemplateParameterObject $deploymentParameters ` + 5>$($DEBUG_MODE ? $debugLog : $null) } $DebugPreference = 'SilentlyContinue' @@ -896,11 +899,11 @@ process { Function Publish-ZipFile { Param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$AppName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$ResourceGroupName, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch]$UseAPI ) @@ -930,8 +933,9 @@ process { -ArchivePath $zipPath ` -Restart ` -Force ` - | Out-Null - } else { + | Out-Null + } + else { Invoke-RestMethod ` -Uri "https://${zipUrl}/api/zipdeploy" ` -Method Post ` @@ -939,20 +943,22 @@ process { -Headers @{ "Authorization" = "Bearer $accessToken" } ` -Form @{ file = $zipContents } ` -StatusCodeVariable statusCode ` - | Out-Null + | Out-Null - if ($statusCode -ne 200) { - throw [System.Exception]::New("Error while uploading ZIP Deploy via Kudu API! ($statusCode)") - } + if ($statusCode -ne 200) { + throw [System.Exception]::New("Error while uploading ZIP Deploy via Kudu API! ($statusCode)") + } } $publishSuccess = $True Write-Host "INFO: ZIP Deploy archive successfully uploaded" -ForegroundColor Green - } catch { - if($publishRetries -gt 0) { + } + catch { + if ($publishRetries -gt 0) { Write-Host "WARNING: Problem while uploading ZIP Deploy archive! Retrying..." -ForegroundColor Yellow $publishRetries-- - } else { + } + else { Write-Host "ERROR: Unable to upload ZIP Deploy archive!" -ForegroundColor Red throw $_ } @@ -962,9 +968,9 @@ process { Function Update-UIApplication { Param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$UIAppId, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$Endpoint ) @@ -981,20 +987,20 @@ process { # Main Deployment Script Section Write-Host - if($DEBUG_MODE) { + if ($DEBUG_MODE) { Write-Host "DEBUG: Debug Mode Enabled" -ForegroundColor Gray } Write-Host "NOTE: IPAM Deployment Type: $($PSCmdlet.ParameterSetName)" -ForegroundColor Magenta try { - if($PrivateAcr) { + if ($PrivateAcr) { Write-Host "INFO: PrivateACR flag set, verifying minimum Azure CLI version" -ForegroundColor Green # Verify Minimum Azure CLI Version $azureCliVer = [System.Version](az version | ConvertFrom-Json).'azure-cli' - if($azureCliVer -lt $MIN_AZ_CLI_VER) { + if ($azureCliVer -lt $MIN_AZ_CLI_VER) { Write-Host "ERROR: Azure CLI must be version $MIN_AZ_CLI_VER or greater!" -ForegroundColor Red exit } @@ -1004,7 +1010,7 @@ process { # Verify Azure PowerShell and Azure CLI Contexts Match $azureCliContext = $(az account show | ConvertFrom-Json) 2>$null - if(-not $azureCliContext) { + if (-not $azureCliContext) { Write-Host "ERROR: Azure CLI not logged in or no subscription has been selected!" -ForegroundColor Red exit } @@ -1012,7 +1018,7 @@ process { $azureCliSub = $azureCliContext.id $azurePowerShellSub = (Get-AzContext).Subscription.Id - if($azurePowerShellSub -ne $azureCliSub) { + if ($azurePowerShellSub -ne $azureCliSub) { Write-Host "ERROR: Azure PowerShell and Azure CLI must be set to the same context!" -ForegroundColor Red exit } @@ -1020,9 +1026,10 @@ process { if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer', 'AppsOnly')) { # Fetch Tenant ID (If Required) - if($MgmtGroupId) { + if ($MgmtGroupId) { Write-Host "NOTE: Management Group ID Specified" -ForegroundColor Magenta - } else { + } + else { Write-Host "INFO: Fetching Tenant ID from Azure PowerShell SDK" -ForegroundColor Green $script:MgmtGroupId = (Get-AzContext).Tenant.Id } @@ -1048,7 +1055,8 @@ process { # Validate Azure Region if (Test-Location -Location $Location) { Write-Host "INFO: Azure Region validated successfully" -ForegroundColor Green - } else { + } + else { Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor Red Write-Host Write-Host "Azure Region: " -ForegroundColor Yellow -NoNewline @@ -1107,7 +1115,8 @@ process { try { Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value - } catch { + } + catch { Write-Host "SWITCH: Retrying ZIP Deploy with Kudu API..." -ForegroundColor Blue Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -UseAPI } @@ -1119,16 +1128,16 @@ process { $containerMap = @{ Debian = @{ Extension = 'deb' - Port = 8080 - Images = @{ + Port = 8080 + Images = @{ Build = 'node:18-slim' Serve = 'python:3.9-slim' } } - RHEL = @{ + RHEL = @{ Extension = 'rhel' - Port = 8080 - Images = @{ + Port = 8080 + Images = @{ Build = 'registry.access.redhat.com/ubi8/nodejs-18' Serve = 'registry.access.redhat.com/ubi8/python-39' } @@ -1139,14 +1148,14 @@ process { $dockerFilePath = Join-Path -Path $ROOT_DIR -ChildPath $dockerFile $dockerFileFunc = Join-Path -Path $ROOT_DIR -ChildPath 'Dockerfile.func' - if($Function) { + if ($Function) { Write-Host "INFO: Building Function container..." -ForegroundColor Green $funcBuildOutput = $( az acr build -r $deployment.Outputs["acrName"].Value ` - -t ipamfunc:latest ` - -f $dockerFileFunc $ROOT_DIR ` - --no-logs + -t ipamfunc:latest ` + -f $dockerFileFunc $ROOT_DIR ` + --no-logs ) *>&1 if ($LASTEXITCODE -ne 0) { @@ -1163,14 +1172,16 @@ process { $buildLogs | Out-File -FilePath $errorLog -Append $script:containerBuildError = $true - } else { + } + else { Write-Host "INFO: Function container image build and push completed successfully" -ForegroundColor Green } Write-Host "INFO: Restarting Function App" -ForegroundColor Green Restart-AzFunctionApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -Force | Out-Null - } else { + } + else { Write-Host "INFO: Building App container ($ContainerType)..." -ForegroundColor Green $appBuildOutput = $( @@ -1197,11 +1208,12 @@ process { $buildLogs | Out-File -FilePath $errorLog -Append $script:containerBuildError = $true - } else { + } + else { Write-Host "INFO: App container image build and push completed successfully" -ForegroundColor Green } - if(-not $containerBuildError) { + if (-not $containerBuildError) { Write-Host "INFO: Restarting App Service" -ForegroundColor Green Restart-AzWebApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value | Out-Null @@ -1209,9 +1221,10 @@ process { } } - if(-not $containerBuildError) { + if (-not $containerBuildError) { Write-Host "INFO: Azure IPAM Solution deployed successfully" -ForegroundColor Green - } else { + } + else { Write-Host "WARNING: Azure IPAM Solution deployed with errors, see logs for details!" -ForegroundColor Yellow Write-Host "Run Log: $transcriptLog" -ForegroundColor Yellow Write-Host "Error Log: $errorLog" -ForegroundColor Yellow @@ -1244,7 +1257,7 @@ process { Write-Host "Run Log: $transcriptLog" -ForegroundColor Red Write-Host "Error Log: $errorLog" -ForegroundColor Red - if($DEBUG_MODE) { + if ($DEBUG_MODE) { Write-Host "Debug Log: $debugLog" -ForegroundColor Red } } From 91d0b16be83748f230a13ec983003442892545a3 Mon Sep 17 00:00:00 2001 From: picccard <7443949+picccard@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:44:16 +0200 Subject: [PATCH 2/6] trim whitespace in vscode --- deploy/deploy.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index 11a10c9..194bbcf 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -1,7 +1,7 @@ ############################################################################################################### ## ## Azure IPAM Solution Deployment Script -## +## ############################################################################################################### # Set minimum version requirements @@ -203,7 +203,7 @@ param( if ($_ -notmatch "(\.json)") { throw [System.ArgumentException]::New("The file specified in the 'ParameterFile' argument must be of type json.") } - return $true + return $true })] [System.IO.FileInfo] $ParameterFile @@ -395,13 +395,13 @@ process { -Uri "https://$($msArmMap[$AzureCloud])/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.ContainerRegistry/registries/$RegistryName/runs/$BuildId/listLogSasUrl?api-version=2019-04-01" ` -Authentication Bearer ` -Token $accessToken - + $logLink = $response.logLink $logs = Invoke-RestMethod ` -Method GET ` -Uri $logLink - + return $logs } @@ -506,7 +506,7 @@ process { $engineApiSettings = @{ Oauth2PermissionScope = @( - @{ + @{ AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." AdminConsentDisplayName = "Access IPAM Engine API" Id = $engineApiGuid @@ -568,7 +568,7 @@ process { $uiObject = Get-AzADApplication -ApplicationId $uiApp.AppId } - + $engineObject = Get-AzADApplication -ApplicationId $engineApp.AppId # Create IPAM UI Service Principal (If DisableUI not specified) @@ -695,7 +695,7 @@ process { foreach ($scope in $uiGraphScopes) { $msGraphId = Get-AzADServicePrincipal ` -ApplicationId $scope.scopeId - + New-MgOauth2PermissionGrant ` -ResourceId $msGraphId.Id ` -Scope $scope.scopes ` @@ -979,7 +979,7 @@ process { $appServiceEndpoint = "https://$Endpoint" # Update UI Application with single-page application configuration - Update-AzADApplication -ApplicationId $UIAppId -SPARedirectUri $appServiceEndpoint + Update-AzADApplication -ApplicationId $UIAppId -SPARedirectUri $appServiceEndpoint Write-Host "INFO: UI Application SPA configuration update complete" -ForegroundColor Green } From 026cda2745792fd053c6d4c16e4c20a0e4bd9940 Mon Sep 17 00:00:00 2001 From: picccard <7443949+picccard@users.noreply.github.com> Date: Thu, 11 Jul 2024 21:53:16 +0200 Subject: [PATCH 3/6] indent with 4 spaces --- deploy/deploy.ps1 | 2200 ++++++++++++++++++++++----------------------- 1 file changed, 1100 insertions(+), 1100 deletions(-) diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index 194bbcf..7874cad 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -12,1267 +12,1267 @@ # Intake and set global parameters [CmdletBinding(DefaultParameterSetName = 'AppContainer')] param( - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'FunctionContainer')] - [string] - $Location, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppsOnly')] - [string] - $UIAppName = 'ipam-ui-app', - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppsOnly')] - [string] - $EngineAppName = 'ipam-engine-app', - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [ValidateLength(1, 7)] - [string] - $NamePrefix, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [hashtable] - $Tags, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'AppsOnly')] - [switch] - $AppsOnly, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppsOnly')] - [switch] - $DisableUI, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppsOnly')] - [ValidatePattern('^([\x21-\x7E]*)(?" $field -ForegroundColor Red - } + if (-not $Function) { + $validators.Remove('functionName') + $validators.Remove('functionPlanName') + $validators.Remove('storageAccountName') + } - foreach ($field in $missingFields) { - Write-Host "ERROR: Missing Field ->" $field -ForegroundColor Red - } + if ($Function) { + $validators.Remove('appServiceName') + $validators.Remove('appServicePlanName') + } - Write-Host "ERROR: Please refer to the 'Naming Rules and Restrictions for Azure Resources'" -ForegroundColor Red - Write-Host "ERROR: " -ForegroundColor Red -NoNewline - Write-Host "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules" -ForegroundColor Yellow - Write-Host + $attrApp = [System.Management.Automation.ParameterAttribute]::new() + $attrApp.ParameterSetName = "App" + $attrApp.Mandatory = $false + + $attrAppContainer = [System.Management.Automation.ParameterAttribute]::new() + $attrAppContainer.ParameterSetName = "AppContainer" + $attrAppContainer.Mandatory = $false + + $attrFunction = [System.Management.Automation.ParameterAttribute]::new() + $attrFunction.ParameterSetName = "Function" + $attrFunction.Mandatory = $false + + $attrFunctionContainer = [System.Management.Automation.ParameterAttribute]::new() + $attrFunctionContainer.ParameterSetName = "FunctionContainer" + $attrFunctionContainer.Mandatory = $false + + $attrValidation = [System.Management.Automation.ValidateScriptAttribute]::new({ + $invalidFields = [System.Collections.ArrayList]@() + $missingFields = [System.Collections.ArrayList]@() + + foreach ($validator in $validators.GetEnumerator()) { + if ($_.ContainsKey($validator.Name)) { + if (-not ($_[$validator.Name] -match $validator.Value)) { + $invalidFields.Add($validator.Name) | Out-Null + } + } + else { + $missingFields.Add($validator.Name) | Out-Null + } + } + + if ($invalidFields -or $missingFields) { + $deploymentType = $PrivateAcr ? "'$($PSCmdlet.ParameterSetName) w/ Private ACR'" : $PSCmdlet.ParameterSetName + Write-Host + Write-Host "ERROR: Missing or improperly formatted field(s) in 'ResourceNames' parameter for deploment type $deploymentType" -ForegroundColor Red + + foreach ($field in $invalidFields) { + Write-Host "ERROR: Invalid Field ->" $field -ForegroundColor Red + } + + foreach ($field in $missingFields) { + Write-Host "ERROR: Missing Field ->" $field -ForegroundColor Red + } + + Write-Host "ERROR: Please refer to the 'Naming Rules and Restrictions for Azure Resources'" -ForegroundColor Red + Write-Host "ERROR: " -ForegroundColor Red -NoNewline + Write-Host "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules" -ForegroundColor Yellow + Write-Host + + throw [System.ArgumentException]::New("One of the required resource names is missing or invalid.") + } + + return -not ($invalidFields -or $missingFields) + }) + + $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() + $attributeCollection.Add($attrApp) + $attributeCollection.Add($attrAppContainer) + $attributeCollection.Add($attrFunction) + $attributeCollection.Add($attrFunctionContainer) + $attributeCollection.Add($attrValidation) + + $param = [System.Management.Automation.RuntimeDefinedParameter]::new('ResourceNames', [hashtable], $attributeCollection) + $paramDict = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() + $paramDict.Add('ResourceNames', $param) + + return $paramDict +} +begin { + $ResourceNames = $PSBoundParameters['ResourceNames'] +} +process { + $AZURE_ENV_MAP = @{ + AzureCloud = "AZURE_PUBLIC" + AzureUSGovernment = "AZURE_US_GOV" + USSec = "AZURE_US_GOV_SECRET" + AzureGermanCloud = "AZURE_GERMANY" + AzureChinaCloud = "AZURE_CHINA" + } - throw [System.ArgumentException]::New("One of the required resource names is missing or invalid.") - } + # Root Directory + $ROOT_DIR = (Get-Item $($MyInvocation.MyCommand.Path)).Directory.Parent.FullName - return -not ($invalidFields -or $missingFields) - }) + # Minimum Required Azure CLI Version + $MIN_AZ_CLI_VER = [System.Version]'2.35.0' - $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() - $attributeCollection.Add($attrApp) - $attributeCollection.Add($attrAppContainer) - $attributeCollection.Add($attrFunction) - $attributeCollection.Add($attrFunctionContainer) - $attributeCollection.Add($attrValidation) + # Check for Debug Flag + $DEBUG_MODE = [bool]$PSCmdlet.MyInvocation.BoundParameters[“Debug”].IsPresent - $param = [System.Management.Automation.RuntimeDefinedParameter]::new('ResourceNames', [hashtable], $attributeCollection) - $paramDict = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() - $paramDict.Add('ResourceNames', $param) + # Set preference variables + $ErrorActionPreference = "Stop" + $DebugPreference = 'SilentlyContinue' + $ProgressPreference = 'SilentlyContinue' - return $paramDict -} -begin { - $ResourceNames = $PSBoundParameters['ResourceNames'] -} -process { - $AZURE_ENV_MAP = @{ - AzureCloud = "AZURE_PUBLIC" - AzureUSGovernment = "AZURE_US_GOV" - USSec = "AZURE_US_GOV_SECRET" - AzureGermanCloud = "AZURE_GERMANY" - AzureChinaCloud = "AZURE_CHINA" - } - - # Root Directory - $ROOT_DIR = (Get-Item $($MyInvocation.MyCommand.Path)).Directory.Parent.FullName - - # Minimum Required Azure CLI Version - $MIN_AZ_CLI_VER = [System.Version]'2.35.0' - - # Check for Debug Flag - $DEBUG_MODE = [bool]$PSCmdlet.MyInvocation.BoundParameters[“Debug”].IsPresent - - # Set preference variables - $ErrorActionPreference = "Stop" - $DebugPreference = 'SilentlyContinue' - $ProgressPreference = 'SilentlyContinue' - - # Hide Azure PowerShell SDK Warnings - $Env:SuppressAzurePowerShellBreakingChangeWarnings = $true - - # Hide Azure PowerShell SDK & Azure CLI Survey Prompts - $Env:AzSurveyMessage = $false - $Env:AZURE_CORE_SURVEY_MESSAGE = $false - - # Set Log File Location - $logPath = Join-Path -Path $ROOT_DIR -ChildPath "logs" - New-Item -ItemType Directory -Path $logpath -Force | Out-Null - - $debugLog = Join-Path -Path $logPath -ChildPath "debug_$(get-date -format `"yyyyMMddhhmmsstt`").log" - $errorLog = Join-Path -Path $logPath -ChildPath "error_$(get-date -format `"yyyyMMddhhmmsstt`").log" - $transcriptLog = Join-Path -Path $logPath -ChildPath "deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log" - - $debugSetting = $DEBUG_MODE ? 'Continue' : 'SilentlyContinue' - - $containerBuildError = $false - $deploymentSuccess = $false - - Start-Transcript -Path $transcriptLog | Out-Null - - Function Test-Location { - Param( - [Parameter(Mandatory = $true)] - [string]$Location - ) - - $validLocations = Get-AzLocation | Select-Object -ExpandProperty Location - - return $validLocations.Contains($Location) - } - - Function Get-BuildLogs { - Param( - [Parameter(Mandatory = $true)] - [string]$SubscriptionId, - [Parameter(Mandatory = $true)] - [string]$ResourceGroupName, - [Parameter(Mandatory = $true)] - [string]$RegistryName, - [Parameter(Mandatory = $true)] - [string]$BuildId - ) - - $msArmMap = @{ - AZURE_PUBLIC = "management.azure.com" - AZURE_US_GOV = "management.usgovcloudapi.net" - AZURE_US_GOV_SECRET = "management.azure.microsoft.scloud" - AZURE_GERMANY = "management.microsoftazure.de" - AZURE_CHINA = "management.chinacloudapi.cn" - }; - - $accessToken = (Get-AzAccessToken).Token | ConvertTo-SecureString -AsPlainText - - $response = Invoke-RestMethod ` - -Method POST ` - -Uri "https://$($msArmMap[$AzureCloud])/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.ContainerRegistry/registries/$RegistryName/runs/$BuildId/listLogSasUrl?api-version=2019-04-01" ` - -Authentication Bearer ` - -Token $accessToken - - $logLink = $response.logLink - - $logs = Invoke-RestMethod ` - -Method GET ` - -Uri $logLink - - return $logs - } - - Function Deploy-IPAMApplications { - Param( - [Parameter(Mandatory = $false)] - [string]$EngineAppName = 'ipam-engine-app', - [Parameter(Mandatory = $false)] - [string]$UIAppName = 'ipam-ui-app', - [Parameter(Mandatory = $true)] - [string]$MgmtGroupId, - [Parameter(Mandatory = $true)] - [string]$AzureCloud, - [Parameter(Mandatory = $false)] - [bool]$DisableUI = $false - ) - - $uiResourceAccess = [System.Collections.ArrayList]@( - @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; # Microsoft Graph - ResourceAccess = @( - @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; # openid - Type = "Scope" - }, - @{ - Id = "14dad69e-099b-42c9-810b-d002981feec1"; # profile - Type = "Scope" - }, - @{ - Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; # offline_access - Type = "Scope" - }, - @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; # User.Read - Type = "Scope" - }, - @{ - Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; # Directory.Read.All - Type = "Scope" - } + # Hide Azure PowerShell SDK Warnings + $Env:SuppressAzurePowerShellBreakingChangeWarnings = $true + + # Hide Azure PowerShell SDK & Azure CLI Survey Prompts + $Env:AzSurveyMessage = $false + $Env:AZURE_CORE_SURVEY_MESSAGE = $false + + # Set Log File Location + $logPath = Join-Path -Path $ROOT_DIR -ChildPath "logs" + New-Item -ItemType Directory -Path $logpath -Force | Out-Null + + $debugLog = Join-Path -Path $logPath -ChildPath "debug_$(get-date -format `"yyyyMMddhhmmsstt`").log" + $errorLog = Join-Path -Path $logPath -ChildPath "error_$(get-date -format `"yyyyMMddhhmmsstt`").log" + $transcriptLog = Join-Path -Path $logPath -ChildPath "deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log" + + $debugSetting = $DEBUG_MODE ? 'Continue' : 'SilentlyContinue' + + $containerBuildError = $false + $deploymentSuccess = $false + + Start-Transcript -Path $transcriptLog | Out-Null + + Function Test-Location { + Param( + [Parameter(Mandatory = $true)] + [string]$Location ) - } - ) - # Create IPAM UI Application (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Creating Azure IPAM UI Application" -ForegroundColor Green + $validLocations = Get-AzLocation | Select-Object -ExpandProperty Location - $uiApp = New-AzADApplication ` - -DisplayName $UiAppName ` - -SPARedirectUri "https://replace-this-value.azurewebsites.net" + return $validLocations.Contains($Location) } - $engineResourceMap = @{ - "AZURE_PUBLIC" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation - } - "AZURE_US_GOV" = @{ - ResourceAppId = "40a69793-8fe6-4db1-9591-dbc5c57b17d8" # Azure Service Management - ResourceAccessIds = @("8eb49ffc-05ac-454c-9027-8648349217dd", "e59ee429-1fb1-4054-b99f-f542e8dc9b95") # user_impersonation - } - "AZURE_US_GOV_SECRET" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation - } - "AZURE_GERMANY" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation - } - "AZURE_CHINA" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation - } - } + Function Get-BuildLogs { + Param( + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + [Parameter(Mandatory = $true)] + [string]$RegistryName, + [Parameter(Mandatory = $true)] + [string]$BuildId + ) - $engineResourceAppId = $engineResourceMap[$AzureCloud].ResourceAppId - $engineResourceAccess = [System.Collections.ArrayList]@() + $msArmMap = @{ + AZURE_PUBLIC = "management.azure.com" + AZURE_US_GOV = "management.usgovcloudapi.net" + AZURE_US_GOV_SECRET = "management.azure.microsoft.scloud" + AZURE_GERMANY = "management.microsoftazure.de" + AZURE_CHINA = "management.chinacloudapi.cn" + }; - foreach ($engineAccessId in $engineResourceMap[$AzureCloud].ResourceAccessIds) { - $access = @{ - Id = $engineAccessId - Type = "Scope" - } + $accessToken = (Get-AzAccessToken).Token | ConvertTo-SecureString -AsPlainText - $engineResourceAccess.Add($access) | Out-Null - } + $response = Invoke-RestMethod ` + -Method POST ` + -Uri "https://$($msArmMap[$AzureCloud])/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.ContainerRegistry/registries/$RegistryName/runs/$BuildId/listLogSasUrl?api-version=2019-04-01" ` + -Authentication Bearer ` + -Token $accessToken - $engineResourceAccessList = [System.Collections.ArrayList]@( - @{ - ResourceAppId = $engineResourceAppId - ResourceAccess = $engineResourceAccess - } - ) - - $engineApiGuid = New-Guid - - $knownClientApplication = @( - $uiApp.AppId - ) - - $engineApiSettings = @{ - Oauth2PermissionScope = @( - @{ - AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." - AdminConsentDisplayName = "Access IPAM Engine API" - Id = $engineApiGuid - IsEnabled = $true - Type = "User" - UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." - UserConsentDisplayName = "Access IPAM Engine API" - Value = "access_as_user" - } - ) - PreAuthorizedApplication = @( # Allow Azure PowerShell/CLI to obtain access tokens - @{ - AppId = "1950a258-227b-4e31-a9cf-717495945fc2" # Azure PowerShell - DelegatedPermissionId = @( $engineApiGuid ) - }, - @{ - AppId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" # Azure CLI - DelegatedPermissionId = @( $engineApiGuid ) - } - ) - RequestedAccessTokenVersion = 2 - } + $logLink = $response.logLink + + $logs = Invoke-RestMethod ` + -Method GET ` + -Uri $logLink - # Add the UI App as a Known Client App (If DisableUI not specified) - if (-not $DisableUI) { - $engineApiSettings.Add("KnownClientApplication", $knownClientApplication) + return $logs } - Write-Host "INFO: Creating Azure IPAM Engine Application" -ForegroundColor Green + Function Deploy-IPAMApplications { + Param( + [Parameter(Mandatory = $false)] + [string]$EngineAppName = 'ipam-engine-app', + [Parameter(Mandatory = $false)] + [string]$UIAppName = 'ipam-ui-app', + [Parameter(Mandatory = $true)] + [string]$MgmtGroupId, + [Parameter(Mandatory = $true)] + [string]$AzureCloud, + [Parameter(Mandatory = $false)] + [bool]$DisableUI = $false + ) - # Create IPAM Engine Application - $engineApp = New-AzADApplication ` - -DisplayName $EngineAppName ` - -Api $engineApiSettings ` - -RequiredResourceAccess $engineResourceAccessList + $uiResourceAccess = [System.Collections.ArrayList]@( + @{ + ResourceAppId = "00000003-0000-0000-c000-000000000000"; # Microsoft Graph + ResourceAccess = @( + @{ + Id = "37f7f235-527c-4136-accd-4a02d197296e"; # openid + Type = "Scope" + }, + @{ + Id = "14dad69e-099b-42c9-810b-d002981feec1"; # profile + Type = "Scope" + }, + @{ + Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; # offline_access + Type = "Scope" + }, + @{ + Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; # User.Read + Type = "Scope" + }, + @{ + Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; # Directory.Read.All + Type = "Scope" + } + ) + } + ) - Write-Host "INFO: Updating Azure IPAM Engine API Endpoint" -ForegroundColor Green + # Create IPAM UI Application (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Creating Azure IPAM UI Application" -ForegroundColor Green - # Update IPAM Engine API Endpoint - Update-AzADApplication -ApplicationId $engineApp.AppId -IdentifierUri "api://$($engineApp.AppId)" + $uiApp = New-AzADApplication ` + -DisplayName $UiAppName ` + -SPARedirectUri "https://replace-this-value.azurewebsites.net" + } - $uiEngineApiAccess = @{ - ResourceAppId = $engineApp.AppId - ResourceAccess = @( - @{ - Id = $engineApiGuid - Type = "Scope" + $engineResourceMap = @{ + "AZURE_PUBLIC" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation + } + "AZURE_US_GOV" = @{ + ResourceAppId = "40a69793-8fe6-4db1-9591-dbc5c57b17d8" # Azure Service Management + ResourceAccessIds = @("8eb49ffc-05ac-454c-9027-8648349217dd", "e59ee429-1fb1-4054-b99f-f542e8dc9b95") # user_impersonation + } + "AZURE_US_GOV_SECRET" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation + } + "AZURE_GERMANY" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation + } + "AZURE_CHINA" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation + } } - ) - } - $uiResourceAccess.Add($uiEngineApiAccess) | Out-Null + $engineResourceAppId = $engineResourceMap[$AzureCloud].ResourceAppId + $engineResourceAccess = [System.Collections.ArrayList]@() - # Update IPAM UI Application Resource Access (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Updating Azure IPAM UI Application Resource Access" -ForegroundColor Green + foreach ($engineAccessId in $engineResourceMap[$AzureCloud].ResourceAccessIds) { + $access = @{ + Id = $engineAccessId + Type = "Scope" + } - Update-AzADApplication -ApplicationId $uiApp.AppId -RequiredResourceAccess $uiResourceAccess + $engineResourceAccess.Add($access) | Out-Null + } - $uiObject = Get-AzADApplication -ApplicationId $uiApp.AppId - } + $engineResourceAccessList = [System.Collections.ArrayList]@( + @{ + ResourceAppId = $engineResourceAppId + ResourceAccess = $engineResourceAccess + } + ) - $engineObject = Get-AzADApplication -ApplicationId $engineApp.AppId + $engineApiGuid = New-Guid - # Create IPAM UI Service Principal (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Creating Azure IPAM UI Service Principal" -ForegroundColor Green + $knownClientApplication = @( + $uiApp.AppId + ) - New-AzADServicePrincipal -ApplicationObject $uiObject | Out-Null - } + $engineApiSettings = @{ + Oauth2PermissionScope = @( + @{ + AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." + AdminConsentDisplayName = "Access IPAM Engine API" + Id = $engineApiGuid + IsEnabled = $true + Type = "User" + UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." + UserConsentDisplayName = "Access IPAM Engine API" + Value = "access_as_user" + } + ) + PreAuthorizedApplication = @( # Allow Azure PowerShell/CLI to obtain access tokens + @{ + AppId = "1950a258-227b-4e31-a9cf-717495945fc2" # Azure PowerShell + DelegatedPermissionId = @( $engineApiGuid ) + }, + @{ + AppId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" # Azure CLI + DelegatedPermissionId = @( $engineApiGuid ) + } + ) + RequestedAccessTokenVersion = 2 + } - $scope = "/providers/Microsoft.Management/managementGroups/$MgmtGroupId" + # Add the UI App as a Known Client App (If DisableUI not specified) + if (-not $DisableUI) { + $engineApiSettings.Add("KnownClientApplication", $knownClientApplication) + } - Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor Green + Write-Host "INFO: Creating Azure IPAM Engine Application" -ForegroundColor Green - # Create IPAM Engine Service Principal - New-AzADServicePrincipal -ApplicationObject $engineObject ` - -Role "Reader" ` - -Scope $scope ` - | Out-Null + # Create IPAM Engine Application + $engineApp = New-AzADApplication ` + -DisplayName $EngineAppName ` + -Api $engineApiSettings ` + -RequiredResourceAccess $engineResourceAccessList - Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor Green + Write-Host "INFO: Updating Azure IPAM Engine API Endpoint" -ForegroundColor Green - # Create IPAM Engine Secret - $engineSecret = New-AzADAppCredential -ApplicationObject $engineObject -StartDate (Get-Date) -EndDate (Get-Date).AddYears(2) + # Update IPAM Engine API Endpoint + Update-AzADApplication -ApplicationId $engineApp.AppId -IdentifierUri "api://$($engineApp.AppId)" - if (-not $DisableUI) { - Write-Host "INFO: Azure IPAM Engine & UI Applications/Service Principals created successfully" -ForegroundColor Green - } - else { - Write-Host "INFO: Azure IPAM Engine Application/Service Principal created successfully" -ForegroundColor Green - } + $uiEngineApiAccess = @{ + ResourceAppId = $engineApp.AppId + ResourceAccess = @( + @{ + Id = $engineApiGuid + Type = "Scope" + } + ) + } - $appDetails = @{ - EngineAppId = $engineApp.AppId - EngineSecret = $engineSecret.SecretText - } + $uiResourceAccess.Add($uiEngineApiAccess) | Out-Null - # Add UI AppID to AppDetails (If DisableUI not specified) - if (-not $DisableUI) { - $appDetails.Add("UIAppId", $uiApp.AppId) - } + # Update IPAM UI Application Resource Access (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Updating Azure IPAM UI Application Resource Access" -ForegroundColor Green - return $appDetails - } - - Function Grant-AdminConsent { - Param( - [Parameter(Mandatory = $false)] - [string]$UIAppId = [GUID]::Empty, - [Parameter(Mandatory = $true)] - [string]$EngineAppId, - [Parameter(Mandatory = $true)] - [string]$AzureCloud, - [Parameter(Mandatory = $false)] - [bool]$DisableUI = $false - ) - - $msGraphMap = @{ - AZURE_PUBLIC = @{ - Endpoint = "graph.microsoft.com" - Environment = "Global" - } - AZURE_US_GOV = @{ - Endpoint = "graph.microsoft.us" - Environment = "USGov" - } - AZURE_US_GOV_SECRET = @{ - Endpoint = "graph.cloudapi.microsoft.scloud" - Environment = "USSec" - } - AZURE_GERMANY = @{ - Endpoint = "graph.microsoft.de" - Environment = "Germany" - } - AZURE_CHINA = @{ - Endpoint = "microsoftgraph.chinacloudapi.cn" - Environment = "China" - } - }; - - $uiGraphScopes = [System.Collections.ArrayList]@( - @{ - scopeId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph - scopes = " openid profile offline_access User.Read Directory.Read.All" - } - ) - - $engineGraphScopes = [System.Collections.ArrayList]@( - @{ - scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - scopes = "user_impersonation" - } - ) - - # Get Microsoft Graph Access Token - $accesstoken = (Get-AzAccessToken -Resource "https://$($msGraphMap[$AzureCloud].Endpoint)/").Token - - # Switch Access Token to SecureString if Graph Version is 2.x - $graphVersion = [System.Version](Get-InstalledModule -Name Microsoft.Graph | Sort-Object -Property Version | Select-Object -Last 1).Version ` - ?? (Get-Module -Name Microsoft.Graph | Sort-Object -Property Version | Select-Object -Last 1).Version - - if ($graphVersion.Major -gt 1) { - $accesstoken = ConvertTo-SecureString $accesstoken -AsPlainText -Force - } + Update-AzADApplication -ApplicationId $uiApp.AppId -RequiredResourceAccess $uiResourceAccess - Write-Host "INFO: Logging in to Microsoft Graph" -ForegroundColor Green + $uiObject = Get-AzADApplication -ApplicationId $uiApp.AppId + } - # Connect to Microsoft Graph - Connect-MgGraph -Environment $msGraphMap[$AzureCloud].Environment -AccessToken $accesstoken | Out-Null + $engineObject = Get-AzADApplication -ApplicationId $engineApp.AppId - # Fetch Azure IPAM UI Service Principal (If DisableUI not specified) - if (-not $DisableUI) { - $uiSpn = Get-AzADServicePrincipal ` - -ApplicationId $UIAppId - } + # Create IPAM UI Service Principal (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Creating Azure IPAM UI Service Principal" -ForegroundColor Green - # Fetch Azure IPAM Engine Service Principal - $engineSpn = Get-AzADServicePrincipal ` - -ApplicationId $EngineAppId + New-AzADServicePrincipal -ApplicationObject $uiObject | Out-Null + } - # Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI application" -ForegroundColor Green + $scope = "/providers/Microsoft.Management/managementGroups/$MgmtGroupId" - foreach ($scope in $uiGraphScopes) { - $msGraphId = Get-AzADServicePrincipal ` - -ApplicationId $scope.scopeId + Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor Green - New-MgOauth2PermissionGrant ` - -ResourceId $msGraphId.Id ` - -Scope $scope.scopes ` - -ClientId $uiSpn.Id ` - -ConsentType AllPrincipals ` + # Create IPAM Engine Service Principal + New-AzADServicePrincipal -ApplicationObject $engineObject ` + -Role "Reader" ` + -Scope $scope ` | Out-Null - } - Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor Green - } - - # Grant admin consent to the IPAM UI application for exposed API from the IPAM Engine application (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Granting admin consent to the IPAM UI application for exposed API from the IPAM Engine application" -ForegroundColor Green + Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor Green - New-MgOauth2PermissionGrant ` - -ResourceId $engineSpn.Id ` - -Scope "access_as_user" ` - -ClientId $uiSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null + # Create IPAM Engine Secret + $engineSecret = New-AzADAppCredential -ApplicationObject $engineObject -StartDate (Get-Date) -EndDate (Get-Date).AddYears(2) - Write-Host "INFO: Admin consent for IPAM Engine exposed API granted successfully" -ForegroundColor Green - } + if (-not $DisableUI) { + Write-Host "INFO: Azure IPAM Engine & UI Applications/Service Principals created successfully" -ForegroundColor Green + } + else { + Write-Host "INFO: Azure IPAM Engine Application/Service Principal created successfully" -ForegroundColor Green + } - Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine application" -ForegroundColor Green + $appDetails = @{ + EngineAppId = $engineApp.AppId + EngineSecret = $engineSecret.SecretText + } - # Grant admin consent for Azure Service Management API permissions assigned to IPAM Engine application - foreach ($scope in $engineGraphScopes) { - $msGraphId = Get-AzADServicePrincipal ` - -ApplicationId $scope.scopeId + # Add UI AppID to AppDetails (If DisableUI not specified) + if (-not $DisableUI) { + $appDetails.Add("UIAppId", $uiApp.AppId) + } - New-MgOauth2PermissionGrant ` - -ResourceId $msGraphId.Id ` - -Scope $scope.scopes ` - -ClientId $engineSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null + return $appDetails } - Write-Host "INFO: Admin consent for Azure Service Management API permissions granted successfully" -ForegroundColor Green - } - - Function Save-Parameters { - Param( - [Parameter(Mandatory = $false)] - [string]$UIAppId = [GUID]::Empty, - [Parameter(Mandatory = $true)] - [string]$EngineAppId, - [Parameter(Mandatory = $true)] - [string]$EngineSecret, - [Parameter(Mandatory = $false)] - [bool]$DisableUI = $false - ) - - Write-Host "INFO: Populating Bicep parameter file for infrastructure deployment" -ForegroundColor Green - - # Retrieve JSON object from sample parameter file - $parametersObject = Get-Content main.parameters.example.json | ConvertFrom-Json - - # Update Parameter Values - $parametersObject.parameters.engineAppId.value = $EngineAppId - $parametersObject.parameters.engineAppSecret.value = $EngineSecret - - if (-not $DisableUI) { - $parametersObject.parameters.uiAppId.value = $UIAppId - $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property uiAppId, engineAppId, engineAppSecret - } - else { - $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property engineAppId, engineAppSecret - } + Function Grant-AdminConsent { + Param( + [Parameter(Mandatory = $false)] + [string]$UIAppId = [GUID]::Empty, + [Parameter(Mandatory = $true)] + [string]$EngineAppId, + [Parameter(Mandatory = $true)] + [string]$AzureCloud, + [Parameter(Mandatory = $false)] + [bool]$DisableUI = $false + ) - # Output updated parameter file for Bicep deployment - $parametersObject | ConvertTo-Json -Depth 4 | Out-File -FilePath main.parameters.json + $msGraphMap = @{ + AZURE_PUBLIC = @{ + Endpoint = "graph.microsoft.com" + Environment = "Global" + } + AZURE_US_GOV = @{ + Endpoint = "graph.microsoft.us" + Environment = "USGov" + } + AZURE_US_GOV_SECRET = @{ + Endpoint = "graph.cloudapi.microsoft.scloud" + Environment = "USSec" + } + AZURE_GERMANY = @{ + Endpoint = "graph.microsoft.de" + Environment = "Germany" + } + AZURE_CHINA = @{ + Endpoint = "microsoftgraph.chinacloudapi.cn" + Environment = "China" + } + }; + + $uiGraphScopes = [System.Collections.ArrayList]@( + @{ + scopeId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph + scopes = " openid profile offline_access User.Read Directory.Read.All" + } + ) - Write-Host "INFO: Bicep parameter file populated successfully" -ForegroundColor Green - } + $engineGraphScopes = [System.Collections.ArrayList]@( + @{ + scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + scopes = "user_impersonation" + } + ) - Function Import-Parameters { - Param( - [Parameter(Mandatory = $true)] - [System.IO.FileInfo]$ParameterFile - ) + # Get Microsoft Graph Access Token + $accesstoken = (Get-AzAccessToken -Resource "https://$($msGraphMap[$AzureCloud].Endpoint)/").Token - Write-Host "INFO: Importing values from Bicep parameters file" -ForegroundColor Green + # Switch Access Token to SecureString if Graph Version is 2.x + $graphVersion = [System.Version](Get-InstalledModule -Name Microsoft.Graph | Sort-Object -Property Version | Select-Object -Last 1).Version ` + ?? (Get-Module -Name Microsoft.Graph | Sort-Object -Property Version | Select-Object -Last 1).Version - # Retrieve JSON object from sample parameter file - $parametersObject = Get-Content $ParameterFile | ConvertFrom-Json + if ($graphVersion.Major -gt 1) { + $accesstoken = ConvertTo-SecureString $accesstoken -AsPlainText -Force + } - # Read Values from Parameters - $UIAppId = $parametersObject.parameters.uiAppId.value ?? [GUID]::Empty - $EngineAppId = $parametersObject.parameters.engineAppId.value - $EngineSecret = $parametersObject.parameters.engineAppSecret.value - $script:DisableUI = ($UIAppId -eq [GUID]::Empty) ? $true : $false + Write-Host "INFO: Logging in to Microsoft Graph" -ForegroundColor Green - if ((-not $EngineAppId) -or (-not $EngineSecret)) { - Write-Host "ERROR: Missing required parameters from Bicep parameter file" -ForegroundColor Red - Write-Host "ERROR: Please ensure the following parameters are present in the Bicep parameter file" -ForegroundColor Red - Write-Host "ERROR: Required: [engineAppId, engineAppSecret]" -ForegroundColor Red - Write-Host "" - Write-Host "ERROR: Please refer to the deployment documentation for more information" -ForegroundColor Red - Write-Host "ERROR: " -ForegroundColor Red -NoNewline - Write-Host "https://azure.github.io/ipam/#/deployment/README" -ForegroundColor Yellow - Write-Host "" + # Connect to Microsoft Graph + Connect-MgGraph -Environment $msGraphMap[$AzureCloud].Environment -AccessToken $accesstoken | Out-Null - throw [System.ArgumentException]::New("One of the required parameters are missing or invalid.") - } + # Fetch Azure IPAM UI Service Principal (If DisableUI not specified) + if (-not $DisableUI) { + $uiSpn = Get-AzADServicePrincipal ` + -ApplicationId $UIAppId + } - # $deployType = $script:AsFunction ? 'Function' : 'Full' + # Fetch Azure IPAM Engine Service Principal + $engineSpn = Get-AzADServicePrincipal ` + -ApplicationId $EngineAppId - Write-Host "INFO: Successfully import Bicep parameter values for deployment" -ForegroundColor Green + # Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI application" -ForegroundColor Green - $appDetails = @{ - UIAppId = $UIAppId - EngineAppId = $EngineAppId - EngineSecret = $EngineSecret - } + foreach ($scope in $uiGraphScopes) { + $msGraphId = Get-AzADServicePrincipal ` + -ApplicationId $scope.scopeId - return $appDetails - } - - Function Deploy-Bicep { - Param( - [Parameter(Mandatory = $false)] - [string]$UIAppId = [GUID]::Empty, - [Parameter(Mandatory = $true)] - [string]$EngineAppId, - [Parameter(Mandatory = $true)] - [string]$EngineSecret, - [Parameter(Mandatory = $false)] - [string]$NamePrefix, - [Parameter(Mandatory = $false)] - [string]$AzureCloud, - [Parameter(Mandatory = $false)] - [bool]$Function, - [Parameter(Mandatory = $false)] - [bool]$Native, - [Parameter(Mandatory = $false)] - [bool]$PrivateAcr, - [Parameter(Mandatory = $false)] - [hashtable]$Tags, - [Parameter(Mandatory = $false)] - [hashtable]$ResourceNames - ) - - Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor Green - - # Instantiate deployment parameter object - $deploymentParameters = @{ - engineAppId = $EngineAppId - engineAppSecret = $EngineSecret - uiAppId = $UiAppId - } + New-MgOauth2PermissionGrant ` + -ResourceId $msGraphId.Id ` + -Scope $scope.scopes ` + -ClientId $uiSpn.Id ` + -ConsentType AllPrincipals ` + | Out-Null + } - if ($NamePrefix) { - $deploymentParameters.Add('namePrefix', $NamePrefix) - } + Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor Green + } - if ($AzureCloud) { - $deploymentParameters.Add('azureCloud', $AzureCloud) - } + # Grant admin consent to the IPAM UI application for exposed API from the IPAM Engine application (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Granting admin consent to the IPAM UI application for exposed API from the IPAM Engine application" -ForegroundColor Green - if ($Function) { - $deploymentParameters.Add('deployAsFunc', $Function) - } + New-MgOauth2PermissionGrant ` + -ResourceId $engineSpn.Id ` + -Scope "access_as_user" ` + -ClientId $uiSpn.Id ` + -ConsentType AllPrincipals ` + | Out-Null - if (-not $Native) { - $deploymentParameters.Add('deployAsContainer', !$Native) - } + Write-Host "INFO: Admin consent for IPAM Engine exposed API granted successfully" -ForegroundColor Green + } - if ($PrivateAcr) { - $deploymentParameters.Add('privateAcr', $PrivateAcr) - } + Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine application" -ForegroundColor Green - if ($Tags) { - $deploymentParameters.Add('tags', $Tags) - } + # Grant admin consent for Azure Service Management API permissions assigned to IPAM Engine application + foreach ($scope in $engineGraphScopes) { + $msGraphId = Get-AzADServicePrincipal ` + -ApplicationId $scope.scopeId - if ($ResourceNames) { - $deploymentParameters.Add('resourceNames', $ResourceNames) + New-MgOauth2PermissionGrant ` + -ResourceId $msGraphId.Id ` + -Scope $scope.scopes ` + -ClientId $engineSpn.Id ` + -ConsentType AllPrincipals ` + | Out-Null + } + + Write-Host "INFO: Admin consent for Azure Service Management API permissions granted successfully" -ForegroundColor Green } - $DebugPreference = $debugSetting + Function Save-Parameters { + Param( + [Parameter(Mandatory = $false)] + [string]$UIAppId = [GUID]::Empty, + [Parameter(Mandatory = $true)] + [string]$EngineAppId, + [Parameter(Mandatory = $true)] + [string]$EngineSecret, + [Parameter(Mandatory = $false)] + [bool]$DisableUI = $false + ) - # Deploy IPAM bicep template - $deployment = & { - New-AzSubscriptionDeployment ` - -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` - -Location $location ` - -TemplateFile main.bicep ` - -TemplateParameterObject $deploymentParameters ` - 5>$($DEBUG_MODE ? $debugLog : $null) - } + Write-Host "INFO: Populating Bicep parameter file for infrastructure deployment" -ForegroundColor Green - $DebugPreference = 'SilentlyContinue' + # Retrieve JSON object from sample parameter file + $parametersObject = Get-Content main.parameters.example.json | ConvertFrom-Json - Write-Host "INFO: IPAM bicep templates deployed successfully" -ForegroundColor Green + # Update Parameter Values + $parametersObject.parameters.engineAppId.value = $EngineAppId + $parametersObject.parameters.engineAppSecret.value = $EngineSecret - return $deployment - } + if (-not $DisableUI) { + $parametersObject.parameters.uiAppId.value = $UIAppId + $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property uiAppId, engineAppId, engineAppSecret + } + else { + $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property engineAppId, engineAppSecret + } - Function Publish-ZipFile { - Param( - [Parameter(Mandatory = $true)] - [string]$AppName, - [Parameter(Mandatory = $true)] - [string]$ResourceGroupName, - [Parameter(Mandatory = $false)] - [switch]$UseAPI - ) + # Output updated parameter file for Bicep deployment + $parametersObject | ConvertTo-Json -Depth 4 | Out-File -FilePath main.parameters.json - if ($UseAPI) { - Write-Host "INFO: Using Kudu API for ZIP Deploy" -ForegroundColor Green + Write-Host "INFO: Bicep parameter file populated successfully" -ForegroundColor Green } - $zipPath = Join-Path -Path $ROOT_DIR -ChildPath 'assets' -AdditionalChildPath "ipam.zip" + Function Import-Parameters { + Param( + [Parameter(Mandatory = $true)] + [System.IO.FileInfo]$ParameterFile + ) - $publishRetries = 3 - $publishSuccess = $False + Write-Host "INFO: Importing values from Bicep parameters file" -ForegroundColor Green - if ($UseAPI) { - $accessToken = (Get-AzAccessToken).Token - $zipContents = Get-Item -Path $zipPath + # Retrieve JSON object from sample parameter file + $parametersObject = Get-Content $ParameterFile | ConvertFrom-Json - $publishProfile = Get-AzWebAppPublishingProfile -Name $AppName -ResourceGroupName $ResourceGroupName - $zipUrl = ([System.uri]($publishProfile | Select-Xml -XPath "//publishProfile[@publishMethod='ZipDeploy']" | Select-Object -ExpandProperty Node).publishUrl).Scheme - } + # Read Values from Parameters + $UIAppId = $parametersObject.parameters.uiAppId.value ?? [GUID]::Empty + $EngineAppId = $parametersObject.parameters.engineAppId.value + $EngineSecret = $parametersObject.parameters.engineAppSecret.value + $script:DisableUI = ($UIAppId -eq [GUID]::Empty) ? $true : $false - do { - try { - if (-not $UseAPI) { - Publish-AzWebApp ` - -Name $AppName ` - -ResourceGroupName $ResourceGroupName ` - -ArchivePath $zipPath ` - -Restart ` - -Force ` - | Out-Null - } - else { - Invoke-RestMethod ` - -Uri "https://${zipUrl}/api/zipdeploy" ` - -Method Post ` - -ContentType "multipart/form-data" ` - -Headers @{ "Authorization" = "Bearer $accessToken" } ` - -Form @{ file = $zipContents } ` - -StatusCodeVariable statusCode ` - | Out-Null - - if ($statusCode -ne 200) { - throw [System.Exception]::New("Error while uploading ZIP Deploy via Kudu API! ($statusCode)") - } - } + if ((-not $EngineAppId) -or (-not $EngineSecret)) { + Write-Host "ERROR: Missing required parameters from Bicep parameter file" -ForegroundColor Red + Write-Host "ERROR: Please ensure the following parameters are present in the Bicep parameter file" -ForegroundColor Red + Write-Host "ERROR: Required: [engineAppId, engineAppSecret]" -ForegroundColor Red + Write-Host "" + Write-Host "ERROR: Please refer to the deployment documentation for more information" -ForegroundColor Red + Write-Host "ERROR: " -ForegroundColor Red -NoNewline + Write-Host "https://azure.github.io/ipam/#/deployment/README" -ForegroundColor Yellow + Write-Host "" - $publishSuccess = $True - Write-Host "INFO: ZIP Deploy archive successfully uploaded" -ForegroundColor Green - } - catch { - if ($publishRetries -gt 0) { - Write-Host "WARNING: Problem while uploading ZIP Deploy archive! Retrying..." -ForegroundColor Yellow - $publishRetries-- + throw [System.ArgumentException]::New("One of the required parameters are missing or invalid.") } - else { - Write-Host "ERROR: Unable to upload ZIP Deploy archive!" -ForegroundColor Red - throw $_ - } - } - } while ($publishSuccess -eq $False -and $publishRetries -ge 0) - } - - Function Update-UIApplication { - Param( - [Parameter(Mandatory = $true)] - [string]$UIAppId, - [Parameter(Mandatory = $true)] - [string]$Endpoint - ) - Write-Host "INFO: Updating UI Application with SPA configuration" -ForegroundColor Green + # $deployType = $script:AsFunction ? 'Function' : 'Full' - $appServiceEndpoint = "https://$Endpoint" + Write-Host "INFO: Successfully import Bicep parameter values for deployment" -ForegroundColor Green - # Update UI Application with single-page application configuration - Update-AzADApplication -ApplicationId $UIAppId -SPARedirectUri $appServiceEndpoint + $appDetails = @{ + UIAppId = $UIAppId + EngineAppId = $EngineAppId + EngineSecret = $EngineSecret + } - Write-Host "INFO: UI Application SPA configuration update complete" -ForegroundColor Green - } + return $appDetails + } - # Main Deployment Script Section - Write-Host + Function Deploy-Bicep { + Param( + [Parameter(Mandatory = $false)] + [string]$UIAppId = [GUID]::Empty, + [Parameter(Mandatory = $true)] + [string]$EngineAppId, + [Parameter(Mandatory = $true)] + [string]$EngineSecret, + [Parameter(Mandatory = $false)] + [string]$NamePrefix, + [Parameter(Mandatory = $false)] + [string]$AzureCloud, + [Parameter(Mandatory = $false)] + [bool]$Function, + [Parameter(Mandatory = $false)] + [bool]$Native, + [Parameter(Mandatory = $false)] + [bool]$PrivateAcr, + [Parameter(Mandatory = $false)] + [hashtable]$Tags, + [Parameter(Mandatory = $false)] + [hashtable]$ResourceNames + ) - if ($DEBUG_MODE) { - Write-Host "DEBUG: Debug Mode Enabled" -ForegroundColor Gray - } + Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor Green - Write-Host "NOTE: IPAM Deployment Type: $($PSCmdlet.ParameterSetName)" -ForegroundColor Magenta + # Instantiate deployment parameter object + $deploymentParameters = @{ + engineAppId = $EngineAppId + engineAppSecret = $EngineSecret + uiAppId = $UiAppId + } - try { - if ($PrivateAcr) { - Write-Host "INFO: PrivateACR flag set, verifying minimum Azure CLI version" -ForegroundColor Green + if ($NamePrefix) { + $deploymentParameters.Add('namePrefix', $NamePrefix) + } - # Verify Minimum Azure CLI Version - $azureCliVer = [System.Version](az version | ConvertFrom-Json).'azure-cli' + if ($AzureCloud) { + $deploymentParameters.Add('azureCloud', $AzureCloud) + } - if ($azureCliVer -lt $MIN_AZ_CLI_VER) { - Write-Host "ERROR: Azure CLI must be version $MIN_AZ_CLI_VER or greater!" -ForegroundColor Red - exit - } + if ($Function) { + $deploymentParameters.Add('deployAsFunc', $Function) + } - Write-Host "INFO: PrivateACR flag set, verifying Azure PowerShell and Azure CLI contexts match" -ForegroundColor Green + if (-not $Native) { + $deploymentParameters.Add('deployAsContainer', !$Native) + } - # Verify Azure PowerShell and Azure CLI Contexts Match - $azureCliContext = $(az account show | ConvertFrom-Json) 2>$null + if ($PrivateAcr) { + $deploymentParameters.Add('privateAcr', $PrivateAcr) + } - if (-not $azureCliContext) { - Write-Host "ERROR: Azure CLI not logged in or no subscription has been selected!" -ForegroundColor Red - exit - } + if ($Tags) { + $deploymentParameters.Add('tags', $Tags) + } - $azureCliSub = $azureCliContext.id - $azurePowerShellSub = (Get-AzContext).Subscription.Id + if ($ResourceNames) { + $deploymentParameters.Add('resourceNames', $ResourceNames) + } - if ($azurePowerShellSub -ne $azureCliSub) { - Write-Host "ERROR: Azure PowerShell and Azure CLI must be set to the same context!" -ForegroundColor Red - exit - } - } + $DebugPreference = $debugSetting - if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer', 'AppsOnly')) { - # Fetch Tenant ID (If Required) - if ($MgmtGroupId) { - Write-Host "NOTE: Management Group ID Specified" -ForegroundColor Magenta - } - else { - Write-Host "INFO: Fetching Tenant ID from Azure PowerShell SDK" -ForegroundColor Green - $script:MgmtGroupId = (Get-AzContext).Tenant.Id - } + # Deploy IPAM bicep template + $deployment = & { + New-AzSubscriptionDeployment ` + -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` + -Location $location ` + -TemplateFile main.bicep ` + -TemplateParameterObject $deploymentParameters ` + 5>$($DEBUG_MODE ? $debugLog : $null) + } - Write-Host "INFO: Fetching Azure Cloud type from Azure PowerShell SDK" -ForegroundColor Green + $DebugPreference = 'SilentlyContinue' - # Fetch Azure Cloud Type - $azureCloud = $AZURE_ENV_MAP[(Get-AzContext).Environment.Name] + Write-Host "INFO: IPAM bicep templates deployed successfully" -ForegroundColor Green - # Verify Azure Cloud Type is Supported - if (-not [bool]$azureCloud) { - Write-Host "ERROR: Azure Cloud type is not currently supported!" -ForegroundColor Red - Write-Host - Write-Host "Azure Cloud type: " -ForegroundColor Yellow -NoNewline - Write-Host (Get-AzContext).Environment.Name -ForegroundColor Cyan - exit - } + return $deployment } - if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer')) { - Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor Green + Function Publish-ZipFile { + Param( + [Parameter(Mandatory = $true)] + [string]$AppName, + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + [Parameter(Mandatory = $false)] + [switch]$UseAPI + ) - # Validate Azure Region - if (Test-Location -Location $Location) { - Write-Host "INFO: Azure Region validated successfully" -ForegroundColor Green - } - else { - Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor Red - Write-Host - Write-Host "Azure Region: " -ForegroundColor Yellow -NoNewline - Write-Host $Location -ForegroundColor Cyan - exit - } - } + if ($UseAPI) { + Write-Host "INFO: Using Kudu API for ZIP Deploy" -ForegroundColor Green + } - if (-not $ParameterFile) { - $appDetails = Deploy-IPAMApplications ` - -UIAppName $UIAppName ` - -EngineAppName $EngineAppName ` - -MgmtGroupId $MgmtGroupId ` - -AzureCloud $azureCloud ` - -DisableUI $DisableUI + $zipPath = Join-Path -Path $ROOT_DIR -ChildPath 'assets' -AdditionalChildPath "ipam.zip" - $consentDetails = @{ - EngineAppId = $appDetails.EngineAppId - } + $publishRetries = 3 + $publishSuccess = $False - if (-not $DisableUI) { - $consentDetails.Add("UIAppId", $appDetails.UIAppId) - } + if ($UseAPI) { + $accessToken = (Get-AzAccessToken).Token + $zipContents = Get-Item -Path $zipPath - Grant-AdminConsent @consentDetails -AzureCloud $azureCloud -DisableUI $DisableUI - } + $publishProfile = Get-AzWebAppPublishingProfile -Name $AppName -ResourceGroupName $ResourceGroupName + $zipUrl = ([System.uri]($publishProfile | Select-Xml -XPath "//publishProfile[@publishMethod='ZipDeploy']" | Select-Object -ExpandProperty Node).publishUrl).Scheme + } - if ($PSCmdlet.ParameterSetName -in ('AppsOnly')) { - Save-Parameters @appDetails -DisableUI $DisableUI + do { + try { + if (-not $UseAPI) { + Publish-AzWebApp ` + -Name $AppName ` + -ResourceGroupName $ResourceGroupName ` + -ArchivePath $zipPath ` + -Restart ` + -Force ` + | Out-Null + } + else { + Invoke-RestMethod ` + -Uri "https://${zipUrl}/api/zipdeploy" ` + -Method Post ` + -ContentType "multipart/form-data" ` + -Headers @{ "Authorization" = "Bearer $accessToken" } ` + -Form @{ file = $zipContents } ` + -StatusCodeVariable statusCode ` + | Out-Null + + if ($statusCode -ne 200) { + throw [System.Exception]::New("Error while uploading ZIP Deploy via Kudu API! ($statusCode)") + } + } + + $publishSuccess = $True + Write-Host "INFO: ZIP Deploy archive successfully uploaded" -ForegroundColor Green + } + catch { + if ($publishRetries -gt 0) { + Write-Host "WARNING: Problem while uploading ZIP Deploy archive! Retrying..." -ForegroundColor Yellow + $publishRetries-- + } + else { + Write-Host "ERROR: Unable to upload ZIP Deploy archive!" -ForegroundColor Red + throw $_ + } + } + } while ($publishSuccess -eq $False -and $publishRetries -ge 0) } - if ($ParameterFile) { - $appDetails = Import-Parameters ` - -ParameterFile $ParameterFile - } + Function Update-UIApplication { + Param( + [Parameter(Mandatory = $true)] + [string]$UIAppId, + [Parameter(Mandatory = $true)] + [string]$Endpoint + ) - if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer')) { - $deployment = Deploy-Bicep @appDetails ` - -NamePrefix $NamePrefix ` - -AzureCloud $azureCloud ` - -PrivateAcr $PrivateAcr ` - -Function $Function ` - -Native $Native ` - -Tags $Tags ` - -ResourceNames $ResourceNames - } + Write-Host "INFO: Updating UI Application with SPA configuration" -ForegroundColor Green + + $appServiceEndpoint = "https://$Endpoint" + + # Update UI Application with single-page application configuration + Update-AzADApplication -ApplicationId $UIAppId -SPARedirectUri $appServiceEndpoint - if (($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and (-not $DisableUI)) { - Update-UIApplication ` - -UIAppId $appDetails.UIAppId ` - -Endpoint $deployment.Outputs["appServiceHostName"].Value + Write-Host "INFO: UI Application SPA configuration update complete" -ForegroundColor Green } - if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) { - Write-Host "INFO: Uploading ZIP Deploy archive..." -ForegroundColor Green + # Main Deployment Script Section + Write-Host - try { - Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value - } - catch { - Write-Host "SWITCH: Retrying ZIP Deploy with Kudu API..." -ForegroundColor Blue - Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -UseAPI - } + if ($DEBUG_MODE) { + Write-Host "DEBUG: Debug Mode Enabled" -ForegroundColor Gray } - if ($PSCmdlet.ParameterSetName -in ('AppContainer', 'FunctionContainer') -and $PrivateAcr) { - Write-Host "INFO: Building and pushing container image to Azure Container Registry" -ForegroundColor Green - - $containerMap = @{ - Debian = @{ - Extension = 'deb' - Port = 8080 - Images = @{ - Build = 'node:18-slim' - Serve = 'python:3.9-slim' - } - } - RHEL = @{ - Extension = 'rhel' - Port = 8080 - Images = @{ - Build = 'registry.access.redhat.com/ubi8/nodejs-18' - Serve = 'registry.access.redhat.com/ubi8/python-39' - } - } - } + Write-Host "NOTE: IPAM Deployment Type: $($PSCmdlet.ParameterSetName)" -ForegroundColor Magenta - $dockerFile = 'Dockerfile.' + $containerMap[$ContainerType].Extension - $dockerFilePath = Join-Path -Path $ROOT_DIR -ChildPath $dockerFile - $dockerFileFunc = Join-Path -Path $ROOT_DIR -ChildPath 'Dockerfile.func' + try { + if ($PrivateAcr) { + Write-Host "INFO: PrivateACR flag set, verifying minimum Azure CLI version" -ForegroundColor Green - if ($Function) { - Write-Host "INFO: Building Function container..." -ForegroundColor Green + # Verify Minimum Azure CLI Version + $azureCliVer = [System.Version](az version | ConvertFrom-Json).'azure-cli' - $funcBuildOutput = $( - az acr build -r $deployment.Outputs["acrName"].Value ` - -t ipamfunc:latest ` - -f $dockerFileFunc $ROOT_DIR ` - --no-logs - ) *>&1 + if ($azureCliVer -lt $MIN_AZ_CLI_VER) { + Write-Host "ERROR: Azure CLI must be version $MIN_AZ_CLI_VER or greater!" -ForegroundColor Red + exit + } - if ($LASTEXITCODE -ne 0) { - Write-Host "ERROR: Container build process failed, fetching error logs..." -ForegroundColor Red + Write-Host "INFO: PrivateACR flag set, verifying Azure PowerShell and Azure CLI contexts match" -ForegroundColor Green - $buildId = [regex]::Matches($funcBuildOutput, "(?<=Queued a build with ID: )[\w]*").Value.Trim() + # Verify Azure PowerShell and Azure CLI Contexts Match + $azureCliContext = $(az account show | ConvertFrom-Json) 2>$null - $buildLogs = Get-BuildLogs ` - -SubscriptionId $deployment.Outputs["subscriptionId"].Value ` - -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value ` - -RegistryName $deployment.Outputs["acrName"].Value ` - -BuildId $buildId + if (-not $azureCliContext) { + Write-Host "ERROR: Azure CLI not logged in or no subscription has been selected!" -ForegroundColor Red + exit + } - $buildLogs | Out-File -FilePath $errorLog -Append + $azureCliSub = $azureCliContext.id + $azurePowerShellSub = (Get-AzContext).Subscription.Id - $script:containerBuildError = $true + if ($azurePowerShellSub -ne $azureCliSub) { + Write-Host "ERROR: Azure PowerShell and Azure CLI must be set to the same context!" -ForegroundColor Red + exit + } } - else { - Write-Host "INFO: Function container image build and push completed successfully" -ForegroundColor Green + + if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer', 'AppsOnly')) { + # Fetch Tenant ID (If Required) + if ($MgmtGroupId) { + Write-Host "NOTE: Management Group ID Specified" -ForegroundColor Magenta + } + else { + Write-Host "INFO: Fetching Tenant ID from Azure PowerShell SDK" -ForegroundColor Green + $script:MgmtGroupId = (Get-AzContext).Tenant.Id + } + + Write-Host "INFO: Fetching Azure Cloud type from Azure PowerShell SDK" -ForegroundColor Green + + # Fetch Azure Cloud Type + $azureCloud = $AZURE_ENV_MAP[(Get-AzContext).Environment.Name] + + # Verify Azure Cloud Type is Supported + if (-not [bool]$azureCloud) { + Write-Host "ERROR: Azure Cloud type is not currently supported!" -ForegroundColor Red + Write-Host + Write-Host "Azure Cloud type: " -ForegroundColor Yellow -NoNewline + Write-Host (Get-AzContext).Environment.Name -ForegroundColor Cyan + exit + } } - Write-Host "INFO: Restarting Function App" -ForegroundColor Green + if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer')) { + Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor Green + + # Validate Azure Region + if (Test-Location -Location $Location) { + Write-Host "INFO: Azure Region validated successfully" -ForegroundColor Green + } + else { + Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor Red + Write-Host + Write-Host "Azure Region: " -ForegroundColor Yellow -NoNewline + Write-Host $Location -ForegroundColor Cyan + exit + } + } - Restart-AzFunctionApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -Force | Out-Null - } - else { - Write-Host "INFO: Building App container ($ContainerType)..." -ForegroundColor Green + if (-not $ParameterFile) { + $appDetails = Deploy-IPAMApplications ` + -UIAppName $UIAppName ` + -EngineAppName $EngineAppName ` + -MgmtGroupId $MgmtGroupId ` + -AzureCloud $azureCloud ` + -DisableUI $DisableUI - $appBuildOutput = $( - az acr build -r $deployment.Outputs["acrName"].Value ` - -t ipam:latest ` - -f $dockerFilePath $ROOT_DIR ` - --build-arg PORT=$($containerMap[$ContainerType].Port) ` - --build-arg BUILD_IMAGE=$($containerMap[$ContainerType].Images.Build) ` - --build-arg SERVE_IMAGE=$($containerMap[$ContainerType].Images.Serve) ` - --no-logs - ) *>&1 + $consentDetails = @{ + EngineAppId = $appDetails.EngineAppId + } - if ($LASTEXITCODE -ne 0) { - Write-Host "ERROR: Container build process failed, fetching error logs..." -ForegroundColor Red + if (-not $DisableUI) { + $consentDetails.Add("UIAppId", $appDetails.UIAppId) + } - $buildId = [regex]::Matches($appBuildOutput, "(?<=Queued a build with ID: )[\w]*").Value.Trim() + Grant-AdminConsent @consentDetails -AzureCloud $azureCloud -DisableUI $DisableUI + } - $buildLogs = Get-BuildLogs ` - -SubscriptionId $deployment.Outputs["subscriptionId"].Value ` - -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value ` - -RegistryName $deployment.Outputs["acrName"].Value ` - -BuildId $buildId + if ($PSCmdlet.ParameterSetName -in ('AppsOnly')) { + Save-Parameters @appDetails -DisableUI $DisableUI + } - $buildLogs | Out-File -FilePath $errorLog -Append + if ($ParameterFile) { + $appDetails = Import-Parameters ` + -ParameterFile $ParameterFile + } - $script:containerBuildError = $true + if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer')) { + $deployment = Deploy-Bicep @appDetails ` + -NamePrefix $NamePrefix ` + -AzureCloud $azureCloud ` + -PrivateAcr $PrivateAcr ` + -Function $Function ` + -Native $Native ` + -Tags $Tags ` + -ResourceNames $ResourceNames } - else { - Write-Host "INFO: App container image build and push completed successfully" -ForegroundColor Green + + if (($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and (-not $DisableUI)) { + Update-UIApplication ` + -UIAppId $appDetails.UIAppId ` + -Endpoint $deployment.Outputs["appServiceHostName"].Value } - if (-not $containerBuildError) { - Write-Host "INFO: Restarting App Service" -ForegroundColor Green + if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) { + Write-Host "INFO: Uploading ZIP Deploy archive..." -ForegroundColor Green - Restart-AzWebApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value | Out-Null + try { + Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value + } + catch { + Write-Host "SWITCH: Retrying ZIP Deploy with Kudu API..." -ForegroundColor Blue + Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -UseAPI + } } - } - } - if (-not $containerBuildError) { - Write-Host "INFO: Azure IPAM Solution deployed successfully" -ForegroundColor Green - } - else { - Write-Host "WARNING: Azure IPAM Solution deployed with errors, see logs for details!" -ForegroundColor Yellow - Write-Host "Run Log: $transcriptLog" -ForegroundColor Yellow - Write-Host "Error Log: $errorLog" -ForegroundColor Yellow - } + if ($PSCmdlet.ParameterSetName -in ('AppContainer', 'FunctionContainer') -and $PrivateAcr) { + Write-Host "INFO: Building and pushing container image to Azure Container Registry" -ForegroundColor Green + + $containerMap = @{ + Debian = @{ + Extension = 'deb' + Port = 8080 + Images = @{ + Build = 'node:18-slim' + Serve = 'python:3.9-slim' + } + } + RHEL = @{ + Extension = 'rhel' + Port = 8080 + Images = @{ + Build = 'registry.access.redhat.com/ubi8/nodejs-18' + Serve = 'registry.access.redhat.com/ubi8/python-39' + } + } + } + + $dockerFile = 'Dockerfile.' + $containerMap[$ContainerType].Extension + $dockerFilePath = Join-Path -Path $ROOT_DIR -ChildPath $dockerFile + $dockerFileFunc = Join-Path -Path $ROOT_DIR -ChildPath 'Dockerfile.func' + + if ($Function) { + Write-Host "INFO: Building Function container..." -ForegroundColor Green + + $funcBuildOutput = $( + az acr build -r $deployment.Outputs["acrName"].Value ` + -t ipamfunc:latest ` + -f $dockerFileFunc $ROOT_DIR ` + --no-logs + ) *>&1 + + if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Container build process failed, fetching error logs..." -ForegroundColor Red + + $buildId = [regex]::Matches($funcBuildOutput, "(?<=Queued a build with ID: )[\w]*").Value.Trim() + + $buildLogs = Get-BuildLogs ` + -SubscriptionId $deployment.Outputs["subscriptionId"].Value ` + -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value ` + -RegistryName $deployment.Outputs["acrName"].Value ` + -BuildId $buildId + + $buildLogs | Out-File -FilePath $errorLog -Append + + $script:containerBuildError = $true + } + else { + Write-Host "INFO: Function container image build and push completed successfully" -ForegroundColor Green + } + + Write-Host "INFO: Restarting Function App" -ForegroundColor Green + + Restart-AzFunctionApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -Force | Out-Null + } + else { + Write-Host "INFO: Building App container ($ContainerType)..." -ForegroundColor Green + + $appBuildOutput = $( + az acr build -r $deployment.Outputs["acrName"].Value ` + -t ipam:latest ` + -f $dockerFilePath $ROOT_DIR ` + --build-arg PORT=$($containerMap[$ContainerType].Port) ` + --build-arg BUILD_IMAGE=$($containerMap[$ContainerType].Images.Build) ` + --build-arg SERVE_IMAGE=$($containerMap[$ContainerType].Images.Serve) ` + --no-logs + ) *>&1 + + if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Container build process failed, fetching error logs..." -ForegroundColor Red + + $buildId = [regex]::Matches($appBuildOutput, "(?<=Queued a build with ID: )[\w]*").Value.Trim() + + $buildLogs = Get-BuildLogs ` + -SubscriptionId $deployment.Outputs["subscriptionId"].Value ` + -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value ` + -RegistryName $deployment.Outputs["acrName"].Value ` + -BuildId $buildId + + $buildLogs | Out-File -FilePath $errorLog -Append + + $script:containerBuildError = $true + } + else { + Write-Host "INFO: App container image build and push completed successfully" -ForegroundColor Green + } + + if (-not $containerBuildError) { + Write-Host "INFO: Restarting App Service" -ForegroundColor Green + + Restart-AzWebApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value | Out-Null + } + } + } - if ($($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and (-not $DisableUI) -and $ParameterFile) { - $updateUrl = "https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Authentication/appId/$($appDetails.UIAppId)" - $updateAddr = "https://$($deployment.Outputs["appServiceHostName"].Value)" - - Write-Host - Write-Host "POST DEPLOYMENT TASKS:" -ForegroundColor Yellow - Write-Host "##############################################" -ForegroundColor Yellow - Write-Host "Navigate In Browser To:" -ForegroundColor Cyan - Write-Host $updateUrl -ForegroundColor White - Write-Host "Change 'Redirect URI' To:" -ForegroundColor Cyan - Write-Host $updateAddr -ForegroundColor White - Write-Host "##############################################" -ForegroundColor Yellow - } + if (-not $containerBuildError) { + Write-Host "INFO: Azure IPAM Solution deployed successfully" -ForegroundColor Green + } + else { + Write-Host "WARNING: Azure IPAM Solution deployed with errors, see logs for details!" -ForegroundColor Yellow + Write-Host "Run Log: $transcriptLog" -ForegroundColor Yellow + Write-Host "Error Log: $errorLog" -ForegroundColor Yellow + } - if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) { - Write-Host - Write-Host "NOTE: Please allow ~5 minutes for the Azure IPAM service to become available" -ForegroundColor Yellow - } + if ($($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and (-not $DisableUI) -and $ParameterFile) { + $updateUrl = "https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Authentication/appId/$($appDetails.UIAppId)" + $updateAddr = "https://$($deployment.Outputs["appServiceHostName"].Value)" + + Write-Host + Write-Host "POST DEPLOYMENT TASKS:" -ForegroundColor Yellow + Write-Host "##############################################" -ForegroundColor Yellow + Write-Host "Navigate In Browser To:" -ForegroundColor Cyan + Write-Host $updateUrl -ForegroundColor White + Write-Host "Change 'Redirect URI' To:" -ForegroundColor Cyan + Write-Host $updateAddr -ForegroundColor White + Write-Host "##############################################" -ForegroundColor Yellow + } - $script:deploymentSuccess = $true - } - catch { - $_ | Out-File -FilePath $errorLog -Append - Write-Host "ERROR: Unable to deploy Azure IPAM solution due to an exception, see logs for detailed information!" -ForegroundColor Red - Write-Host "Run Log: $transcriptLog" -ForegroundColor Red - Write-Host "Error Log: $errorLog" -ForegroundColor Red + if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) { + Write-Host + Write-Host "NOTE: Please allow ~5 minutes for the Azure IPAM service to become available" -ForegroundColor Yellow + } - if ($DEBUG_MODE) { - Write-Host "Debug Log: $debugLog" -ForegroundColor Red + $script:deploymentSuccess = $true } - } - finally { - Write-Host - Stop-Transcript | Out-Null - - if (($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and $script:deploymentSuccess) { - Write-Output "ipamURL=https://$($deployment.Outputs["appServiceHostName"].Value)" >> $Env:GITHUB_OUTPUT - Write-Output "ipamUIAppId=$($appDetails.UIAppId)" >> $Env:GITHUB_OUTPUT - Write-Output "ipamEngineAppId=$($appDetails.EngineAppId)" >> $Env:GITHUB_OUTPUT - Write-Output "ipamSuffix=$($deployment.Outputs["suffix"].Value)" >> $Env:GITHUB_OUTPUT - Write-Output "ipamResourceGroup=$($deployment.Outputs["resourceGroupName"].Value)" >> $Env:GITHUB_OUTPUT + catch { + $_ | Out-File -FilePath $errorLog -Append + Write-Host "ERROR: Unable to deploy Azure IPAM solution due to an exception, see logs for detailed information!" -ForegroundColor Red + Write-Host "Run Log: $transcriptLog" -ForegroundColor Red + Write-Host "Error Log: $errorLog" -ForegroundColor Red + + if ($DEBUG_MODE) { + Write-Host "Debug Log: $debugLog" -ForegroundColor Red + } } + finally { + Write-Host + Stop-Transcript | Out-Null + + if (($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and $script:deploymentSuccess) { + Write-Output "ipamURL=https://$($deployment.Outputs["appServiceHostName"].Value)" >> $Env:GITHUB_OUTPUT + Write-Output "ipamUIAppId=$($appDetails.UIAppId)" >> $Env:GITHUB_OUTPUT + Write-Output "ipamEngineAppId=$($appDetails.EngineAppId)" >> $Env:GITHUB_OUTPUT + Write-Output "ipamSuffix=$($deployment.Outputs["suffix"].Value)" >> $Env:GITHUB_OUTPUT + Write-Output "ipamResourceGroup=$($deployment.Outputs["resourceGroupName"].Value)" >> $Env:GITHUB_OUTPUT + } - exit - } + exit + } } From 306e3c4f713668445c2badc9a6af324afeb6617b Mon Sep 17 00:00:00 2001 From: picccard <7443949+picccard@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:01:19 +0200 Subject: [PATCH 4/6] change back to 2 spaces for indentation --- deploy/deploy.ps1 | 2200 ++++++++++++++++++++++----------------------- 1 file changed, 1100 insertions(+), 1100 deletions(-) diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index 7874cad..194bbcf 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -12,1267 +12,1267 @@ # Intake and set global parameters [CmdletBinding(DefaultParameterSetName = 'AppContainer')] param( - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'FunctionContainer')] - [string] - $Location, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppsOnly')] - [string] - $UIAppName = 'ipam-ui-app', - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppsOnly')] - [string] - $EngineAppName = 'ipam-engine-app', - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [ValidateLength(1, 7)] - [string] - $NamePrefix, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [hashtable] - $Tags, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $true, - ParameterSetName = 'AppsOnly')] - [switch] - $AppsOnly, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppsOnly')] - [switch] - $DisableUI, - - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'App')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'Function')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'FunctionContainer')] - [Parameter(ValueFromPipelineByPropertyName = $true, - Mandatory = $false, - ParameterSetName = 'AppsOnly')] - [ValidatePattern('^([\x21-\x7E]*)(?" $field -ForegroundColor Red - } - - foreach ($field in $missingFields) { - Write-Host "ERROR: Missing Field ->" $field -ForegroundColor Red - } - - Write-Host "ERROR: Please refer to the 'Naming Rules and Restrictions for Azure Resources'" -ForegroundColor Red - Write-Host "ERROR: " -ForegroundColor Red -NoNewline - Write-Host "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules" -ForegroundColor Yellow - Write-Host - - throw [System.ArgumentException]::New("One of the required resource names is missing or invalid.") - } - - return -not ($invalidFields -or $missingFields) - }) - - $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() - $attributeCollection.Add($attrApp) - $attributeCollection.Add($attrAppContainer) - $attributeCollection.Add($attrFunction) - $attributeCollection.Add($attrFunctionContainer) - $attributeCollection.Add($attrValidation) - - $param = [System.Management.Automation.RuntimeDefinedParameter]::new('ResourceNames', [hashtable], $attributeCollection) - $paramDict = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() - $paramDict.Add('ResourceNames', $param) - - return $paramDict -} -begin { - $ResourceNames = $PSBoundParameters['ResourceNames'] -} -process { - $AZURE_ENV_MAP = @{ - AzureCloud = "AZURE_PUBLIC" - AzureUSGovernment = "AZURE_US_GOV" - USSec = "AZURE_US_GOV_SECRET" - AzureGermanCloud = "AZURE_GERMANY" - AzureChinaCloud = "AZURE_CHINA" - } - - # Root Directory - $ROOT_DIR = (Get-Item $($MyInvocation.MyCommand.Path)).Directory.Parent.FullName - - # Minimum Required Azure CLI Version - $MIN_AZ_CLI_VER = [System.Version]'2.35.0' - - # Check for Debug Flag - $DEBUG_MODE = [bool]$PSCmdlet.MyInvocation.BoundParameters[“Debug”].IsPresent + $validators = @{ + functionName = '^(?=^.{2,59}$)([^-][\w-]*[^-])$' + appServiceName = '^(?=^.{2,59}$)([^-][\w-]*[^-])$' + functionPlanName = '^(?=^.{1,40}$)([\w-]*)$' + appServicePlanName = '^(?=^.{1,40}$)([\w-]*)$' + cosmosAccountName = '^(?=^.{3,44}$)([^-][a-z0-9-]*[^-])$' + cosmosContainerName = '^(?=^.{1,255}$)([^/\\#?]*)$' + cosmosDatabaseName = '^(?=^.{1,255}$)([^/\\#?]*)$' + keyVaultName = '^(?=^.{3,24}$)(?!.*--)([a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])$' + workspaceName = '^(?=^.{4,63}$)([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])$' + managedIdentityName = '^(?=^.{3,128}$)([a-zA-Z0-9][a-zA-Z0-9-_]*)$' + resourceGroupName = '^(?=^.{1,90}$)(?!.*\.$)([a-zA-Z0-9-_\.\p{L}\p{N}]*)$' + storageAccountName = '^(?=^.{3,24}$)([a-z0-9]*)$' + containerRegistryName = '^(?=^.{5,50}$)([a-zA-Z0-9]*)$' + } + + if (-not $PrivateAcr) { + $validators.Remove('containerRegistryName') + } + + if (-not $Function) { + $validators.Remove('functionName') + $validators.Remove('functionPlanName') + $validators.Remove('storageAccountName') + } + + if ($Function) { + $validators.Remove('appServiceName') + $validators.Remove('appServicePlanName') + } + + $attrApp = [System.Management.Automation.ParameterAttribute]::new() + $attrApp.ParameterSetName = "App" + $attrApp.Mandatory = $false + + $attrAppContainer = [System.Management.Automation.ParameterAttribute]::new() + $attrAppContainer.ParameterSetName = "AppContainer" + $attrAppContainer.Mandatory = $false + + $attrFunction = [System.Management.Automation.ParameterAttribute]::new() + $attrFunction.ParameterSetName = "Function" + $attrFunction.Mandatory = $false + + $attrFunctionContainer = [System.Management.Automation.ParameterAttribute]::new() + $attrFunctionContainer.ParameterSetName = "FunctionContainer" + $attrFunctionContainer.Mandatory = $false + + $attrValidation = [System.Management.Automation.ValidateScriptAttribute]::new({ + $invalidFields = [System.Collections.ArrayList]@() + $missingFields = [System.Collections.ArrayList]@() + + foreach ($validator in $validators.GetEnumerator()) { + if ($_.ContainsKey($validator.Name)) { + if (-not ($_[$validator.Name] -match $validator.Value)) { + $invalidFields.Add($validator.Name) | Out-Null + } + } + else { + $missingFields.Add($validator.Name) | Out-Null + } + } - # Set preference variables - $ErrorActionPreference = "Stop" - $DebugPreference = 'SilentlyContinue' - $ProgressPreference = 'SilentlyContinue' + if ($invalidFields -or $missingFields) { + $deploymentType = $PrivateAcr ? "'$($PSCmdlet.ParameterSetName) w/ Private ACR'" : $PSCmdlet.ParameterSetName + Write-Host + Write-Host "ERROR: Missing or improperly formatted field(s) in 'ResourceNames' parameter for deploment type $deploymentType" -ForegroundColor Red - # Hide Azure PowerShell SDK Warnings - $Env:SuppressAzurePowerShellBreakingChangeWarnings = $true + foreach ($field in $invalidFields) { + Write-Host "ERROR: Invalid Field ->" $field -ForegroundColor Red + } - # Hide Azure PowerShell SDK & Azure CLI Survey Prompts - $Env:AzSurveyMessage = $false - $Env:AZURE_CORE_SURVEY_MESSAGE = $false + foreach ($field in $missingFields) { + Write-Host "ERROR: Missing Field ->" $field -ForegroundColor Red + } - # Set Log File Location - $logPath = Join-Path -Path $ROOT_DIR -ChildPath "logs" - New-Item -ItemType Directory -Path $logpath -Force | Out-Null + Write-Host "ERROR: Please refer to the 'Naming Rules and Restrictions for Azure Resources'" -ForegroundColor Red + Write-Host "ERROR: " -ForegroundColor Red -NoNewline + Write-Host "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules" -ForegroundColor Yellow + Write-Host - $debugLog = Join-Path -Path $logPath -ChildPath "debug_$(get-date -format `"yyyyMMddhhmmsstt`").log" - $errorLog = Join-Path -Path $logPath -ChildPath "error_$(get-date -format `"yyyyMMddhhmmsstt`").log" - $transcriptLog = Join-Path -Path $logPath -ChildPath "deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log" + throw [System.ArgumentException]::New("One of the required resource names is missing or invalid.") + } - $debugSetting = $DEBUG_MODE ? 'Continue' : 'SilentlyContinue' + return -not ($invalidFields -or $missingFields) + }) - $containerBuildError = $false - $deploymentSuccess = $false + $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() + $attributeCollection.Add($attrApp) + $attributeCollection.Add($attrAppContainer) + $attributeCollection.Add($attrFunction) + $attributeCollection.Add($attrFunctionContainer) + $attributeCollection.Add($attrValidation) - Start-Transcript -Path $transcriptLog | Out-Null + $param = [System.Management.Automation.RuntimeDefinedParameter]::new('ResourceNames', [hashtable], $attributeCollection) + $paramDict = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() + $paramDict.Add('ResourceNames', $param) - Function Test-Location { - Param( - [Parameter(Mandatory = $true)] - [string]$Location + return $paramDict +} +begin { + $ResourceNames = $PSBoundParameters['ResourceNames'] +} +process { + $AZURE_ENV_MAP = @{ + AzureCloud = "AZURE_PUBLIC" + AzureUSGovernment = "AZURE_US_GOV" + USSec = "AZURE_US_GOV_SECRET" + AzureGermanCloud = "AZURE_GERMANY" + AzureChinaCloud = "AZURE_CHINA" + } + + # Root Directory + $ROOT_DIR = (Get-Item $($MyInvocation.MyCommand.Path)).Directory.Parent.FullName + + # Minimum Required Azure CLI Version + $MIN_AZ_CLI_VER = [System.Version]'2.35.0' + + # Check for Debug Flag + $DEBUG_MODE = [bool]$PSCmdlet.MyInvocation.BoundParameters[“Debug”].IsPresent + + # Set preference variables + $ErrorActionPreference = "Stop" + $DebugPreference = 'SilentlyContinue' + $ProgressPreference = 'SilentlyContinue' + + # Hide Azure PowerShell SDK Warnings + $Env:SuppressAzurePowerShellBreakingChangeWarnings = $true + + # Hide Azure PowerShell SDK & Azure CLI Survey Prompts + $Env:AzSurveyMessage = $false + $Env:AZURE_CORE_SURVEY_MESSAGE = $false + + # Set Log File Location + $logPath = Join-Path -Path $ROOT_DIR -ChildPath "logs" + New-Item -ItemType Directory -Path $logpath -Force | Out-Null + + $debugLog = Join-Path -Path $logPath -ChildPath "debug_$(get-date -format `"yyyyMMddhhmmsstt`").log" + $errorLog = Join-Path -Path $logPath -ChildPath "error_$(get-date -format `"yyyyMMddhhmmsstt`").log" + $transcriptLog = Join-Path -Path $logPath -ChildPath "deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log" + + $debugSetting = $DEBUG_MODE ? 'Continue' : 'SilentlyContinue' + + $containerBuildError = $false + $deploymentSuccess = $false + + Start-Transcript -Path $transcriptLog | Out-Null + + Function Test-Location { + Param( + [Parameter(Mandatory = $true)] + [string]$Location + ) + + $validLocations = Get-AzLocation | Select-Object -ExpandProperty Location + + return $validLocations.Contains($Location) + } + + Function Get-BuildLogs { + Param( + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + [Parameter(Mandatory = $true)] + [string]$RegistryName, + [Parameter(Mandatory = $true)] + [string]$BuildId + ) + + $msArmMap = @{ + AZURE_PUBLIC = "management.azure.com" + AZURE_US_GOV = "management.usgovcloudapi.net" + AZURE_US_GOV_SECRET = "management.azure.microsoft.scloud" + AZURE_GERMANY = "management.microsoftazure.de" + AZURE_CHINA = "management.chinacloudapi.cn" + }; + + $accessToken = (Get-AzAccessToken).Token | ConvertTo-SecureString -AsPlainText + + $response = Invoke-RestMethod ` + -Method POST ` + -Uri "https://$($msArmMap[$AzureCloud])/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.ContainerRegistry/registries/$RegistryName/runs/$BuildId/listLogSasUrl?api-version=2019-04-01" ` + -Authentication Bearer ` + -Token $accessToken + + $logLink = $response.logLink + + $logs = Invoke-RestMethod ` + -Method GET ` + -Uri $logLink + + return $logs + } + + Function Deploy-IPAMApplications { + Param( + [Parameter(Mandatory = $false)] + [string]$EngineAppName = 'ipam-engine-app', + [Parameter(Mandatory = $false)] + [string]$UIAppName = 'ipam-ui-app', + [Parameter(Mandatory = $true)] + [string]$MgmtGroupId, + [Parameter(Mandatory = $true)] + [string]$AzureCloud, + [Parameter(Mandatory = $false)] + [bool]$DisableUI = $false + ) + + $uiResourceAccess = [System.Collections.ArrayList]@( + @{ + ResourceAppId = "00000003-0000-0000-c000-000000000000"; # Microsoft Graph + ResourceAccess = @( + @{ + Id = "37f7f235-527c-4136-accd-4a02d197296e"; # openid + Type = "Scope" + }, + @{ + Id = "14dad69e-099b-42c9-810b-d002981feec1"; # profile + Type = "Scope" + }, + @{ + Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; # offline_access + Type = "Scope" + }, + @{ + Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; # User.Read + Type = "Scope" + }, + @{ + Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; # Directory.Read.All + Type = "Scope" + } ) + } + ) - $validLocations = Get-AzLocation | Select-Object -ExpandProperty Location + # Create IPAM UI Application (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Creating Azure IPAM UI Application" -ForegroundColor Green - return $validLocations.Contains($Location) + $uiApp = New-AzADApplication ` + -DisplayName $UiAppName ` + -SPARedirectUri "https://replace-this-value.azurewebsites.net" } - Function Get-BuildLogs { - Param( - [Parameter(Mandatory = $true)] - [string]$SubscriptionId, - [Parameter(Mandatory = $true)] - [string]$ResourceGroupName, - [Parameter(Mandatory = $true)] - [string]$RegistryName, - [Parameter(Mandatory = $true)] - [string]$BuildId - ) - - $msArmMap = @{ - AZURE_PUBLIC = "management.azure.com" - AZURE_US_GOV = "management.usgovcloudapi.net" - AZURE_US_GOV_SECRET = "management.azure.microsoft.scloud" - AZURE_GERMANY = "management.microsoftazure.de" - AZURE_CHINA = "management.chinacloudapi.cn" - }; + $engineResourceMap = @{ + "AZURE_PUBLIC" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation + } + "AZURE_US_GOV" = @{ + ResourceAppId = "40a69793-8fe6-4db1-9591-dbc5c57b17d8" # Azure Service Management + ResourceAccessIds = @("8eb49ffc-05ac-454c-9027-8648349217dd", "e59ee429-1fb1-4054-b99f-f542e8dc9b95") # user_impersonation + } + "AZURE_US_GOV_SECRET" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation + } + "AZURE_GERMANY" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation + } + "AZURE_CHINA" = @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation + } + } - $accessToken = (Get-AzAccessToken).Token | ConvertTo-SecureString -AsPlainText + $engineResourceAppId = $engineResourceMap[$AzureCloud].ResourceAppId + $engineResourceAccess = [System.Collections.ArrayList]@() - $response = Invoke-RestMethod ` - -Method POST ` - -Uri "https://$($msArmMap[$AzureCloud])/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.ContainerRegistry/registries/$RegistryName/runs/$BuildId/listLogSasUrl?api-version=2019-04-01" ` - -Authentication Bearer ` - -Token $accessToken + foreach ($engineAccessId in $engineResourceMap[$AzureCloud].ResourceAccessIds) { + $access = @{ + Id = $engineAccessId + Type = "Scope" + } - $logLink = $response.logLink + $engineResourceAccess.Add($access) | Out-Null + } - $logs = Invoke-RestMethod ` - -Method GET ` - -Uri $logLink + $engineResourceAccessList = [System.Collections.ArrayList]@( + @{ + ResourceAppId = $engineResourceAppId + ResourceAccess = $engineResourceAccess + } + ) + + $engineApiGuid = New-Guid + + $knownClientApplication = @( + $uiApp.AppId + ) + + $engineApiSettings = @{ + Oauth2PermissionScope = @( + @{ + AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." + AdminConsentDisplayName = "Access IPAM Engine API" + Id = $engineApiGuid + IsEnabled = $true + Type = "User" + UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." + UserConsentDisplayName = "Access IPAM Engine API" + Value = "access_as_user" + } + ) + PreAuthorizedApplication = @( # Allow Azure PowerShell/CLI to obtain access tokens + @{ + AppId = "1950a258-227b-4e31-a9cf-717495945fc2" # Azure PowerShell + DelegatedPermissionId = @( $engineApiGuid ) + }, + @{ + AppId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" # Azure CLI + DelegatedPermissionId = @( $engineApiGuid ) + } + ) + RequestedAccessTokenVersion = 2 + } - return $logs + # Add the UI App as a Known Client App (If DisableUI not specified) + if (-not $DisableUI) { + $engineApiSettings.Add("KnownClientApplication", $knownClientApplication) } - Function Deploy-IPAMApplications { - Param( - [Parameter(Mandatory = $false)] - [string]$EngineAppName = 'ipam-engine-app', - [Parameter(Mandatory = $false)] - [string]$UIAppName = 'ipam-ui-app', - [Parameter(Mandatory = $true)] - [string]$MgmtGroupId, - [Parameter(Mandatory = $true)] - [string]$AzureCloud, - [Parameter(Mandatory = $false)] - [bool]$DisableUI = $false - ) + Write-Host "INFO: Creating Azure IPAM Engine Application" -ForegroundColor Green - $uiResourceAccess = [System.Collections.ArrayList]@( - @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; # Microsoft Graph - ResourceAccess = @( - @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; # openid - Type = "Scope" - }, - @{ - Id = "14dad69e-099b-42c9-810b-d002981feec1"; # profile - Type = "Scope" - }, - @{ - Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; # offline_access - Type = "Scope" - }, - @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; # User.Read - Type = "Scope" - }, - @{ - Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; # Directory.Read.All - Type = "Scope" - } - ) - } - ) + # Create IPAM Engine Application + $engineApp = New-AzADApplication ` + -DisplayName $EngineAppName ` + -Api $engineApiSettings ` + -RequiredResourceAccess $engineResourceAccessList - # Create IPAM UI Application (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Creating Azure IPAM UI Application" -ForegroundColor Green + Write-Host "INFO: Updating Azure IPAM Engine API Endpoint" -ForegroundColor Green - $uiApp = New-AzADApplication ` - -DisplayName $UiAppName ` - -SPARedirectUri "https://replace-this-value.azurewebsites.net" - } + # Update IPAM Engine API Endpoint + Update-AzADApplication -ApplicationId $engineApp.AppId -IdentifierUri "api://$($engineApp.AppId)" - $engineResourceMap = @{ - "AZURE_PUBLIC" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation - } - "AZURE_US_GOV" = @{ - ResourceAppId = "40a69793-8fe6-4db1-9591-dbc5c57b17d8" # Azure Service Management - ResourceAccessIds = @("8eb49ffc-05ac-454c-9027-8648349217dd", "e59ee429-1fb1-4054-b99f-f542e8dc9b95") # user_impersonation - } - "AZURE_US_GOV_SECRET" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation - } - "AZURE_GERMANY" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation - } - "AZURE_CHINA" = @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation - } + $uiEngineApiAccess = @{ + ResourceAppId = $engineApp.AppId + ResourceAccess = @( + @{ + Id = $engineApiGuid + Type = "Scope" } + ) + } - $engineResourceAppId = $engineResourceMap[$AzureCloud].ResourceAppId - $engineResourceAccess = [System.Collections.ArrayList]@() + $uiResourceAccess.Add($uiEngineApiAccess) | Out-Null - foreach ($engineAccessId in $engineResourceMap[$AzureCloud].ResourceAccessIds) { - $access = @{ - Id = $engineAccessId - Type = "Scope" - } + # Update IPAM UI Application Resource Access (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Updating Azure IPAM UI Application Resource Access" -ForegroundColor Green - $engineResourceAccess.Add($access) | Out-Null - } + Update-AzADApplication -ApplicationId $uiApp.AppId -RequiredResourceAccess $uiResourceAccess - $engineResourceAccessList = [System.Collections.ArrayList]@( - @{ - ResourceAppId = $engineResourceAppId - ResourceAccess = $engineResourceAccess - } - ) + $uiObject = Get-AzADApplication -ApplicationId $uiApp.AppId + } - $engineApiGuid = New-Guid + $engineObject = Get-AzADApplication -ApplicationId $engineApp.AppId - $knownClientApplication = @( - $uiApp.AppId - ) + # Create IPAM UI Service Principal (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Creating Azure IPAM UI Service Principal" -ForegroundColor Green - $engineApiSettings = @{ - Oauth2PermissionScope = @( - @{ - AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." - AdminConsentDisplayName = "Access IPAM Engine API" - Id = $engineApiGuid - IsEnabled = $true - Type = "User" - UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." - UserConsentDisplayName = "Access IPAM Engine API" - Value = "access_as_user" - } - ) - PreAuthorizedApplication = @( # Allow Azure PowerShell/CLI to obtain access tokens - @{ - AppId = "1950a258-227b-4e31-a9cf-717495945fc2" # Azure PowerShell - DelegatedPermissionId = @( $engineApiGuid ) - }, - @{ - AppId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" # Azure CLI - DelegatedPermissionId = @( $engineApiGuid ) - } - ) - RequestedAccessTokenVersion = 2 - } + New-AzADServicePrincipal -ApplicationObject $uiObject | Out-Null + } - # Add the UI App as a Known Client App (If DisableUI not specified) - if (-not $DisableUI) { - $engineApiSettings.Add("KnownClientApplication", $knownClientApplication) - } + $scope = "/providers/Microsoft.Management/managementGroups/$MgmtGroupId" - Write-Host "INFO: Creating Azure IPAM Engine Application" -ForegroundColor Green + Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor Green - # Create IPAM Engine Application - $engineApp = New-AzADApplication ` - -DisplayName $EngineAppName ` - -Api $engineApiSettings ` - -RequiredResourceAccess $engineResourceAccessList + # Create IPAM Engine Service Principal + New-AzADServicePrincipal -ApplicationObject $engineObject ` + -Role "Reader" ` + -Scope $scope ` + | Out-Null - Write-Host "INFO: Updating Azure IPAM Engine API Endpoint" -ForegroundColor Green + Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor Green - # Update IPAM Engine API Endpoint - Update-AzADApplication -ApplicationId $engineApp.AppId -IdentifierUri "api://$($engineApp.AppId)" + # Create IPAM Engine Secret + $engineSecret = New-AzADAppCredential -ApplicationObject $engineObject -StartDate (Get-Date) -EndDate (Get-Date).AddYears(2) - $uiEngineApiAccess = @{ - ResourceAppId = $engineApp.AppId - ResourceAccess = @( - @{ - Id = $engineApiGuid - Type = "Scope" - } - ) - } + if (-not $DisableUI) { + Write-Host "INFO: Azure IPAM Engine & UI Applications/Service Principals created successfully" -ForegroundColor Green + } + else { + Write-Host "INFO: Azure IPAM Engine Application/Service Principal created successfully" -ForegroundColor Green + } - $uiResourceAccess.Add($uiEngineApiAccess) | Out-Null + $appDetails = @{ + EngineAppId = $engineApp.AppId + EngineSecret = $engineSecret.SecretText + } - # Update IPAM UI Application Resource Access (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Updating Azure IPAM UI Application Resource Access" -ForegroundColor Green + # Add UI AppID to AppDetails (If DisableUI not specified) + if (-not $DisableUI) { + $appDetails.Add("UIAppId", $uiApp.AppId) + } - Update-AzADApplication -ApplicationId $uiApp.AppId -RequiredResourceAccess $uiResourceAccess + return $appDetails + } + + Function Grant-AdminConsent { + Param( + [Parameter(Mandatory = $false)] + [string]$UIAppId = [GUID]::Empty, + [Parameter(Mandatory = $true)] + [string]$EngineAppId, + [Parameter(Mandatory = $true)] + [string]$AzureCloud, + [Parameter(Mandatory = $false)] + [bool]$DisableUI = $false + ) + + $msGraphMap = @{ + AZURE_PUBLIC = @{ + Endpoint = "graph.microsoft.com" + Environment = "Global" + } + AZURE_US_GOV = @{ + Endpoint = "graph.microsoft.us" + Environment = "USGov" + } + AZURE_US_GOV_SECRET = @{ + Endpoint = "graph.cloudapi.microsoft.scloud" + Environment = "USSec" + } + AZURE_GERMANY = @{ + Endpoint = "graph.microsoft.de" + Environment = "Germany" + } + AZURE_CHINA = @{ + Endpoint = "microsoftgraph.chinacloudapi.cn" + Environment = "China" + } + }; + + $uiGraphScopes = [System.Collections.ArrayList]@( + @{ + scopeId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph + scopes = " openid profile offline_access User.Read Directory.Read.All" + } + ) + + $engineGraphScopes = [System.Collections.ArrayList]@( + @{ + scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management + scopes = "user_impersonation" + } + ) + + # Get Microsoft Graph Access Token + $accesstoken = (Get-AzAccessToken -Resource "https://$($msGraphMap[$AzureCloud].Endpoint)/").Token + + # Switch Access Token to SecureString if Graph Version is 2.x + $graphVersion = [System.Version](Get-InstalledModule -Name Microsoft.Graph | Sort-Object -Property Version | Select-Object -Last 1).Version ` + ?? (Get-Module -Name Microsoft.Graph | Sort-Object -Property Version | Select-Object -Last 1).Version + + if ($graphVersion.Major -gt 1) { + $accesstoken = ConvertTo-SecureString $accesstoken -AsPlainText -Force + } - $uiObject = Get-AzADApplication -ApplicationId $uiApp.AppId - } + Write-Host "INFO: Logging in to Microsoft Graph" -ForegroundColor Green - $engineObject = Get-AzADApplication -ApplicationId $engineApp.AppId + # Connect to Microsoft Graph + Connect-MgGraph -Environment $msGraphMap[$AzureCloud].Environment -AccessToken $accesstoken | Out-Null - # Create IPAM UI Service Principal (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Creating Azure IPAM UI Service Principal" -ForegroundColor Green + # Fetch Azure IPAM UI Service Principal (If DisableUI not specified) + if (-not $DisableUI) { + $uiSpn = Get-AzADServicePrincipal ` + -ApplicationId $UIAppId + } - New-AzADServicePrincipal -ApplicationObject $uiObject | Out-Null - } + # Fetch Azure IPAM Engine Service Principal + $engineSpn = Get-AzADServicePrincipal ` + -ApplicationId $EngineAppId - $scope = "/providers/Microsoft.Management/managementGroups/$MgmtGroupId" + # Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI application" -ForegroundColor Green - Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor Green + foreach ($scope in $uiGraphScopes) { + $msGraphId = Get-AzADServicePrincipal ` + -ApplicationId $scope.scopeId - # Create IPAM Engine Service Principal - New-AzADServicePrincipal -ApplicationObject $engineObject ` - -Role "Reader" ` - -Scope $scope ` + New-MgOauth2PermissionGrant ` + -ResourceId $msGraphId.Id ` + -Scope $scope.scopes ` + -ClientId $uiSpn.Id ` + -ConsentType AllPrincipals ` | Out-Null + } - Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor Green + Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor Green + } - # Create IPAM Engine Secret - $engineSecret = New-AzADAppCredential -ApplicationObject $engineObject -StartDate (Get-Date) -EndDate (Get-Date).AddYears(2) + # Grant admin consent to the IPAM UI application for exposed API from the IPAM Engine application (If DisableUI not specified) + if (-not $DisableUI) { + Write-Host "INFO: Granting admin consent to the IPAM UI application for exposed API from the IPAM Engine application" -ForegroundColor Green - if (-not $DisableUI) { - Write-Host "INFO: Azure IPAM Engine & UI Applications/Service Principals created successfully" -ForegroundColor Green - } - else { - Write-Host "INFO: Azure IPAM Engine Application/Service Principal created successfully" -ForegroundColor Green - } + New-MgOauth2PermissionGrant ` + -ResourceId $engineSpn.Id ` + -Scope "access_as_user" ` + -ClientId $uiSpn.Id ` + -ConsentType AllPrincipals ` + | Out-Null - $appDetails = @{ - EngineAppId = $engineApp.AppId - EngineSecret = $engineSecret.SecretText - } + Write-Host "INFO: Admin consent for IPAM Engine exposed API granted successfully" -ForegroundColor Green + } - # Add UI AppID to AppDetails (If DisableUI not specified) - if (-not $DisableUI) { - $appDetails.Add("UIAppId", $uiApp.AppId) - } + Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine application" -ForegroundColor Green - return $appDetails - } + # Grant admin consent for Azure Service Management API permissions assigned to IPAM Engine application + foreach ($scope in $engineGraphScopes) { + $msGraphId = Get-AzADServicePrincipal ` + -ApplicationId $scope.scopeId - Function Grant-AdminConsent { - Param( - [Parameter(Mandatory = $false)] - [string]$UIAppId = [GUID]::Empty, - [Parameter(Mandatory = $true)] - [string]$EngineAppId, - [Parameter(Mandatory = $true)] - [string]$AzureCloud, - [Parameter(Mandatory = $false)] - [bool]$DisableUI = $false - ) + New-MgOauth2PermissionGrant ` + -ResourceId $msGraphId.Id ` + -Scope $scope.scopes ` + -ClientId $engineSpn.Id ` + -ConsentType AllPrincipals ` + | Out-Null + } - $msGraphMap = @{ - AZURE_PUBLIC = @{ - Endpoint = "graph.microsoft.com" - Environment = "Global" - } - AZURE_US_GOV = @{ - Endpoint = "graph.microsoft.us" - Environment = "USGov" - } - AZURE_US_GOV_SECRET = @{ - Endpoint = "graph.cloudapi.microsoft.scloud" - Environment = "USSec" - } - AZURE_GERMANY = @{ - Endpoint = "graph.microsoft.de" - Environment = "Germany" - } - AZURE_CHINA = @{ - Endpoint = "microsoftgraph.chinacloudapi.cn" - Environment = "China" - } - }; - - $uiGraphScopes = [System.Collections.ArrayList]@( - @{ - scopeId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph - scopes = " openid profile offline_access User.Read Directory.Read.All" - } - ) + Write-Host "INFO: Admin consent for Azure Service Management API permissions granted successfully" -ForegroundColor Green + } + + Function Save-Parameters { + Param( + [Parameter(Mandatory = $false)] + [string]$UIAppId = [GUID]::Empty, + [Parameter(Mandatory = $true)] + [string]$EngineAppId, + [Parameter(Mandatory = $true)] + [string]$EngineSecret, + [Parameter(Mandatory = $false)] + [bool]$DisableUI = $false + ) + + Write-Host "INFO: Populating Bicep parameter file for infrastructure deployment" -ForegroundColor Green + + # Retrieve JSON object from sample parameter file + $parametersObject = Get-Content main.parameters.example.json | ConvertFrom-Json + + # Update Parameter Values + $parametersObject.parameters.engineAppId.value = $EngineAppId + $parametersObject.parameters.engineAppSecret.value = $EngineSecret + + if (-not $DisableUI) { + $parametersObject.parameters.uiAppId.value = $UIAppId + $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property uiAppId, engineAppId, engineAppSecret + } + else { + $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property engineAppId, engineAppSecret + } - $engineGraphScopes = [System.Collections.ArrayList]@( - @{ - scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management - scopes = "user_impersonation" - } - ) + # Output updated parameter file for Bicep deployment + $parametersObject | ConvertTo-Json -Depth 4 | Out-File -FilePath main.parameters.json - # Get Microsoft Graph Access Token - $accesstoken = (Get-AzAccessToken -Resource "https://$($msGraphMap[$AzureCloud].Endpoint)/").Token + Write-Host "INFO: Bicep parameter file populated successfully" -ForegroundColor Green + } - # Switch Access Token to SecureString if Graph Version is 2.x - $graphVersion = [System.Version](Get-InstalledModule -Name Microsoft.Graph | Sort-Object -Property Version | Select-Object -Last 1).Version ` - ?? (Get-Module -Name Microsoft.Graph | Sort-Object -Property Version | Select-Object -Last 1).Version + Function Import-Parameters { + Param( + [Parameter(Mandatory = $true)] + [System.IO.FileInfo]$ParameterFile + ) - if ($graphVersion.Major -gt 1) { - $accesstoken = ConvertTo-SecureString $accesstoken -AsPlainText -Force - } + Write-Host "INFO: Importing values from Bicep parameters file" -ForegroundColor Green - Write-Host "INFO: Logging in to Microsoft Graph" -ForegroundColor Green + # Retrieve JSON object from sample parameter file + $parametersObject = Get-Content $ParameterFile | ConvertFrom-Json - # Connect to Microsoft Graph - Connect-MgGraph -Environment $msGraphMap[$AzureCloud].Environment -AccessToken $accesstoken | Out-Null + # Read Values from Parameters + $UIAppId = $parametersObject.parameters.uiAppId.value ?? [GUID]::Empty + $EngineAppId = $parametersObject.parameters.engineAppId.value + $EngineSecret = $parametersObject.parameters.engineAppSecret.value + $script:DisableUI = ($UIAppId -eq [GUID]::Empty) ? $true : $false - # Fetch Azure IPAM UI Service Principal (If DisableUI not specified) - if (-not $DisableUI) { - $uiSpn = Get-AzADServicePrincipal ` - -ApplicationId $UIAppId - } + if ((-not $EngineAppId) -or (-not $EngineSecret)) { + Write-Host "ERROR: Missing required parameters from Bicep parameter file" -ForegroundColor Red + Write-Host "ERROR: Please ensure the following parameters are present in the Bicep parameter file" -ForegroundColor Red + Write-Host "ERROR: Required: [engineAppId, engineAppSecret]" -ForegroundColor Red + Write-Host "" + Write-Host "ERROR: Please refer to the deployment documentation for more information" -ForegroundColor Red + Write-Host "ERROR: " -ForegroundColor Red -NoNewline + Write-Host "https://azure.github.io/ipam/#/deployment/README" -ForegroundColor Yellow + Write-Host "" - # Fetch Azure IPAM Engine Service Principal - $engineSpn = Get-AzADServicePrincipal ` - -ApplicationId $EngineAppId + throw [System.ArgumentException]::New("One of the required parameters are missing or invalid.") + } - # Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI application" -ForegroundColor Green + # $deployType = $script:AsFunction ? 'Function' : 'Full' - foreach ($scope in $uiGraphScopes) { - $msGraphId = Get-AzADServicePrincipal ` - -ApplicationId $scope.scopeId + Write-Host "INFO: Successfully import Bicep parameter values for deployment" -ForegroundColor Green - New-MgOauth2PermissionGrant ` - -ResourceId $msGraphId.Id ` - -Scope $scope.scopes ` - -ClientId $uiSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null - } + $appDetails = @{ + UIAppId = $UIAppId + EngineAppId = $EngineAppId + EngineSecret = $EngineSecret + } - Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor Green - } + return $appDetails + } + + Function Deploy-Bicep { + Param( + [Parameter(Mandatory = $false)] + [string]$UIAppId = [GUID]::Empty, + [Parameter(Mandatory = $true)] + [string]$EngineAppId, + [Parameter(Mandatory = $true)] + [string]$EngineSecret, + [Parameter(Mandatory = $false)] + [string]$NamePrefix, + [Parameter(Mandatory = $false)] + [string]$AzureCloud, + [Parameter(Mandatory = $false)] + [bool]$Function, + [Parameter(Mandatory = $false)] + [bool]$Native, + [Parameter(Mandatory = $false)] + [bool]$PrivateAcr, + [Parameter(Mandatory = $false)] + [hashtable]$Tags, + [Parameter(Mandatory = $false)] + [hashtable]$ResourceNames + ) + + Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor Green + + # Instantiate deployment parameter object + $deploymentParameters = @{ + engineAppId = $EngineAppId + engineAppSecret = $EngineSecret + uiAppId = $UiAppId + } - # Grant admin consent to the IPAM UI application for exposed API from the IPAM Engine application (If DisableUI not specified) - if (-not $DisableUI) { - Write-Host "INFO: Granting admin consent to the IPAM UI application for exposed API from the IPAM Engine application" -ForegroundColor Green + if ($NamePrefix) { + $deploymentParameters.Add('namePrefix', $NamePrefix) + } - New-MgOauth2PermissionGrant ` - -ResourceId $engineSpn.Id ` - -Scope "access_as_user" ` - -ClientId $uiSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null + if ($AzureCloud) { + $deploymentParameters.Add('azureCloud', $AzureCloud) + } - Write-Host "INFO: Admin consent for IPAM Engine exposed API granted successfully" -ForegroundColor Green - } + if ($Function) { + $deploymentParameters.Add('deployAsFunc', $Function) + } - Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine application" -ForegroundColor Green + if (-not $Native) { + $deploymentParameters.Add('deployAsContainer', !$Native) + } - # Grant admin consent for Azure Service Management API permissions assigned to IPAM Engine application - foreach ($scope in $engineGraphScopes) { - $msGraphId = Get-AzADServicePrincipal ` - -ApplicationId $scope.scopeId + if ($PrivateAcr) { + $deploymentParameters.Add('privateAcr', $PrivateAcr) + } - New-MgOauth2PermissionGrant ` - -ResourceId $msGraphId.Id ` - -Scope $scope.scopes ` - -ClientId $engineSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null - } + if ($Tags) { + $deploymentParameters.Add('tags', $Tags) + } - Write-Host "INFO: Admin consent for Azure Service Management API permissions granted successfully" -ForegroundColor Green + if ($ResourceNames) { + $deploymentParameters.Add('resourceNames', $ResourceNames) } - Function Save-Parameters { - Param( - [Parameter(Mandatory = $false)] - [string]$UIAppId = [GUID]::Empty, - [Parameter(Mandatory = $true)] - [string]$EngineAppId, - [Parameter(Mandatory = $true)] - [string]$EngineSecret, - [Parameter(Mandatory = $false)] - [bool]$DisableUI = $false - ) + $DebugPreference = $debugSetting - Write-Host "INFO: Populating Bicep parameter file for infrastructure deployment" -ForegroundColor Green + # Deploy IPAM bicep template + $deployment = & { + New-AzSubscriptionDeployment ` + -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` + -Location $location ` + -TemplateFile main.bicep ` + -TemplateParameterObject $deploymentParameters ` + 5>$($DEBUG_MODE ? $debugLog : $null) + } - # Retrieve JSON object from sample parameter file - $parametersObject = Get-Content main.parameters.example.json | ConvertFrom-Json + $DebugPreference = 'SilentlyContinue' - # Update Parameter Values - $parametersObject.parameters.engineAppId.value = $EngineAppId - $parametersObject.parameters.engineAppSecret.value = $EngineSecret + Write-Host "INFO: IPAM bicep templates deployed successfully" -ForegroundColor Green - if (-not $DisableUI) { - $parametersObject.parameters.uiAppId.value = $UIAppId - $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property uiAppId, engineAppId, engineAppSecret - } - else { - $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property engineAppId, engineAppSecret - } + return $deployment + } - # Output updated parameter file for Bicep deployment - $parametersObject | ConvertTo-Json -Depth 4 | Out-File -FilePath main.parameters.json + Function Publish-ZipFile { + Param( + [Parameter(Mandatory = $true)] + [string]$AppName, + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + [Parameter(Mandatory = $false)] + [switch]$UseAPI + ) - Write-Host "INFO: Bicep parameter file populated successfully" -ForegroundColor Green + if ($UseAPI) { + Write-Host "INFO: Using Kudu API for ZIP Deploy" -ForegroundColor Green } - Function Import-Parameters { - Param( - [Parameter(Mandatory = $true)] - [System.IO.FileInfo]$ParameterFile - ) + $zipPath = Join-Path -Path $ROOT_DIR -ChildPath 'assets' -AdditionalChildPath "ipam.zip" - Write-Host "INFO: Importing values from Bicep parameters file" -ForegroundColor Green + $publishRetries = 3 + $publishSuccess = $False - # Retrieve JSON object from sample parameter file - $parametersObject = Get-Content $ParameterFile | ConvertFrom-Json + if ($UseAPI) { + $accessToken = (Get-AzAccessToken).Token + $zipContents = Get-Item -Path $zipPath - # Read Values from Parameters - $UIAppId = $parametersObject.parameters.uiAppId.value ?? [GUID]::Empty - $EngineAppId = $parametersObject.parameters.engineAppId.value - $EngineSecret = $parametersObject.parameters.engineAppSecret.value - $script:DisableUI = ($UIAppId -eq [GUID]::Empty) ? $true : $false + $publishProfile = Get-AzWebAppPublishingProfile -Name $AppName -ResourceGroupName $ResourceGroupName + $zipUrl = ([System.uri]($publishProfile | Select-Xml -XPath "//publishProfile[@publishMethod='ZipDeploy']" | Select-Object -ExpandProperty Node).publishUrl).Scheme + } - if ((-not $EngineAppId) -or (-not $EngineSecret)) { - Write-Host "ERROR: Missing required parameters from Bicep parameter file" -ForegroundColor Red - Write-Host "ERROR: Please ensure the following parameters are present in the Bicep parameter file" -ForegroundColor Red - Write-Host "ERROR: Required: [engineAppId, engineAppSecret]" -ForegroundColor Red - Write-Host "" - Write-Host "ERROR: Please refer to the deployment documentation for more information" -ForegroundColor Red - Write-Host "ERROR: " -ForegroundColor Red -NoNewline - Write-Host "https://azure.github.io/ipam/#/deployment/README" -ForegroundColor Yellow - Write-Host "" + do { + try { + if (-not $UseAPI) { + Publish-AzWebApp ` + -Name $AppName ` + -ResourceGroupName $ResourceGroupName ` + -ArchivePath $zipPath ` + -Restart ` + -Force ` + | Out-Null + } + else { + Invoke-RestMethod ` + -Uri "https://${zipUrl}/api/zipdeploy" ` + -Method Post ` + -ContentType "multipart/form-data" ` + -Headers @{ "Authorization" = "Bearer $accessToken" } ` + -Form @{ file = $zipContents } ` + -StatusCodeVariable statusCode ` + | Out-Null + + if ($statusCode -ne 200) { + throw [System.Exception]::New("Error while uploading ZIP Deploy via Kudu API! ($statusCode)") + } + } - throw [System.ArgumentException]::New("One of the required parameters are missing or invalid.") + $publishSuccess = $True + Write-Host "INFO: ZIP Deploy archive successfully uploaded" -ForegroundColor Green + } + catch { + if ($publishRetries -gt 0) { + Write-Host "WARNING: Problem while uploading ZIP Deploy archive! Retrying..." -ForegroundColor Yellow + $publishRetries-- } + else { + Write-Host "ERROR: Unable to upload ZIP Deploy archive!" -ForegroundColor Red + throw $_ + } + } + } while ($publishSuccess -eq $False -and $publishRetries -ge 0) + } - # $deployType = $script:AsFunction ? 'Function' : 'Full' + Function Update-UIApplication { + Param( + [Parameter(Mandatory = $true)] + [string]$UIAppId, + [Parameter(Mandatory = $true)] + [string]$Endpoint + ) - Write-Host "INFO: Successfully import Bicep parameter values for deployment" -ForegroundColor Green + Write-Host "INFO: Updating UI Application with SPA configuration" -ForegroundColor Green - $appDetails = @{ - UIAppId = $UIAppId - EngineAppId = $EngineAppId - EngineSecret = $EngineSecret - } + $appServiceEndpoint = "https://$Endpoint" - return $appDetails - } + # Update UI Application with single-page application configuration + Update-AzADApplication -ApplicationId $UIAppId -SPARedirectUri $appServiceEndpoint - Function Deploy-Bicep { - Param( - [Parameter(Mandatory = $false)] - [string]$UIAppId = [GUID]::Empty, - [Parameter(Mandatory = $true)] - [string]$EngineAppId, - [Parameter(Mandatory = $true)] - [string]$EngineSecret, - [Parameter(Mandatory = $false)] - [string]$NamePrefix, - [Parameter(Mandatory = $false)] - [string]$AzureCloud, - [Parameter(Mandatory = $false)] - [bool]$Function, - [Parameter(Mandatory = $false)] - [bool]$Native, - [Parameter(Mandatory = $false)] - [bool]$PrivateAcr, - [Parameter(Mandatory = $false)] - [hashtable]$Tags, - [Parameter(Mandatory = $false)] - [hashtable]$ResourceNames - ) + Write-Host "INFO: UI Application SPA configuration update complete" -ForegroundColor Green + } - Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor Green + # Main Deployment Script Section + Write-Host - # Instantiate deployment parameter object - $deploymentParameters = @{ - engineAppId = $EngineAppId - engineAppSecret = $EngineSecret - uiAppId = $UiAppId - } + if ($DEBUG_MODE) { + Write-Host "DEBUG: Debug Mode Enabled" -ForegroundColor Gray + } - if ($NamePrefix) { - $deploymentParameters.Add('namePrefix', $NamePrefix) - } + Write-Host "NOTE: IPAM Deployment Type: $($PSCmdlet.ParameterSetName)" -ForegroundColor Magenta - if ($AzureCloud) { - $deploymentParameters.Add('azureCloud', $AzureCloud) - } + try { + if ($PrivateAcr) { + Write-Host "INFO: PrivateACR flag set, verifying minimum Azure CLI version" -ForegroundColor Green - if ($Function) { - $deploymentParameters.Add('deployAsFunc', $Function) - } + # Verify Minimum Azure CLI Version + $azureCliVer = [System.Version](az version | ConvertFrom-Json).'azure-cli' - if (-not $Native) { - $deploymentParameters.Add('deployAsContainer', !$Native) - } + if ($azureCliVer -lt $MIN_AZ_CLI_VER) { + Write-Host "ERROR: Azure CLI must be version $MIN_AZ_CLI_VER or greater!" -ForegroundColor Red + exit + } - if ($PrivateAcr) { - $deploymentParameters.Add('privateAcr', $PrivateAcr) - } + Write-Host "INFO: PrivateACR flag set, verifying Azure PowerShell and Azure CLI contexts match" -ForegroundColor Green - if ($Tags) { - $deploymentParameters.Add('tags', $Tags) - } + # Verify Azure PowerShell and Azure CLI Contexts Match + $azureCliContext = $(az account show | ConvertFrom-Json) 2>$null - if ($ResourceNames) { - $deploymentParameters.Add('resourceNames', $ResourceNames) - } + if (-not $azureCliContext) { + Write-Host "ERROR: Azure CLI not logged in or no subscription has been selected!" -ForegroundColor Red + exit + } - $DebugPreference = $debugSetting + $azureCliSub = $azureCliContext.id + $azurePowerShellSub = (Get-AzContext).Subscription.Id - # Deploy IPAM bicep template - $deployment = & { - New-AzSubscriptionDeployment ` - -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` - -Location $location ` - -TemplateFile main.bicep ` - -TemplateParameterObject $deploymentParameters ` - 5>$($DEBUG_MODE ? $debugLog : $null) - } + if ($azurePowerShellSub -ne $azureCliSub) { + Write-Host "ERROR: Azure PowerShell and Azure CLI must be set to the same context!" -ForegroundColor Red + exit + } + } - $DebugPreference = 'SilentlyContinue' + if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer', 'AppsOnly')) { + # Fetch Tenant ID (If Required) + if ($MgmtGroupId) { + Write-Host "NOTE: Management Group ID Specified" -ForegroundColor Magenta + } + else { + Write-Host "INFO: Fetching Tenant ID from Azure PowerShell SDK" -ForegroundColor Green + $script:MgmtGroupId = (Get-AzContext).Tenant.Id + } - Write-Host "INFO: IPAM bicep templates deployed successfully" -ForegroundColor Green + Write-Host "INFO: Fetching Azure Cloud type from Azure PowerShell SDK" -ForegroundColor Green - return $deployment - } + # Fetch Azure Cloud Type + $azureCloud = $AZURE_ENV_MAP[(Get-AzContext).Environment.Name] - Function Publish-ZipFile { - Param( - [Parameter(Mandatory = $true)] - [string]$AppName, - [Parameter(Mandatory = $true)] - [string]$ResourceGroupName, - [Parameter(Mandatory = $false)] - [switch]$UseAPI - ) + # Verify Azure Cloud Type is Supported + if (-not [bool]$azureCloud) { + Write-Host "ERROR: Azure Cloud type is not currently supported!" -ForegroundColor Red + Write-Host + Write-Host "Azure Cloud type: " -ForegroundColor Yellow -NoNewline + Write-Host (Get-AzContext).Environment.Name -ForegroundColor Cyan + exit + } + } - if ($UseAPI) { - Write-Host "INFO: Using Kudu API for ZIP Deploy" -ForegroundColor Green - } + if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer')) { + Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor Green - $zipPath = Join-Path -Path $ROOT_DIR -ChildPath 'assets' -AdditionalChildPath "ipam.zip" + # Validate Azure Region + if (Test-Location -Location $Location) { + Write-Host "INFO: Azure Region validated successfully" -ForegroundColor Green + } + else { + Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor Red + Write-Host + Write-Host "Azure Region: " -ForegroundColor Yellow -NoNewline + Write-Host $Location -ForegroundColor Cyan + exit + } + } - $publishRetries = 3 - $publishSuccess = $False + if (-not $ParameterFile) { + $appDetails = Deploy-IPAMApplications ` + -UIAppName $UIAppName ` + -EngineAppName $EngineAppName ` + -MgmtGroupId $MgmtGroupId ` + -AzureCloud $azureCloud ` + -DisableUI $DisableUI - if ($UseAPI) { - $accessToken = (Get-AzAccessToken).Token - $zipContents = Get-Item -Path $zipPath + $consentDetails = @{ + EngineAppId = $appDetails.EngineAppId + } - $publishProfile = Get-AzWebAppPublishingProfile -Name $AppName -ResourceGroupName $ResourceGroupName - $zipUrl = ([System.uri]($publishProfile | Select-Xml -XPath "//publishProfile[@publishMethod='ZipDeploy']" | Select-Object -ExpandProperty Node).publishUrl).Scheme - } + if (-not $DisableUI) { + $consentDetails.Add("UIAppId", $appDetails.UIAppId) + } - do { - try { - if (-not $UseAPI) { - Publish-AzWebApp ` - -Name $AppName ` - -ResourceGroupName $ResourceGroupName ` - -ArchivePath $zipPath ` - -Restart ` - -Force ` - | Out-Null - } - else { - Invoke-RestMethod ` - -Uri "https://${zipUrl}/api/zipdeploy" ` - -Method Post ` - -ContentType "multipart/form-data" ` - -Headers @{ "Authorization" = "Bearer $accessToken" } ` - -Form @{ file = $zipContents } ` - -StatusCodeVariable statusCode ` - | Out-Null - - if ($statusCode -ne 200) { - throw [System.Exception]::New("Error while uploading ZIP Deploy via Kudu API! ($statusCode)") - } - } - - $publishSuccess = $True - Write-Host "INFO: ZIP Deploy archive successfully uploaded" -ForegroundColor Green - } - catch { - if ($publishRetries -gt 0) { - Write-Host "WARNING: Problem while uploading ZIP Deploy archive! Retrying..." -ForegroundColor Yellow - $publishRetries-- - } - else { - Write-Host "ERROR: Unable to upload ZIP Deploy archive!" -ForegroundColor Red - throw $_ - } - } - } while ($publishSuccess -eq $False -and $publishRetries -ge 0) + Grant-AdminConsent @consentDetails -AzureCloud $azureCloud -DisableUI $DisableUI } - Function Update-UIApplication { - Param( - [Parameter(Mandatory = $true)] - [string]$UIAppId, - [Parameter(Mandatory = $true)] - [string]$Endpoint - ) - - Write-Host "INFO: Updating UI Application with SPA configuration" -ForegroundColor Green + if ($PSCmdlet.ParameterSetName -in ('AppsOnly')) { + Save-Parameters @appDetails -DisableUI $DisableUI + } - $appServiceEndpoint = "https://$Endpoint" + if ($ParameterFile) { + $appDetails = Import-Parameters ` + -ParameterFile $ParameterFile + } - # Update UI Application with single-page application configuration - Update-AzADApplication -ApplicationId $UIAppId -SPARedirectUri $appServiceEndpoint + if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer')) { + $deployment = Deploy-Bicep @appDetails ` + -NamePrefix $NamePrefix ` + -AzureCloud $azureCloud ` + -PrivateAcr $PrivateAcr ` + -Function $Function ` + -Native $Native ` + -Tags $Tags ` + -ResourceNames $ResourceNames + } - Write-Host "INFO: UI Application SPA configuration update complete" -ForegroundColor Green + if (($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and (-not $DisableUI)) { + Update-UIApplication ` + -UIAppId $appDetails.UIAppId ` + -Endpoint $deployment.Outputs["appServiceHostName"].Value } - # Main Deployment Script Section - Write-Host + if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) { + Write-Host "INFO: Uploading ZIP Deploy archive..." -ForegroundColor Green - if ($DEBUG_MODE) { - Write-Host "DEBUG: Debug Mode Enabled" -ForegroundColor Gray + try { + Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value + } + catch { + Write-Host "SWITCH: Retrying ZIP Deploy with Kudu API..." -ForegroundColor Blue + Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -UseAPI + } } - Write-Host "NOTE: IPAM Deployment Type: $($PSCmdlet.ParameterSetName)" -ForegroundColor Magenta + if ($PSCmdlet.ParameterSetName -in ('AppContainer', 'FunctionContainer') -and $PrivateAcr) { + Write-Host "INFO: Building and pushing container image to Azure Container Registry" -ForegroundColor Green + + $containerMap = @{ + Debian = @{ + Extension = 'deb' + Port = 8080 + Images = @{ + Build = 'node:18-slim' + Serve = 'python:3.9-slim' + } + } + RHEL = @{ + Extension = 'rhel' + Port = 8080 + Images = @{ + Build = 'registry.access.redhat.com/ubi8/nodejs-18' + Serve = 'registry.access.redhat.com/ubi8/python-39' + } + } + } - try { - if ($PrivateAcr) { - Write-Host "INFO: PrivateACR flag set, verifying minimum Azure CLI version" -ForegroundColor Green + $dockerFile = 'Dockerfile.' + $containerMap[$ContainerType].Extension + $dockerFilePath = Join-Path -Path $ROOT_DIR -ChildPath $dockerFile + $dockerFileFunc = Join-Path -Path $ROOT_DIR -ChildPath 'Dockerfile.func' - # Verify Minimum Azure CLI Version - $azureCliVer = [System.Version](az version | ConvertFrom-Json).'azure-cli' + if ($Function) { + Write-Host "INFO: Building Function container..." -ForegroundColor Green - if ($azureCliVer -lt $MIN_AZ_CLI_VER) { - Write-Host "ERROR: Azure CLI must be version $MIN_AZ_CLI_VER or greater!" -ForegroundColor Red - exit - } + $funcBuildOutput = $( + az acr build -r $deployment.Outputs["acrName"].Value ` + -t ipamfunc:latest ` + -f $dockerFileFunc $ROOT_DIR ` + --no-logs + ) *>&1 - Write-Host "INFO: PrivateACR flag set, verifying Azure PowerShell and Azure CLI contexts match" -ForegroundColor Green + if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Container build process failed, fetching error logs..." -ForegroundColor Red - # Verify Azure PowerShell and Azure CLI Contexts Match - $azureCliContext = $(az account show | ConvertFrom-Json) 2>$null + $buildId = [regex]::Matches($funcBuildOutput, "(?<=Queued a build with ID: )[\w]*").Value.Trim() - if (-not $azureCliContext) { - Write-Host "ERROR: Azure CLI not logged in or no subscription has been selected!" -ForegroundColor Red - exit - } + $buildLogs = Get-BuildLogs ` + -SubscriptionId $deployment.Outputs["subscriptionId"].Value ` + -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value ` + -RegistryName $deployment.Outputs["acrName"].Value ` + -BuildId $buildId - $azureCliSub = $azureCliContext.id - $azurePowerShellSub = (Get-AzContext).Subscription.Id + $buildLogs | Out-File -FilePath $errorLog -Append - if ($azurePowerShellSub -ne $azureCliSub) { - Write-Host "ERROR: Azure PowerShell and Azure CLI must be set to the same context!" -ForegroundColor Red - exit - } + $script:containerBuildError = $true } - - if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer', 'AppsOnly')) { - # Fetch Tenant ID (If Required) - if ($MgmtGroupId) { - Write-Host "NOTE: Management Group ID Specified" -ForegroundColor Magenta - } - else { - Write-Host "INFO: Fetching Tenant ID from Azure PowerShell SDK" -ForegroundColor Green - $script:MgmtGroupId = (Get-AzContext).Tenant.Id - } - - Write-Host "INFO: Fetching Azure Cloud type from Azure PowerShell SDK" -ForegroundColor Green - - # Fetch Azure Cloud Type - $azureCloud = $AZURE_ENV_MAP[(Get-AzContext).Environment.Name] - - # Verify Azure Cloud Type is Supported - if (-not [bool]$azureCloud) { - Write-Host "ERROR: Azure Cloud type is not currently supported!" -ForegroundColor Red - Write-Host - Write-Host "Azure Cloud type: " -ForegroundColor Yellow -NoNewline - Write-Host (Get-AzContext).Environment.Name -ForegroundColor Cyan - exit - } + else { + Write-Host "INFO: Function container image build and push completed successfully" -ForegroundColor Green } - if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer')) { - Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor Green - - # Validate Azure Region - if (Test-Location -Location $Location) { - Write-Host "INFO: Azure Region validated successfully" -ForegroundColor Green - } - else { - Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor Red - Write-Host - Write-Host "Azure Region: " -ForegroundColor Yellow -NoNewline - Write-Host $Location -ForegroundColor Cyan - exit - } - } + Write-Host "INFO: Restarting Function App" -ForegroundColor Green - if (-not $ParameterFile) { - $appDetails = Deploy-IPAMApplications ` - -UIAppName $UIAppName ` - -EngineAppName $EngineAppName ` - -MgmtGroupId $MgmtGroupId ` - -AzureCloud $azureCloud ` - -DisableUI $DisableUI + Restart-AzFunctionApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -Force | Out-Null + } + else { + Write-Host "INFO: Building App container ($ContainerType)..." -ForegroundColor Green - $consentDetails = @{ - EngineAppId = $appDetails.EngineAppId - } + $appBuildOutput = $( + az acr build -r $deployment.Outputs["acrName"].Value ` + -t ipam:latest ` + -f $dockerFilePath $ROOT_DIR ` + --build-arg PORT=$($containerMap[$ContainerType].Port) ` + --build-arg BUILD_IMAGE=$($containerMap[$ContainerType].Images.Build) ` + --build-arg SERVE_IMAGE=$($containerMap[$ContainerType].Images.Serve) ` + --no-logs + ) *>&1 - if (-not $DisableUI) { - $consentDetails.Add("UIAppId", $appDetails.UIAppId) - } + if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Container build process failed, fetching error logs..." -ForegroundColor Red - Grant-AdminConsent @consentDetails -AzureCloud $azureCloud -DisableUI $DisableUI - } + $buildId = [regex]::Matches($appBuildOutput, "(?<=Queued a build with ID: )[\w]*").Value.Trim() - if ($PSCmdlet.ParameterSetName -in ('AppsOnly')) { - Save-Parameters @appDetails -DisableUI $DisableUI - } + $buildLogs = Get-BuildLogs ` + -SubscriptionId $deployment.Outputs["subscriptionId"].Value ` + -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value ` + -RegistryName $deployment.Outputs["acrName"].Value ` + -BuildId $buildId - if ($ParameterFile) { - $appDetails = Import-Parameters ` - -ParameterFile $ParameterFile - } + $buildLogs | Out-File -FilePath $errorLog -Append - if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer')) { - $deployment = Deploy-Bicep @appDetails ` - -NamePrefix $NamePrefix ` - -AzureCloud $azureCloud ` - -PrivateAcr $PrivateAcr ` - -Function $Function ` - -Native $Native ` - -Tags $Tags ` - -ResourceNames $ResourceNames + $script:containerBuildError = $true } - - if (($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and (-not $DisableUI)) { - Update-UIApplication ` - -UIAppId $appDetails.UIAppId ` - -Endpoint $deployment.Outputs["appServiceHostName"].Value + else { + Write-Host "INFO: App container image build and push completed successfully" -ForegroundColor Green } - if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) { - Write-Host "INFO: Uploading ZIP Deploy archive..." -ForegroundColor Green + if (-not $containerBuildError) { + Write-Host "INFO: Restarting App Service" -ForegroundColor Green - try { - Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value - } - catch { - Write-Host "SWITCH: Retrying ZIP Deploy with Kudu API..." -ForegroundColor Blue - Publish-ZipFile -AppName $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -UseAPI - } + Restart-AzWebApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value | Out-Null } + } + } - if ($PSCmdlet.ParameterSetName -in ('AppContainer', 'FunctionContainer') -and $PrivateAcr) { - Write-Host "INFO: Building and pushing container image to Azure Container Registry" -ForegroundColor Green - - $containerMap = @{ - Debian = @{ - Extension = 'deb' - Port = 8080 - Images = @{ - Build = 'node:18-slim' - Serve = 'python:3.9-slim' - } - } - RHEL = @{ - Extension = 'rhel' - Port = 8080 - Images = @{ - Build = 'registry.access.redhat.com/ubi8/nodejs-18' - Serve = 'registry.access.redhat.com/ubi8/python-39' - } - } - } - - $dockerFile = 'Dockerfile.' + $containerMap[$ContainerType].Extension - $dockerFilePath = Join-Path -Path $ROOT_DIR -ChildPath $dockerFile - $dockerFileFunc = Join-Path -Path $ROOT_DIR -ChildPath 'Dockerfile.func' - - if ($Function) { - Write-Host "INFO: Building Function container..." -ForegroundColor Green - - $funcBuildOutput = $( - az acr build -r $deployment.Outputs["acrName"].Value ` - -t ipamfunc:latest ` - -f $dockerFileFunc $ROOT_DIR ` - --no-logs - ) *>&1 - - if ($LASTEXITCODE -ne 0) { - Write-Host "ERROR: Container build process failed, fetching error logs..." -ForegroundColor Red - - $buildId = [regex]::Matches($funcBuildOutput, "(?<=Queued a build with ID: )[\w]*").Value.Trim() - - $buildLogs = Get-BuildLogs ` - -SubscriptionId $deployment.Outputs["subscriptionId"].Value ` - -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value ` - -RegistryName $deployment.Outputs["acrName"].Value ` - -BuildId $buildId - - $buildLogs | Out-File -FilePath $errorLog -Append - - $script:containerBuildError = $true - } - else { - Write-Host "INFO: Function container image build and push completed successfully" -ForegroundColor Green - } - - Write-Host "INFO: Restarting Function App" -ForegroundColor Green - - Restart-AzFunctionApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -Force | Out-Null - } - else { - Write-Host "INFO: Building App container ($ContainerType)..." -ForegroundColor Green - - $appBuildOutput = $( - az acr build -r $deployment.Outputs["acrName"].Value ` - -t ipam:latest ` - -f $dockerFilePath $ROOT_DIR ` - --build-arg PORT=$($containerMap[$ContainerType].Port) ` - --build-arg BUILD_IMAGE=$($containerMap[$ContainerType].Images.Build) ` - --build-arg SERVE_IMAGE=$($containerMap[$ContainerType].Images.Serve) ` - --no-logs - ) *>&1 - - if ($LASTEXITCODE -ne 0) { - Write-Host "ERROR: Container build process failed, fetching error logs..." -ForegroundColor Red - - $buildId = [regex]::Matches($appBuildOutput, "(?<=Queued a build with ID: )[\w]*").Value.Trim() - - $buildLogs = Get-BuildLogs ` - -SubscriptionId $deployment.Outputs["subscriptionId"].Value ` - -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value ` - -RegistryName $deployment.Outputs["acrName"].Value ` - -BuildId $buildId - - $buildLogs | Out-File -FilePath $errorLog -Append - - $script:containerBuildError = $true - } - else { - Write-Host "INFO: App container image build and push completed successfully" -ForegroundColor Green - } - - if (-not $containerBuildError) { - Write-Host "INFO: Restarting App Service" -ForegroundColor Green - - Restart-AzWebApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value | Out-Null - } - } - } + if (-not $containerBuildError) { + Write-Host "INFO: Azure IPAM Solution deployed successfully" -ForegroundColor Green + } + else { + Write-Host "WARNING: Azure IPAM Solution deployed with errors, see logs for details!" -ForegroundColor Yellow + Write-Host "Run Log: $transcriptLog" -ForegroundColor Yellow + Write-Host "Error Log: $errorLog" -ForegroundColor Yellow + } - if (-not $containerBuildError) { - Write-Host "INFO: Azure IPAM Solution deployed successfully" -ForegroundColor Green - } - else { - Write-Host "WARNING: Azure IPAM Solution deployed with errors, see logs for details!" -ForegroundColor Yellow - Write-Host "Run Log: $transcriptLog" -ForegroundColor Yellow - Write-Host "Error Log: $errorLog" -ForegroundColor Yellow - } + if ($($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and (-not $DisableUI) -and $ParameterFile) { + $updateUrl = "https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Authentication/appId/$($appDetails.UIAppId)" + $updateAddr = "https://$($deployment.Outputs["appServiceHostName"].Value)" + + Write-Host + Write-Host "POST DEPLOYMENT TASKS:" -ForegroundColor Yellow + Write-Host "##############################################" -ForegroundColor Yellow + Write-Host "Navigate In Browser To:" -ForegroundColor Cyan + Write-Host $updateUrl -ForegroundColor White + Write-Host "Change 'Redirect URI' To:" -ForegroundColor Cyan + Write-Host $updateAddr -ForegroundColor White + Write-Host "##############################################" -ForegroundColor Yellow + } - if ($($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and (-not $DisableUI) -and $ParameterFile) { - $updateUrl = "https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Authentication/appId/$($appDetails.UIAppId)" - $updateAddr = "https://$($deployment.Outputs["appServiceHostName"].Value)" - - Write-Host - Write-Host "POST DEPLOYMENT TASKS:" -ForegroundColor Yellow - Write-Host "##############################################" -ForegroundColor Yellow - Write-Host "Navigate In Browser To:" -ForegroundColor Cyan - Write-Host $updateUrl -ForegroundColor White - Write-Host "Change 'Redirect URI' To:" -ForegroundColor Cyan - Write-Host $updateAddr -ForegroundColor White - Write-Host "##############################################" -ForegroundColor Yellow - } + if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) { + Write-Host + Write-Host "NOTE: Please allow ~5 minutes for the Azure IPAM service to become available" -ForegroundColor Yellow + } - if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) { - Write-Host - Write-Host "NOTE: Please allow ~5 minutes for the Azure IPAM service to become available" -ForegroundColor Yellow - } + $script:deploymentSuccess = $true + } + catch { + $_ | Out-File -FilePath $errorLog -Append + Write-Host "ERROR: Unable to deploy Azure IPAM solution due to an exception, see logs for detailed information!" -ForegroundColor Red + Write-Host "Run Log: $transcriptLog" -ForegroundColor Red + Write-Host "Error Log: $errorLog" -ForegroundColor Red - $script:deploymentSuccess = $true + if ($DEBUG_MODE) { + Write-Host "Debug Log: $debugLog" -ForegroundColor Red } - catch { - $_ | Out-File -FilePath $errorLog -Append - Write-Host "ERROR: Unable to deploy Azure IPAM solution due to an exception, see logs for detailed information!" -ForegroundColor Red - Write-Host "Run Log: $transcriptLog" -ForegroundColor Red - Write-Host "Error Log: $errorLog" -ForegroundColor Red - - if ($DEBUG_MODE) { - Write-Host "Debug Log: $debugLog" -ForegroundColor Red - } + } + finally { + Write-Host + Stop-Transcript | Out-Null + + if (($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and $script:deploymentSuccess) { + Write-Output "ipamURL=https://$($deployment.Outputs["appServiceHostName"].Value)" >> $Env:GITHUB_OUTPUT + Write-Output "ipamUIAppId=$($appDetails.UIAppId)" >> $Env:GITHUB_OUTPUT + Write-Output "ipamEngineAppId=$($appDetails.EngineAppId)" >> $Env:GITHUB_OUTPUT + Write-Output "ipamSuffix=$($deployment.Outputs["suffix"].Value)" >> $Env:GITHUB_OUTPUT + Write-Output "ipamResourceGroup=$($deployment.Outputs["resourceGroupName"].Value)" >> $Env:GITHUB_OUTPUT } - finally { - Write-Host - Stop-Transcript | Out-Null - - if (($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and $script:deploymentSuccess) { - Write-Output "ipamURL=https://$($deployment.Outputs["appServiceHostName"].Value)" >> $Env:GITHUB_OUTPUT - Write-Output "ipamUIAppId=$($appDetails.UIAppId)" >> $Env:GITHUB_OUTPUT - Write-Output "ipamEngineAppId=$($appDetails.EngineAppId)" >> $Env:GITHUB_OUTPUT - Write-Output "ipamSuffix=$($deployment.Outputs["suffix"].Value)" >> $Env:GITHUB_OUTPUT - Write-Output "ipamResourceGroup=$($deployment.Outputs["resourceGroupName"].Value)" >> $Env:GITHUB_OUTPUT - } - exit - } + exit + } } From ccec76e8f69db29ff8ebde86b79ba02ee02df739 Mon Sep 17 00:00:00 2001 From: picccard <7443949+picccard@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:02:59 +0200 Subject: [PATCH 5/6] format update script --- deploy/update.ps1 | 94 ++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/deploy/update.ps1 b/deploy/update.ps1 index 92c798c..00902b2 100644 --- a/deploy/update.ps1 +++ b/deploy/update.ps1 @@ -41,11 +41,11 @@ $updateLog = Join-Path -Path $logPath -ChildPath "update_$(get-date -format `"yy Function Restart-IpamApp { Param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$AppName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$ResourceGroupName, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch]$Function ) @@ -61,14 +61,15 @@ Function Restart-IpamApp { -ErrorVariable restartErr ` -ErrorAction SilentlyContinue ` -Force ` - | Out-Null - } else { + | Out-Null + } + else { Restart-AzWebApp ` -Name $AppName ` -ResourceGroupName $ResourceGroupName ` -ErrorVariable restartErr ` -ErrorAction SilentlyContinue ` - | Out-Null + | Out-Null } if ($restartErr) { @@ -77,11 +78,13 @@ Function Restart-IpamApp { $restartSuccess = $True Write-Host "INFO: Application successfuly restarted" -ForegroundColor Green - } catch { - if($restartRetries -gt 0) { + } + catch { + if ($restartRetries -gt 0) { Write-Host "WARNING: Problem while restarting application! Retrying..." -ForegroundColor Yellow $restartRetries-- - } else { + } + else { Write-Host "ERROR: Unable to restart application!" -ForegroundColor Red throw $_ } @@ -91,11 +94,11 @@ Function Restart-IpamApp { Function Publish-ZipFile { Param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$AppName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$ResourceGroupName, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch]$UseAPI ) @@ -125,8 +128,9 @@ Function Publish-ZipFile { -ArchivePath $zipPath ` -Restart ` -Force ` - | Out-Null - } else { + | Out-Null + } + else { Invoke-RestMethod ` -Uri "https://${zipUrl}/api/zipdeploy" ` -Method Post ` @@ -134,20 +138,22 @@ Function Publish-ZipFile { -Headers @{ "Authorization" = "Bearer $accessToken" } ` -Form @{ file = $zipContents } ` -StatusCodeVariable statusCode ` - | Out-Null + | Out-Null - if ($statusCode -ne 200) { - throw [System.Exception]::New("Error while uploading ZIP Deploy via Kudu API! ($statusCode)") - } + if ($statusCode -ne 200) { + throw [System.Exception]::New("Error while uploading ZIP Deploy via Kudu API! ($statusCode)") + } } $publishSuccess = $True Write-Host "INFO: ZIP Deploy archive successfully uploaded" -ForegroundColor Green - } catch { - if($publishRetries -gt 0) { + } + catch { + if ($publishRetries -gt 0) { Write-Host "WARNING: Problem while uploading ZIP Deploy archive! Retrying..." -ForegroundColor Yellow $publishRetries-- - } else { + } + else { Write-Host "ERROR: Unable to upload ZIP Deploy archive!" -ForegroundColor Red throw $_ } @@ -168,10 +174,11 @@ try { $existingApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $AppName -ErrorAction SilentlyContinue - if($null -eq $existingApp) { + if ($null -eq $existingApp) { Write-Host "ERROR: Application not found in current subscription!" -ForegroundColor Red throw "Application does not exist!" - } else { + } + else { $appKind = $existingApp.Kind $appType = $($appKind.Split(",") -contains 'functionapp') ? 'Function' : 'App' $isFunction = $appType -eq 'Function' ? $true : $false @@ -196,7 +203,7 @@ try { exit } - if($privateAcr) { + if ($privateAcr) { $acrName = $appAcr.Split('.')[0] Write-Host "INFO: Deployment is using a private ACR (" -ForegroundColor Green -NoNewline @@ -222,7 +229,7 @@ try { # Verify Minimum Azure CLI Version $azureCliVer = [System.Version](az version | ConvertFrom-Json).'azure-cli' - if($azureCliVer -lt $MIN_AZ_CLI_VER) { + if ($azureCliVer -lt $MIN_AZ_CLI_VER) { Write-Host "ERROR: Azure CLI must be version $MIN_AZ_CLI_VER or greater!" -ForegroundColor Red exit } @@ -232,7 +239,7 @@ try { # Verify Azure PowerShell and Azure CLI Contexts Match $azureCliContext = $(az account show | ConvertFrom-Json) 2>$null - if(-not $azureCliContext) { + if (-not $azureCliContext) { Write-Host "ERROR: Azure CLI not logged in or no subscription has been selected!" -ForegroundColor Red exit } @@ -240,7 +247,7 @@ try { $azureCliSub = $azureCliContext.id $azurePowerShellSub = (Get-AzContext).Subscription.Id - if($azurePowerShellSub -ne $azureCliSub) { + if ($azurePowerShellSub -ne $azureCliSub) { Write-Host "ERROR: Azure PowerShell and Azure CLI must be set to the same context!" -ForegroundColor Red exit } @@ -256,7 +263,7 @@ try { $appPythonVersion = $existingApp.SiteConfig.LinuxFxVersion.Split('|')[1] - if($enginePythonVersion -ne $appPythonVersion) { + if ($enginePythonVersion -ne $appPythonVersion) { Write-Host "WARNING: Python version has changed (" -ForegroundColor Yellow -NoNewline Write-Host "v$appPythonVersion -> v$enginePythonVersion" -ForegroundColor Cyan -NoNewline Write-Host ")" -ForegroundColor Yellow @@ -290,16 +297,16 @@ try { $containerMap = @{ debian = @{ Extension = 'deb' - Port = 80 - Images = @{ + Port = 80 + Images = @{ Build = 'node:18-slim' Serve = 'python:3.9-slim' } } - rhel = @{ + rhel = @{ Extension = 'rhel' - Port = 8080 - Images = @{ + Port = 8080 + Images = @{ Build = 'registry.access.redhat.com/ubi8/nodejs-18' Serve = 'registry.access.redhat.com/ubi8/python-39' } @@ -310,25 +317,27 @@ try { $dockerFilePath = Join-Path -Path $ROOT_DIR -ChildPath $dockerFile $dockerFileFunc = Join-Path -Path $ROOT_DIR -ChildPath 'Dockerfile.func' - if($isFunction) { + if ($isFunction) { Write-Host "INFO: Building Function container..." -ForegroundColor Green $funcBuildOutput = $( az acr build -r $acrName ` - -t ipamfunc:latest ` - -f $dockerFileFunc $ROOT_DIR + -t ipamfunc:latest ` + -f $dockerFileFunc $ROOT_DIR ) *>&1 if ($LASTEXITCODE -ne 0) { throw $funcBuildOutput - } else { + } + else { Write-Host "INFO: Function container image build and push completed successfully" -ForegroundColor Green } Write-Host "INFO: Restarting Function App" -ForegroundColor Green Restart-IpamApp -AppName $AppName -ResourceGroupName $ResourceGroupName -Function - } else { + } + else { Write-Host "INFO: Building App container (" -ForegroundColor Green -NoNewline Write-Host "$containerType" -ForegroundColor Cyan -NoNewline Write-Host ")..." -ForegroundColor Green @@ -344,7 +353,8 @@ try { if ($LASTEXITCODE -ne 0) { throw $appBuildOutput - } else { + } + else { Write-Host "INFO: App container image build and push completed successfully" -ForegroundColor Green } @@ -352,12 +362,14 @@ try { Restart-IpamApp -AppName $AppName -ResourceGroupName $ResourceGroupName } - } else { + } + else { Write-Host "INFO: Uploading ZIP Deploy archive..." -ForegroundColor Green try { Publish-ZipFile -AppName $AppName -ResourceGroupName $ResourceGroupName - } catch { + } + catch { Write-Host "SWITCH: Retrying ZIP Deploy with Kudu API..." -ForegroundColor Blue Publish-ZipFile -AppName $AppName -ResourceGroupName $ResourceGroupName -UseAPI } From 51d84f5222aca806df50d884ebbb92e49ba4995e Mon Sep 17 00:00:00 2001 From: picccard <7443949+picccard@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:03:19 +0200 Subject: [PATCH 6/6] trim trailing whitespace in update script --- deploy/update.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/update.ps1 b/deploy/update.ps1 index 00902b2..5b3103e 100644 --- a/deploy/update.ps1 +++ b/deploy/update.ps1 @@ -1,7 +1,7 @@ ############################################################################################################### ## ## Azure IPAM ZIP Deploy Updater Script -## +## ############################################################################################################### # Set minimum version requirements @@ -181,11 +181,11 @@ try { else { $appKind = $existingApp.Kind $appType = $($appKind.Split(",") -contains 'functionapp') ? 'Function' : 'App' - $isFunction = $appType -eq 'Function' ? $true : $false + $isFunction = $appType -eq 'Function' ? $true : $false } $appContainer = $existingApp.Kind.Split(",") -contains 'container' - + if ($appContainer) { $appType += "Container" }