diff --git a/.github/workflows/powershell-tests.yml b/.github/workflows/powershell-tests.yml index 88b7a88eda..7d33170ea0 100644 --- a/.github/workflows/powershell-tests.yml +++ b/.github/workflows/powershell-tests.yml @@ -7,6 +7,7 @@ on: branches: [ main ] paths: - 'helpers/software-report-base/**' + - 'images.CI/linux-and-win/**' jobs: powershell-tests: @@ -22,4 +23,9 @@ jobs: run: | $ErrorActionPreference = "Stop" Invoke-Pester -Output Detailed "helpers/software-report-base/tests" - \ No newline at end of file + + - name: Run linux-and-win scripts tests + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + Invoke-Pester -Output Detailed "images.CI/linux-and-win/tests" diff --git a/docs/create-image-and-azure-resources.md b/docs/create-image-and-azure-resources.md index ce84d62c6c..eca389ab6a 100644 --- a/docs/create-image-and-azure-resources.md +++ b/docs/create-image-and-azure-resources.md @@ -235,7 +235,6 @@ The following variables are required to be passed to the Packer process: | `client_id` | `ARM_CLIENT_ID` | The Active Directory service principal associated with your builder. | `client_secret` | `ARM_CLIENT_SECRET` | The password or secret for your service principal; may be omitted if `client_cert_path` is set. | `client_cert_path` | `ARM_CLIENT_CERT_PATH` | The location of a PEM file containing a certificate and private key for the service principal; may be omitted if `client_secret` is set. -| `location` | `ARM_RESOURCE_LOCATION` | The Azure datacenter in which your VM will be built. | `managed_image_resource_group_name` | `ARM_RESOURCE_GROUP` | The resource group under which the final artifact will be stored. ### Optional variables @@ -243,10 +242,11 @@ The following variables are required to be passed to the Packer process: The following variables are optional: - `managed_image_name` - the name of the managed image to create. If not specified, "Runner-Image-{{ImageType}}" will be used; -- `build_resource_group_name` - specify an existing resource group to run the build in; by default, a temporary resource group will be created and destroyed as part of the build; if you do not have permission to do so, use `build_resource_group_name` to specify an existing resource group to run the build in; +- `build_resource_group_name` - specify an existing resource group to run the build in; by default, a temporary resource group will be created and destroyed as part of the build; if you do not have permission to do so, use `build_resource_group_name` to specify an existing resource group to run the build in; not allowed to be used in combination with `location` or `temp_resource_group_name`; - `object_id` - the object ID for the AAD SP; will be derived from the oAuth token if empty; - `tenant_id` - the Active Directory tenant identifier with which your `client_id` and `subscription_id` are associated; if not specified, `tenant_id` will be looked up using `subscription_id`; -- `temp_resource_group_name` - the name assigned to the temporary resource group created during the build; if this value is not set, a random value will be assigned; this resource group is deleted at the end of the build; +- `location` (env var `ARM_RESOURCE_LOCATION`) - The Azure datacenter in which your VM will be built; required if the build is in a temporary resource group; not allowed to be used in combination with `build_resource_group_name`; +- `temp_resource_group_name` - the name assigned to the temporary resource group created during the build; if this value is not set, a random value will be assigned; this resource group is deleted at the end of the build; not allowed to be used in combination with `build_resource_group_name`; - `private_virtual_network_with_public_ip` - this value allows you to set a `virtual_network_name` and obtain a public IP; if this value is not set and `virtual_network_name` is defined, Packer is only allowed to be executed from a host on the same subnet / virtual network; - `virtual_network_name` - use a pre-existing virtual network for the VM; this option enables private communication with the VM, no public IP address is used or provisioned (unless you set `private_virtual_network_with_public_ip`); - `virtual_network_resource_group_name` - if `virtual_network_name` is set, this value may also be set; if `virtual_network_name` is set, and this value is not set, the builder attempts to determine the resource group containing the virtual network; if the resource group cannot be found, or it cannot be disambiguated, this value should be set; diff --git a/images.CI/linux-and-win/build-image.ps1 b/images.CI/linux-and-win/build-image.ps1 index fa4dac9191..8a5fe684d4 100644 --- a/images.CI/linux-and-win/build-image.ps1 +++ b/images.CI/linux-and-win/build-image.ps1 @@ -1,12 +1,14 @@ +[CmdletBinding(DefaultParameterSetName = 'TempResourceGroup')] param( [String] [Parameter (Mandatory=$true)] $TemplatePath, [String] [Parameter (Mandatory=$true)] $BuildTemplateName, [String] [Parameter (Mandatory=$true)] $ClientId, [String] [Parameter (Mandatory=$false)] $ClientSecret, - [String] [Parameter (Mandatory=$true)] $Location, + [String] [Parameter (Mandatory=$true, ParameterSetName = 'TempResourceGroup')] $Location, [String] [Parameter (Mandatory=$true)] $ImageName, [String] [Parameter (Mandatory=$true)] $ImageResourceGroupName, - [String] [Parameter (Mandatory=$true)] $TempResourceGroupName, + [String] [Parameter (Mandatory=$true, ParameterSetName = 'TempResourceGroup')] $TempResourceGroupName, + [String] [Parameter (Mandatory=$true, ParameterSetName = 'ExistingResourceGroup')] $ExistingResourceGroupName, [String] [Parameter (Mandatory=$true)] $SubscriptionId, [String] [Parameter (Mandatory=$true)] $TenantId, [String] [Parameter (Mandatory=$true)] $ImageOS, # e.g. "ubuntu22", "ubuntu24" or "win22", "win25" @@ -40,6 +42,20 @@ $SensitiveData = @( $azure_tags = $Tags | ConvertTo-Json -Compress +function Add-PackerVariableFlag { + param( + [String[]]$packerVariables + ) + + $result = @() + + foreach ($packerVariable in $packerVariables) { + $result += "-var", $packerVariable + } + + return $result +} + Write-Host "Show Packer Version" packer --version @@ -49,24 +65,46 @@ packer plugins install github.com/hashicorp/azure $pluginVersion Write-Host "Validate packer template" packer validate -syntax-only -only "$buildName*" $TemplatePath +$packerVariablesList = Add-PackerVariableFlag -packerVariables @( + "client_id=$ClientId", + "client_secret=$ClientSecret", + "install_password=$InstallPassword", + "image_os=$ImageOS", + "managed_image_name=$ImageName", + "managed_image_resource_group_name=$ImageResourceGroupName", + "subscription_id=$SubscriptionId", + "tenant_id=$TenantId", + "virtual_network_name=$VirtualNetworkName", + "virtual_network_resource_group_name=$VirtualNetworkRG", + "virtual_network_subnet_name=$VirtualNetworkSubnet", + "allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)", + "use_azure_cli_auth=$UseAzureCliAuth", + "azure_tags=$azure_tags" + ) + +switch ($PSCmdlet.ParameterSetName) { + 'TempResourceGroup' { + Write-Host "Use temporary resource group $TempResourceGroupName" + $packerVariablesList += Add-PackerVariableFlag -packerVariables @( + "temp_resource_group_name=$TempResourceGroupName", + "location=$Location" + ) + + break + } + 'ExistingResourceGroup' { + Write-Host "Use existing resource group $ExistingResourceGroupName" + $packerVariablesList += Add-PackerVariableFlag -packerVariables @( + "build_resource_group_name=$ExistingResourceGroupName" + ) + + break + } +} + Write-Host "Build $buildName VM" packer build -only "$buildName*" ` - -var "client_id=$ClientId" ` - -var "client_secret=$ClientSecret" ` - -var "install_password=$InstallPassword" ` - -var "location=$Location" ` - -var "image_os=$ImageOS" ` - -var "managed_image_name=$ImageName" ` - -var "managed_image_resource_group_name=$ImageResourceGroupName" ` - -var "subscription_id=$SubscriptionId" ` - -var "temp_resource_group_name=$TempResourceGroupName" ` - -var "tenant_id=$TenantId" ` - -var "virtual_network_name=$VirtualNetworkName" ` - -var "virtual_network_resource_group_name=$VirtualNetworkRG" ` - -var "virtual_network_subnet_name=$VirtualNetworkSubnet" ` - -var "allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" ` - -var "use_azure_cli_auth=$UseAzureCliAuth" ` - -var "azure_tags=$azure_tags" ` + @packerVariablesList ` -color=false ` $TemplatePath ` | Where-Object { diff --git a/images.CI/linux-and-win/tests/build-image.Tests.ps1 b/images.CI/linux-and-win/tests/build-image.Tests.ps1 new file mode 100644 index 0000000000..1b8bd02cd0 --- /dev/null +++ b/images.CI/linux-and-win/tests/build-image.Tests.ps1 @@ -0,0 +1,103 @@ +Describe "build-image.ps1 parameter sets" { + BeforeAll { + $scriptPath = (Resolve-Path (Join-Path $PSScriptRoot "..\build-image.ps1")).Path + $command = Get-Command -Name $scriptPath + } + + It "defines exactly two parameter sets" { + $command.ParameterSets | Should -HaveCount 2 + } + + It "requires temp resource group parameters in TempResourceGroup set" { + $tempSet = $command.ParameterSets | Where-Object Name -eq "TempResourceGroup" + $tempSet | Should -Not -BeNullOrEmpty + + ($tempSet.Parameters | Where-Object Name -eq "Location") | Should -Not -BeNullOrEmpty + ($tempSet.Parameters | Where-Object Name -eq "Location").IsMandatory | Should -BeTrue + + ($tempSet.Parameters | Where-Object Name -eq "TempResourceGroupName") | Should -Not -BeNullOrEmpty + ($tempSet.Parameters | Where-Object Name -eq "TempResourceGroupName").IsMandatory | Should -BeTrue + + $tempSet.Parameters.Name | Should -Not -Contain "ExistingResourceGroupName" + } + + It "requires existing resource group parameter in ExistingResourceGroup set" { + $existingSet = $command.ParameterSets | Where-Object Name -eq "ExistingResourceGroup" + $existingSet | Should -Not -BeNullOrEmpty + + ($existingSet.Parameters | Where-Object Name -eq "ExistingResourceGroupName") | Should -Not -BeNullOrEmpty + ($existingSet.Parameters | Where-Object Name -eq "ExistingResourceGroupName").IsMandatory | Should -BeTrue + + $existingSet.Parameters.Name | Should -Not -Contain "Location" + $existingSet.Parameters.Name | Should -Not -Contain "TempResourceGroupName" + } +} + +Describe "build-image.ps1 switch" { + BeforeAll { + $scriptPath = (Resolve-Path (Join-Path $PSScriptRoot "..\build-image.ps1")).Path + + Mock -CommandName Write-Host + + $global:packerInvocations = @() + Mock -CommandName packer -MockWith { + param( + [Parameter(ValueFromRemainingArguments = $true)] + [string[]] $arguments + ) + + $global:packerInvocations += ,$arguments + "mocked packer output" + } + } + + Context "TempResourceGroup" { + It "calls packer build with temp resource group parameters" { + + & $scriptPath -TemplatePath ".\" ` + -BuildTemplateName "template.TestBuild" ` + -TempResourceGroupName "TestTempRG" ` + -ClientId "TestClientId" ` + -Location "TestLocation" ` + -ImageName "TestImage" ` + -ImageResourceGroupName "TestImageRG" ` + -SubscriptionId "TestSubId" ` + -TenantId "TestTenantId" ` + -ImageOS "TestOS" | Out-Null + + Should -Invoke -CommandName Write-Host -Times 1 -Exactly -ParameterFilter { + $Object -eq "Use temporary resource group TestTempRG" + } + + Should -Invoke -CommandName packer -Times 1 -Exactly -ParameterFilter { + $arguments -contains "temp_resource_group_name=TestTempRG" -and + $arguments -contains "location=TestLocation" -and + $arguments -contains "-var" + } + } + } + + Context "ExistingResourceGroup" { + It "calls packer build with existing resource group parameters" { + + & $scriptPath -TemplatePath ".\" ` + -BuildTemplateName "template.TestBuild" ` + -ExistingResourceGroupName "TestExistingRG" ` + -ClientId "TestClientId" ` + -ImageName "TestImage" ` + -ImageResourceGroupName "TestImageRG" ` + -SubscriptionId "TestSubId" ` + -TenantId "TestTenantId" ` + -ImageOS "TestOS" | Out-Null + + Should -Invoke -CommandName Write-Host -Times 1 -Exactly -ParameterFilter { + $Object -eq "Use existing resource group TestExistingRG" + } + + Should -Invoke -CommandName packer -Times 1 -Exactly -ParameterFilter { + $arguments -contains "build_resource_group_name=TestExistingRG" -and + $arguments -contains "-var" + } + } + } +}