mirror of
https://github.com/actions/runner-images.git
synced 2025-12-10 19:16:48 +00:00
Rewrite helper script for image generation (#8065)
This commit is contained in:
committed by
GitHub
parent
7fa63e2b95
commit
5fca0f3f62
@@ -56,14 +56,6 @@ In any case you will need these software installed:
|
|||||||
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; rm .\AzureCLI.msi
|
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; rm .\AzureCLI.msi
|
||||||
```
|
```
|
||||||
|
|
||||||
- [Az Powershell module](https://docs.microsoft.com/en-us/powershell/azure/install-az-ps).
|
|
||||||
|
|
||||||
Run this command in Powershell:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
Install-Module -Name Az -Repository PSGallery -Force
|
|
||||||
```
|
|
||||||
|
|
||||||
## Automated image generation
|
## Automated image generation
|
||||||
|
|
||||||
This repo bundles script that automates image generation process.
|
This repo bundles script that automates image generation process.
|
||||||
@@ -85,11 +77,6 @@ Then import [GenerateResourcesAndImage](../helpers/GenerateResourcesAndImage.ps1
|
|||||||
Import-Module .\helpers\GenerateResourcesAndImage.ps1
|
Import-Module .\helpers\GenerateResourcesAndImage.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
> :warning: When running `GenerateResourcesAndImage` in PowerShell 7.3, following command should be executed first:
|
|
||||||
> ```powershell
|
|
||||||
> $PSNativeCommandArgumentPassing = 'Legacy'
|
|
||||||
> ```
|
|
||||||
|
|
||||||
Finally, run `GenerateResourcesAndImage` function setting mandatory arguments: image type and where to create resources:
|
Finally, run `GenerateResourcesAndImage` function setting mandatory arguments: image type and where to create resources:
|
||||||
|
|
||||||
- `SubscriptionId` - your Azure Subscription ID
|
- `SubscriptionId` - your Azure Subscription ID
|
||||||
@@ -275,4 +262,3 @@ The scripts are copied to the VHD during the image generation process to the fol
|
|||||||
- **InternetExplorerConfiguration** - turns off the Internet Explorer Enhanced Security feature
|
- **InternetExplorerConfiguration** - turns off the Internet Explorer Enhanced Security feature
|
||||||
- **Msys2FirstLaunch.ps1** - initializes bash user profile in MSYS2
|
- **Msys2FirstLaunch.ps1** - initializes bash user profile in MSYS2
|
||||||
- **VSConfiguration.ps1** - performs initial Visual Studio configuration
|
- **VSConfiguration.ps1** - performs initial Visual Studio configuration
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
enum ImageType {
|
enum ImageType {
|
||||||
Windows2019 = 1
|
Windows2019 = 1
|
||||||
Windows2022 = 2
|
Windows2022 = 2
|
||||||
Ubuntu2004 = 3
|
Ubuntu2004 = 3
|
||||||
Ubuntu2204 = 4
|
Ubuntu2204 = 4
|
||||||
UbuntuMinimal = 5
|
UbuntuMinimal = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,55 +38,78 @@ Function Get-PackerTemplatePath {
|
|||||||
$imageTemplatePath = [IO.Path]::Combine($RepositoryRoot, "images", $relativeTemplatePath)
|
$imageTemplatePath = [IO.Path]::Combine($RepositoryRoot, "images", $relativeTemplatePath)
|
||||||
|
|
||||||
if (-not (Test-Path $imageTemplatePath)) {
|
if (-not (Test-Path $imageTemplatePath)) {
|
||||||
throw "Template for image '$ImageType' doesn't exist on path '$imageTemplatePath'"
|
throw "Template for image '$ImageType' doesn't exist on path '$imageTemplatePath'."
|
||||||
}
|
}
|
||||||
|
|
||||||
return $imageTemplatePath;
|
return $imageTemplatePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
Function Get-LatestCommit {
|
Function Show-LatestCommit {
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param()
|
param()
|
||||||
|
|
||||||
process {
|
process {
|
||||||
Write-Host "Latest commit:"
|
$latestCommit = (git --no-pager log --pretty=format:"Date: %cd; Commit: %H - %s; Author: %an <%ae>" -1)
|
||||||
git --no-pager log --pretty=format:"Date: %cd; Commit: %H - %s; Author: %an <%ae>" -1
|
Write-Host "Latest commit: $latestCommit."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Start-Sleep($seconds) {
|
||||||
|
$doneDT = (Get-Date).AddSeconds($seconds)
|
||||||
|
while ($doneDT -gt (Get-Date)) {
|
||||||
|
$secondsLeft = $doneDT.Subtract((Get-Date)).TotalSeconds
|
||||||
|
$percent = ($seconds - $secondsLeft) / $seconds * 100
|
||||||
|
Write-Progress -Activity "Sleeping" -Status "Sleeping..." -SecondsRemaining $secondsLeft -PercentComplete $percent
|
||||||
|
[System.Threading.Thread]::Sleep(500)
|
||||||
|
}
|
||||||
|
Write-Progress -Activity "Sleeping" -Status "Sleeping..." -SecondsRemaining 0 -Completed
|
||||||
|
}
|
||||||
|
|
||||||
Function GenerateResourcesAndImage {
|
Function GenerateResourcesAndImage {
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
A helper function to help generate an image.
|
A helper function to help generate an image.
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Creates Azure resources and kicks off a packer image generation for the selected image type.
|
This function will generate the Azure resources and image for the specified image type.
|
||||||
.PARAMETER SubscriptionId
|
.PARAMETER SubscriptionId
|
||||||
The Azure subscription Id where resources will be created.
|
The Azure subscription id where the Azure resources will be created.
|
||||||
.PARAMETER ResourceGroupName
|
.PARAMETER ResourceGroupName
|
||||||
The Azure resource group name where the Azure resources will be created.
|
The name of the resource group to create the Azure resources in.
|
||||||
.PARAMETER ImageGenerationRepositoryRoot
|
|
||||||
The root path of the image generation repository source.
|
|
||||||
.PARAMETER ImageType
|
.PARAMETER ImageType
|
||||||
The type of the image being generated. Valid options are: {"Windows2019", "Windows2022", "Ubuntu2004", "Ubuntu2204", "UbuntuMinimal"}.
|
The type of image to generate. Valid values are: Windows2019, Windows2022, Ubuntu2004, Ubuntu2204, UbuntuMinimal.
|
||||||
.PARAMETER AzureLocation
|
.PARAMETER AzureLocation
|
||||||
The location of the resources being created in Azure. For example "East US".
|
The Azure location where the Azure resources will be created. For example: "East US"
|
||||||
.PARAMETER Force
|
.PARAMETER ImageGenerationRepositoryRoot
|
||||||
Delete the resource group if it exists without user confirmation.
|
The root directory of the image generation repository. This is used to locate the packer template.
|
||||||
|
.PARAMETER SecondsToWaitForServicePrincipalSetup
|
||||||
|
The number of seconds to wait for the service principal to be setup. The default is 120 seconds.
|
||||||
.PARAMETER AzureClientId
|
.PARAMETER AzureClientId
|
||||||
Client id needs to be provided for optional authentication via service principal. Example: "11111111-1111-1111-1111-111111111111"
|
The Azure client id to use to authenticate with Azure. If not specified, the current user's credentials will be used.
|
||||||
.PARAMETER AzureClientSecret
|
.PARAMETER AzureClientSecret
|
||||||
Client secret needs to be provided for optional authentication via service principal. Example: "11111111-1111-1111-1111-111111111111"
|
The Azure client secret to use to authenticate with Azure. If not specified, the current user's credentials will be used.
|
||||||
.PARAMETER AzureTenantId
|
.PARAMETER AzureTenantId
|
||||||
Tenant needs to be provided for optional authentication via service principal. Example: "11111111-1111-1111-1111-111111111111"
|
The Azure tenant id to use to authenticate with Azure. If not specified, the current user's credentials will be used.
|
||||||
.PARAMETER RestrictToAgentIpAddress
|
.PARAMETER RestrictToAgentIpAddress
|
||||||
If set, access to the VM used by packer to generate the image is restricted to the public IP address this script is run from.
|
If set, access to the VM used by packer to generate the image is restricted to the public IP address this script is run from.
|
||||||
This parameter cannot be used in combination with the virtual_network_name packer parameter.
|
This parameter cannot be used in combination with the virtual_network_name packer parameter.
|
||||||
|
.PARAMETER Force
|
||||||
|
Delete the resource group if it exists without user confirmation.
|
||||||
|
.PARAMETER ReuseResourceGroup
|
||||||
|
Reuse the resource group if it exists without user confirmation.
|
||||||
.PARAMETER AllowBlobPublicAccess
|
.PARAMETER AllowBlobPublicAccess
|
||||||
The Azure storage account will be created with this option.
|
Allow public access to the generated image blob.
|
||||||
.PARAMETER EnableHttpsTrafficOnly
|
.PARAMETER EnableHttpsTrafficOnly
|
||||||
The Azure storage account will be created with this option.
|
Enable https traffic only for the generated image blob.
|
||||||
.PARAMETER OnError
|
.PARAMETER OnError
|
||||||
Specify how packer handles an error during image creation.
|
Specify how packer handles an error during image creation.
|
||||||
|
Options:
|
||||||
|
abort - abort immediately
|
||||||
|
ask - ask user for input
|
||||||
|
cleanup - attempt to cleanup and then abort
|
||||||
|
run-cleanup-provisioner - run the cleanup provisioner and then abort
|
||||||
|
The default is 'ask'.
|
||||||
|
.PARAMETER Tags
|
||||||
|
Tags to be applied to the Azure resources created.
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "shsamytest1" -ImageGenerationRepositoryRoot "C:\runner-images" -ImageType Ubuntu2004 -AzureLocation "East US"
|
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "shsamytest1" -ImageGenerationRepositoryRoot "C:\runner-images" -ImageType Ubuntu2004 -AzureLocation "East US"
|
||||||
#>
|
#>
|
||||||
@@ -102,7 +125,7 @@ Function GenerateResourcesAndImage {
|
|||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[string] $ImageGenerationRepositoryRoot = $pwd,
|
[string] $ImageGenerationRepositoryRoot = $pwd,
|
||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[int] $SecondsToWaitForServicePrincipalSetup = 30,
|
[int] $SecondsToWaitForServicePrincipalSetup = 120,
|
||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[string] $AzureClientId,
|
[string] $AzureClientId,
|
||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
@@ -110,219 +133,276 @@ Function GenerateResourcesAndImage {
|
|||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[string] $AzureTenantId,
|
[string] $AzureTenantId,
|
||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[Switch] $RestrictToAgentIpAddress,
|
[switch] $RestrictToAgentIpAddress,
|
||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[Switch] $Force,
|
[switch] $Force,
|
||||||
|
[Parameter(Mandatory = $False)]
|
||||||
|
[switch] $ReuseResourceGroup,
|
||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[bool] $AllowBlobPublicAccess = $False,
|
[bool] $AllowBlobPublicAccess = $False,
|
||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[bool] $EnableHttpsTrafficOnly = $False,
|
[bool] $EnableHttpsTrafficOnly = $False,
|
||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[ValidateSet("abort","ask","cleanup","run-cleanup-provisioner")]
|
[ValidateSet("abort", "ask", "cleanup", "run-cleanup-provisioner")]
|
||||||
[string] $OnError = "ask",
|
[string] $OnError = "ask",
|
||||||
[Parameter(Mandatory = $False)]
|
[Parameter(Mandatory = $False)]
|
||||||
[hashtable] $Tags
|
[hashtable] $Tags = @{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if ($Force -and $ReuseResourceGroup) {
|
||||||
|
throw "Force and ReuseResourceGroup cannot be used together."
|
||||||
|
}
|
||||||
|
|
||||||
|
Show-LatestCommit -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Validate packer is installed
|
||||||
|
$PackerBinary = Get-Command "packer"
|
||||||
|
if (-not ($PackerBinary)) {
|
||||||
|
throw "'packer' binary is not found on PATH."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get template path
|
||||||
|
$TemplatePath = Get-PackerTemplatePath -RepositoryRoot $ImageGenerationRepositoryRoot -ImageType $ImageType
|
||||||
|
Write-Debug "Template path: $TemplatePath."
|
||||||
|
|
||||||
|
# Prepare list of allowed inbound IP addresses
|
||||||
|
if ($RestrictToAgentIpAddress) {
|
||||||
|
$AgentIp = (Invoke-RestMethod http://ipinfo.io/json).ip
|
||||||
|
if (-not $AgentIp) {
|
||||||
|
throw "Unable to determine agent IP address."
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Access to packer generated VM will be restricted to agent IP Address: $AgentIp."
|
||||||
|
if ($TemplatePath.Contains("pkr.hcl")) {
|
||||||
|
if ($PSVersionTable.PSVersion.Major -eq 5) {
|
||||||
|
Write-Verbose "PowerShell 5 detected. Replacing double quotes with escaped double quotes in allowed inbound IP addresses."
|
||||||
|
$AllowedInboundIpAddresses = '[\"{0}\"]' -f $AgentIp
|
||||||
|
} else {
|
||||||
|
$AllowedInboundIpAddresses = '["{0}"]' -f $AgentIp
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$AllowedInboundIpAddresses = $AgentIp
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($TemplatePath.Contains("pkr.hcl")) {
|
||||||
|
$AllowedInboundIpAddresses = "[]"
|
||||||
|
} else {
|
||||||
|
$AllowedInboundIpAddresses = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Debug "Allowed inbound IP addresses: $AllowedInboundIpAddresses."
|
||||||
|
|
||||||
|
# Prepare tags
|
||||||
|
$TagsList = $Tags.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }
|
||||||
|
Write-Debug "Tags list: $TagsList."
|
||||||
|
$TagsJson = $Tags | ConvertTo-Json -Compress
|
||||||
|
if ($PSVersionTable.PSVersion.Major -eq 5) {
|
||||||
|
Write-Verbose "PowerShell 5 detected. Replacing double quotes with escaped double quotes in tags JSON."
|
||||||
|
$TagsJson = $TagsJson -replace '"', '\"'
|
||||||
|
}
|
||||||
|
Write-Debug "Tags JSON: $TagsJson."
|
||||||
|
if ($TemplatePath.Contains(".json")) {
|
||||||
|
Write-Verbose "Injecting tags into packer template."
|
||||||
|
if ($Tags) {
|
||||||
|
$BuilderScriptPathInjected = $TemplatePath.Replace(".json", "-temp.json")
|
||||||
|
$PackerTemplateContent = Get-Content -Path $TemplatePath | ConvertFrom-Json
|
||||||
|
$PackerTemplateContent.builders | Add-Member -Name "azure_tags" -Value $Tags -MemberType NoteProperty
|
||||||
|
$PackerTemplateContent | ConvertTo-Json -Depth 3 | Out-File -Encoding Ascii $BuilderScriptPathInjected
|
||||||
|
$TemplatePath = $BuilderScriptPathInjected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$InstallPassword = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||||
|
|
||||||
|
Write-Host "Validating packer template..."
|
||||||
|
& $PackerBinary validate `
|
||||||
|
"-var=client_id=fake" `
|
||||||
|
"-var=client_secret=fake" `
|
||||||
|
"-var=subscription_id=$($SubscriptionId)" `
|
||||||
|
"-var=tenant_id=fake" `
|
||||||
|
"-var=location=$($AzureLocation)" `
|
||||||
|
"-var=resource_group=$($ResourceGroupName)" `
|
||||||
|
"-var=storage_account=fake" `
|
||||||
|
"-var=install_password=$($InstallPassword)" `
|
||||||
|
"-var=allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" `
|
||||||
|
"-var=azure_tags=$($TagsJson)" `
|
||||||
|
$TemplatePath
|
||||||
|
|
||||||
|
if ($LastExitCode -ne 0) {
|
||||||
|
throw "Packer template validation failed."
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$builderScriptPath = Get-PackerTemplatePath -RepositoryRoot $ImageGenerationRepositoryRoot -ImageType $ImageType
|
# Login to Azure subscription
|
||||||
$ServicePrincipalClientSecret = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
|
||||||
$InstallPassword = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
|
||||||
|
|
||||||
if ([string]::IsNullOrEmpty($AzureClientId))
|
|
||||||
{
|
|
||||||
Connect-AzAccount
|
|
||||||
} else {
|
|
||||||
$AzSecureSecret = ConvertTo-SecureString $AzureClientSecret -AsPlainText -Force
|
|
||||||
$AzureAppCred = New-Object System.Management.Automation.PSCredential($AzureClientId, $AzSecureSecret)
|
|
||||||
Connect-AzAccount -ServicePrincipal -Credential $AzureAppCred -Tenant $AzureTenantId
|
|
||||||
}
|
|
||||||
Set-AzContext -SubscriptionId $SubscriptionId
|
|
||||||
|
|
||||||
$alreadyExists = $true;
|
|
||||||
try {
|
|
||||||
Get-AzResourceGroup -Name $ResourceGroupName
|
|
||||||
Write-Verbose "Resource group was found, will delete and recreate it."
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Verbose "Resource group was not found, will create it."
|
|
||||||
$alreadyExists = $false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($alreadyExists) {
|
|
||||||
if($Force -eq $true) {
|
|
||||||
# Cleanup the resource group if it already exitsted before
|
|
||||||
Remove-AzResourceGroup -Name $ResourceGroupName -Force
|
|
||||||
New-AzResourceGroup -Name $ResourceGroupName -Location $AzureLocation -Tag $tags
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$title = "Delete Resource Group"
|
|
||||||
$message = "The resource group you specified already exists. Do you want to clean it up?"
|
|
||||||
|
|
||||||
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
|
|
||||||
"Delete the resource group including all resources."
|
|
||||||
|
|
||||||
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
|
|
||||||
"Keep the resource group and continue."
|
|
||||||
|
|
||||||
$stop = New-Object System.Management.Automation.Host.ChoiceDescription "&Stop", `
|
|
||||||
"Stop the current action."
|
|
||||||
|
|
||||||
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no, $stop)
|
|
||||||
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
|
|
||||||
|
|
||||||
switch ($result)
|
|
||||||
{
|
|
||||||
0 { Remove-AzResourceGroup -Name $ResourceGroupName -Force; New-AzResourceGroup -Name $ResourceGroupName -Location $AzureLocation -Tag $tags }
|
|
||||||
1 { <# Do nothing #> }
|
|
||||||
2 { exit }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
New-AzResourceGroup -Name $ResourceGroupName -Location $AzureLocation -Tag $tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# This script should follow the recommended naming conventions for azure resources
|
|
||||||
$storageAccountName = if($ResourceGroupName.EndsWith("-rg")) {
|
|
||||||
$ResourceGroupName.Substring(0, $ResourceGroupName.Length -3)
|
|
||||||
} else { $ResourceGroupName }
|
|
||||||
|
|
||||||
# Resource group names may contain special characters, that are not allowed in the storage account name
|
|
||||||
$storageAccountName = $storageAccountName.Replace("-", "").Replace("_", "").Replace("(", "").Replace(")", "").ToLower()
|
|
||||||
$storageAccountName += "001"
|
|
||||||
|
|
||||||
|
|
||||||
# Storage Account Name can only be 24 characters long
|
|
||||||
if ($storageAccountName.Length -gt 24){
|
|
||||||
$storageAccountName = $storageAccountName.Substring(0, 24)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($tags) {
|
|
||||||
New-AzStorageAccount -ResourceGroupName $ResourceGroupName -AccountName $storageAccountName -Location $AzureLocation -SkuName "Standard_LRS" -AllowBlobPublicAccess $AllowBlobPublicAccess -EnableHttpsTrafficOnly $EnableHttpsTrafficOnly -MinimumTlsVersion "TLS1_2" -Tag $tags
|
|
||||||
} else {
|
|
||||||
New-AzStorageAccount -ResourceGroupName $ResourceGroupName -AccountName $storageAccountName -Location $AzureLocation -SkuName "Standard_LRS" -AllowBlobPublicAccess $AllowBlobPublicAccess -EnableHttpsTrafficOnly $EnableHttpsTrafficOnly -MinimumTlsVersion "TLS1_2"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([string]::IsNullOrEmpty($AzureClientId)) {
|
if ([string]::IsNullOrEmpty($AzureClientId)) {
|
||||||
# Interactive authentication: A service principal is created during runtime.
|
Write-Verbose "No AzureClientId was provided, will use interactive login."
|
||||||
$spDisplayName = [System.GUID]::NewGuid().ToString().ToUpper()
|
az login --output none
|
||||||
$startDate = Get-Date
|
|
||||||
$endDate = $startDate.AddYears(1)
|
|
||||||
|
|
||||||
if ('Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential' -as [type]) {
|
|
||||||
$credentials = [Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential]@{
|
|
||||||
StartDate = $startDate
|
|
||||||
EndDate = $endDate
|
|
||||||
Password = $ServicePrincipalClientSecret
|
|
||||||
}
|
|
||||||
$sp = New-AzADServicePrincipal -DisplayName $spDisplayName -PasswordCredential $credentials
|
|
||||||
$spClientId = $sp.ApplicationId
|
|
||||||
$azRoleParam = @{
|
|
||||||
RoleDefinitionName = "Contributor"
|
|
||||||
ServicePrincipalName = $spClientId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential' -as [type]) {
|
|
||||||
$credentials = [Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential]@{
|
|
||||||
StartDateTime = $startDate
|
|
||||||
EndDateTime = $endDate
|
|
||||||
}
|
|
||||||
$sp = New-AzADServicePrincipal -DisplayName $spDisplayName
|
|
||||||
$appCred = New-AzADAppCredential -ApplicationId $sp.AppId -PasswordCredentials $credentials
|
|
||||||
$spClientId = $sp.AppId
|
|
||||||
$azRoleParam = @{
|
|
||||||
RoleDefinitionName = "Contributor"
|
|
||||||
PrincipalId = $sp.Id
|
|
||||||
}
|
|
||||||
$ServicePrincipalClientSecret = $appCred.SecretText
|
|
||||||
}
|
|
||||||
|
|
||||||
Start-Sleep -Seconds $SecondsToWaitForServicePrincipalSetup
|
|
||||||
New-AzRoleAssignment @azRoleParam
|
|
||||||
Start-Sleep -Seconds $SecondsToWaitForServicePrincipalSetup
|
|
||||||
$sub = Get-AzSubscription -SubscriptionId $SubscriptionId
|
|
||||||
$tenantId = $sub.TenantId
|
|
||||||
|
|
||||||
# Remove ADPrincipal after the script completed
|
|
||||||
$isCleanupADPrincipal = $true
|
|
||||||
} else {
|
} else {
|
||||||
# Parametrized Authentication via given service principal: The service principal with the data provided via the command line
|
Write-Verbose "AzureClientId was provided, will use service principal login."
|
||||||
# is used for all authentication purposes.
|
az login --service-principal --username $AzureClientId --password $AzureClientSecret --tenant $AzureTenantId --output none
|
||||||
$spClientId = $AzureClientId
|
}
|
||||||
$credentials = $AzureAppCred
|
az account set --subscription $SubscriptionId
|
||||||
$ServicePrincipalClientSecret = $AzureClientSecret
|
if ($LastExitCode -ne 0) {
|
||||||
$tenantId = $AzureTenantId
|
throw "Failed to login to Azure subscription '$SubscriptionId'."
|
||||||
}
|
}
|
||||||
|
|
||||||
Get-LatestCommit -ErrorAction SilentlyContinue
|
# Check resource group
|
||||||
|
$ResourceGroupExists = [System.Convert]::ToBoolean((az group exists --name $ResourceGroupName));
|
||||||
$packerBinary = Get-Command "packer"
|
if ($ResourceGroupExists) {
|
||||||
if (-not ($packerBinary)) {
|
Write-Verbose "Resource group '$ResourceGroupName' already exists."
|
||||||
throw "'packer' binary is not found on PATH"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($RestrictToAgentIpAddress) {
|
# Remove resource group if it exists and we are not reusing it
|
||||||
$AgentIp = (Invoke-RestMethod http://ipinfo.io/json).ip
|
if ($ResourceGroupExists -and -not $ReuseResourceGroup) {
|
||||||
Write-Host "Restricting access to packer generated VM to agent IP Address: $AgentIp"
|
if ($Force) {
|
||||||
}
|
# Delete and recreate the resource group
|
||||||
|
Write-Host "Deleting resource group '$ResourceGroupName'..."
|
||||||
if ($builderScriptPath.Contains("pkr.hcl")) {
|
az group delete --name $ResourceGroupName --yes --output none
|
||||||
if ($AgentIp) {
|
if ($LastExitCode -ne 0) {
|
||||||
$AgentIp = '[ \"{0}\" ]' -f $AgentIp
|
throw "Failed to delete resource group '$ResourceGroupName'."
|
||||||
|
}
|
||||||
|
Write-Host "Resource group '$ResourceGroupName' was deleted."
|
||||||
|
$ResourceGroupExists = $false
|
||||||
} else {
|
} else {
|
||||||
$AgentIp = "[]"
|
# Resource group already exists, ask the user what to do
|
||||||
}
|
$title = "Resource group '$ResourceGroupName' already exists"
|
||||||
if (-not $Tags) {
|
$message = "Do you want to delete the resource group and all resources in it?"
|
||||||
$Tags = @{}
|
|
||||||
|
$options = @(
|
||||||
|
[System.Management.Automation.Host.ChoiceDescription]::new("&Yes", "Delete the resource group and all resources in it."),
|
||||||
|
[System.Management.Automation.Host.ChoiceDescription]::new("&No", "Keep the resource group and continue."),
|
||||||
|
[System.Management.Automation.Host.ChoiceDescription]::new("&Abort", "Abort execution.")
|
||||||
|
)
|
||||||
|
$result = $Host.UI.PromptForChoice($title, $message, $options, 0)
|
||||||
|
|
||||||
|
switch ($result) {
|
||||||
|
0 {
|
||||||
|
# Delete and recreate the resource group
|
||||||
|
Write-Host "Deleting resource group '$ResourceGroupName'..."
|
||||||
|
az group delete --name $ResourceGroupName --yes
|
||||||
|
if ($LastExitCode -ne 0) {
|
||||||
|
throw "Failed to delete resource group '$ResourceGroupName'."
|
||||||
|
}
|
||||||
|
Write-Host "Resource group '$ResourceGroupName' was deleted."
|
||||||
|
$ResourceGroupExists = $false
|
||||||
|
}
|
||||||
|
1 {
|
||||||
|
# Keep the resource group and continue
|
||||||
|
}
|
||||||
|
2 {
|
||||||
|
# Stop the current action
|
||||||
|
Write-Error "User stopped the action."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($builderScriptPath.Contains(".json")) {
|
# Create resource group
|
||||||
if ($Tags) {
|
if (-not $ResourceGroupExists) {
|
||||||
$builderScriptPath_temp = $builderScriptPath.Replace(".json", "-temp.json")
|
Write-Host "Creating resource group '$ResourceGroupName' in location '$AzureLocation'..."
|
||||||
$packer_script = Get-Content -Path $builderScriptPath | ConvertFrom-Json
|
if ($TagsList) {
|
||||||
$packer_script.builders | Add-Member -Name "azure_tags" -Value $Tags -MemberType NoteProperty
|
az group create --name $ResourceGroupName --location $AzureLocation --tags $TagsList --query id
|
||||||
$packer_script | ConvertTo-Json -Depth 3 | Out-File -Encoding Ascii $builderScriptPath_temp
|
} else {
|
||||||
$builderScriptPath = $builderScriptPath_temp
|
az group create --name $ResourceGroupName --location $AzureLocation --query id
|
||||||
|
}
|
||||||
|
if ($LastExitCode -ne 0) {
|
||||||
|
throw "Failed to create resource group '$ResourceGroupName'."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$TagsJson = $Tags | ConvertTo-Json -Compress
|
# Generate proper name for the storage account that follows the recommended naming conventions for azure resources
|
||||||
if ($PSVersionTable.PSVersion.Major -eq 5) {
|
$StorageAccountName = $ResourceGroupName
|
||||||
Write-Verbose "PowerShell 5 detected. Replacing double quotes with escaped double quotes in tags JSON."
|
if ($ResourceGroupName.EndsWith("-rg")) {
|
||||||
$TagsJson = $TagsJson -replace '"', '\"'
|
$StorageAccountName = $ResourceGroupName.Substring(0, $ResourceGroupName.Length - 3)
|
||||||
|
}
|
||||||
|
$StorageAccountName = $StorageAccountName.Replace("-", "").Replace("_", "").Replace("(", "").Replace(")", "").ToLower()
|
||||||
|
$StorageAccountName += "001"
|
||||||
|
if ($StorageAccountName.Length -gt 24) {
|
||||||
|
$StorageAccountName = $StorageAccountName.Substring(0, 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$StorageAccountId = (az storage account show --name $StorageAccountName --resource-group $ResourceGroupName --query id 2>$null)
|
||||||
|
$StorageAccountExists = "$StorageAccountId" -ne ""
|
||||||
|
} catch {
|
||||||
|
$StorageAccountExists = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
& $packerBinary build -on-error="$($OnError)" `
|
# Create storage account
|
||||||
-var "client_id=$($spClientId)" `
|
if ($StorageAccountExists) {
|
||||||
-var "client_secret=$($ServicePrincipalClientSecret)" `
|
Write-Verbose "Storage account '$StorageAccountName' already exists."
|
||||||
|
} else {
|
||||||
|
Write-Host "Creating storage account..."
|
||||||
|
if ($TagsList) {
|
||||||
|
az storage account create --name $StorageAccountName --resource-group $ResourceGroupName --location $AzureLocation --sku Standard_LRS --allow-blob-public-access $AllowBlobPublicAccess --https-only $EnableHttpsTrafficOnly --min-tls-version TLS1_2 --tags $TagsList --query id
|
||||||
|
} else {
|
||||||
|
az storage account create --name $StorageAccountName --resource-group $ResourceGroupName --location $AzureLocation --sku Standard_LRS --allow-blob-public-access $AllowBlobPublicAccess --https-only $EnableHttpsTrafficOnly --min-tls-version TLS1_2 --query id
|
||||||
|
}
|
||||||
|
if ($LastExitCode -ne 0) {
|
||||||
|
throw "Failed to create storage account '$StorageAccountName'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create service principal
|
||||||
|
if ([string]::IsNullOrEmpty($AzureClientId)) {
|
||||||
|
Write-Host "Creating service principal for packer..."
|
||||||
|
$ADCleanupRequired = $true
|
||||||
|
|
||||||
|
$ServicePrincipalName = "packer-" + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||||
|
$ServicePrincipal = az ad sp create-for-rbac --name $ServicePrincipalName --role Contributor --scopes /subscriptions/$SubscriptionId --only-show-errors | ConvertFrom-Json
|
||||||
|
if ($LastExitCode -ne 0) {
|
||||||
|
throw "Failed to create service principal '$ServicePrincipalName'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$ServicePrincipalAppId = $ServicePrincipal.appId
|
||||||
|
$ServicePrincipalPassword = $ServicePrincipal.password
|
||||||
|
$TenantId = $ServicePrincipal.tenant
|
||||||
|
|
||||||
|
Write-Verbose "Waiting for service principal to propagate..."
|
||||||
|
Start-Sleep $SecondsToWaitForServicePrincipalSetup
|
||||||
|
Write-Host "Service principal created with id '$ServicePrincipalAppId'. It will be deleted after the build."
|
||||||
|
} else {
|
||||||
|
$ServicePrincipalAppId = $AzureClientId
|
||||||
|
$ServicePrincipalPassword = $AzureClientSecret
|
||||||
|
$TenantId = $AzureTenantId
|
||||||
|
}
|
||||||
|
Write-Debug "Service principal app id: $ServicePrincipalAppId."
|
||||||
|
Write-Debug "Tenant id: $TenantId."
|
||||||
|
|
||||||
|
& $PackerBinary build -on-error="$($OnError)" `
|
||||||
|
-var "client_id=$($ServicePrincipalAppId)" `
|
||||||
|
-var "client_secret=$($ServicePrincipalPassword)" `
|
||||||
-var "subscription_id=$($SubscriptionId)" `
|
-var "subscription_id=$($SubscriptionId)" `
|
||||||
-var "tenant_id=$($tenantId)" `
|
-var "tenant_id=$($TenantId)" `
|
||||||
-var "location=$($AzureLocation)" `
|
-var "location=$($AzureLocation)" `
|
||||||
-var "resource_group=$($ResourceGroupName)" `
|
-var "resource_group=$($ResourceGroupName)" `
|
||||||
-var "storage_account=$($storageAccountName)" `
|
-var "storage_account=$($StorageAccountName)" `
|
||||||
-var "install_password=$($InstallPassword)" `
|
-var "install_password=$($InstallPassword)" `
|
||||||
-var "allowed_inbound_ip_addresses=$($AgentIp)" `
|
-var "allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" `
|
||||||
-var "azure_tags=$($TagsJson)" `
|
-var "azure_tags=$($TagsJson)" `
|
||||||
$builderScriptPath
|
$TemplatePath
|
||||||
}
|
|
||||||
catch {
|
if ($LastExitCode -ne 0) {
|
||||||
|
throw "Failed to build image."
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
Write-Error $_
|
Write-Error $_
|
||||||
}
|
} finally {
|
||||||
finally {
|
Write-Verbose "`nCleaning up..."
|
||||||
|
|
||||||
# Remove ADServicePrincipal and ADApplication
|
# Remove ADServicePrincipal and ADApplication
|
||||||
if ($isCleanupADPrincipal) {
|
if ($ADCleanupRequired) {
|
||||||
Write-Host "`nRemoving ${spDisplayName}/${spClientId}:"
|
Write-Host "Removing ADServicePrincipal..."
|
||||||
if (Get-AzADServicePrincipal -DisplayName $spDisplayName) {
|
if (az ad sp show --id $ServicePrincipalAppId --query id) {
|
||||||
Write-Host " [+] ADServicePrincipal"
|
az ad sp delete --id $ServicePrincipalAppId
|
||||||
Remove-AzADServicePrincipal -DisplayName $spDisplayName -Confirm:$false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Get-AzADApplication -DisplayName $spDisplayName) {
|
Write-Host "Removing ADApplication..."
|
||||||
Write-Host " [+] ADApplication"
|
if (az ad app show --id $ServicePrincipalAppId --query id) {
|
||||||
Remove-AzADApplication -DisplayName $spDisplayName -Confirm:$false
|
az ad app delete --id $ServicePrincipalAppId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Write-Verbose "Cleanup completed."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user