vcpkg/scripts/azure-pipelines/windows/create-vmss.ps1
Billy Robert O'Neal III 070a18974b Change supporting infrastructure to use Azure Virtual Machine Scale Sets for vcpkg's PR builds, which should both improve our PR build times and reduce Azure spending by shutting down machines when they aren't being used.
Included is a script that sets up all vcpkg's Azure infrastructure for Windows PR tests, and several updates to baselines. The baseline updates are generally caused by an updated copy of the MSVC++ compiler caused by updating the VMs, but some are caused by missed failures only detected now because this did a cleared out archives directory first.

Some of the build infrastructure isn't what I'd call 'pretty' (e.g. we're split into more scripts and such than I'd like) but this mirrors how our existing PR system works.

It is expected that the existing vcpkg Windows PR system will hate these baseline updates so we'll need to merge this, then remove that (duplicate) workflow immediately afterwards, then delete all the Windows VMs powering the old infrastructure.
2020-04-21 17:12:21 -07:00

459 lines
14 KiB
PowerShell

# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
#
#
<#
.SYNOPSIS
Creates a Windows virtual machine scale set, set up for vcpkg's CI.
.DESCRIPTION
create-vmss.ps1 creates an Azure Windows VM scale set, set up for vcpkg's CI
system. See https://docs.microsoft.com/en-us/azure/virtual-machine-scale-sets/overview
for more information.
This script assumes you have installed Azure tools into PowerShell by following the instructions
at https://docs.microsoft.com/en-us/powershell/azure/install-az-ps?view=azps-3.6.1
or are running from Azure Cloud Shell.
#>
$Location = 'SouthCentralUS'
$Prefix = 'PrWin-' + (Get-Date -Format 'yyyy-MM-dd')
$VMSize = 'Standard_F16s_v2'
$ProtoVMName = 'PROTOTYPE'
$LiveVMPrefix = 'BUILD'
$WindowsServerSku = '2019-Datacenter'
$InstalledDiskSizeInGB = 1024
$ErrorActionPreference = 'Stop'
$ProgressActivity = 'Creating Scale Set'
$TotalProgress = 12
$CurrentProgress = 1
<#
.SYNOPSIS
Returns whether there's a name collision in the resource group.
.DESCRIPTION
Find-ResourceGroupNameCollision takes a list of resources, and checks if $Test
collides names with any of the resources.
.PARAMETER Test
The name to test.
.PARAMETER Resources
The list of resources.
#>
function Find-ResourceGroupNameCollision {
[CmdletBinding()]
Param([string]$Test, $Resources)
foreach ($resource in $Resources) {
if ($resource.ResourceGroupName -eq $Test) {
return $true
}
}
return $false
}
<#
.SYNOPSIS
Attempts to find a name that does not collide with any resources in the resource group.
.DESCRIPTION
Find-ResourceGroupName takes a set of resources from Get-AzResourceGroup, and finds the
first name in {$Prefix, $Prefix-1, $Prefix-2, ...} such that the name doesn't collide with
any of the resources in the resource group.
.PARAMETER Prefix
The prefix of the final name; the returned name will be of the form "$Prefix(-[1-9][0-9]*)?"
#>
function Find-ResourceGroupName {
[CmdletBinding()]
Param([string] $Prefix)
$resources = Get-AzResourceGroup
$result = $Prefix
$suffix = 0
while (Find-ResourceGroupNameCollision -Test $result -Resources $resources) {
$suffix++
$result = "$Prefix-$suffix"
}
return $result
}
<#
.SYNOPSIS
Creates a randomly generated password.
.DESCRIPTION
New-Password generates a password, randomly, of length $Length, containing
only alphanumeric characters (both uppercase and lowercase).
.PARAMETER Length
The length of the returned password.
#>
function New-Password {
Param ([int] $Length = 32)
$Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
$result = ''
for ($idx = 0; $idx -lt $Length; $idx++) {
# NOTE: this should probably use RNGCryptoServiceProvider
$result += $Chars[(Get-Random -Minimum 0 -Maximum $Chars.Length)]
}
return $result
}
<#
.SYNOPSIS
Waits for the shutdown of the specified resource.
.DESCRIPTION
Wait-Shutdown takes a VM, and checks if there's a 'PowerState/stopped'
code; if there is, it returns. If there isn't, it waits ten seconds and
tries again.
.PARAMETER ResourceGroupName
The name of the resource group to look up the VM in.
.PARAMETER Name
The name of the virtual machine to wait on.
#>
function Wait-Shutdown {
[CmdletBinding()]
Param([string]$ResourceGroupName, [string]$Name)
Write-Host "Waiting for $Name to stop..."
while ($true) {
$Vm = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $Name -Status
$highestStatus = $Vm.Statuses.Count
for ($idx = 0; $idx -lt $highestStatus; $idx++) {
if ($Vm.Statuses[$idx].Code -eq 'PowerState/stopped') {
return
}
}
Write-Host "... not stopped yet, sleeping for 10 seconds"
Start-Sleep -Seconds 10
}
}
<#
.SYNOPSIS
Sanitizes a name to be used in a storage account.
.DESCRIPTION
Sanitize-Name takes a string, and removes all of the '-'s and
lowercases the string, since storage account names must have no
'-'s and must be completely lowercase alphanumeric. It then makes
certain that the length of the string is not greater than 24,
since that is invalid.
.PARAMETER RawName
The name to sanitize.
#>
function Sanitize-Name {
[CmdletBinding()]
Param(
[string]$RawName
)
$result = $RawName.Replace('-', '').ToLowerInvariant()
if ($result.Length -gt 24) {
Write-Error 'Sanitized name for storage account $result was too long.'
}
return $result
}
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Creating resource group' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$ResourceGroupName = Find-ResourceGroupName $Prefix
$AdminPW = New-Password
New-AzResourceGroup -Name $ResourceGroupName -Location $Location
$AdminPWSecure = ConvertTo-SecureString $AdminPW -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ("AdminUser", $AdminPWSecure)
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Creating virtual network' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$allowHttp = New-AzNetworkSecurityRuleConfig `
-Name AllowHTTP `
-Description 'Allow HTTP(S)' `
-Access Allow `
-Protocol Tcp `
-Direction Outbound `
-Priority 1008 `
-SourceAddressPrefix * `
-SourcePortRange * `
-DestinationAddressPrefix * `
-DestinationPortRange @(80, 443)
$allowDns = New-AzNetworkSecurityRuleConfig `
-Name AllowDNS `
-Description 'Allow DNS' `
-Access Allow `
-Protocol * `
-Direction Outbound `
-Priority 1009 `
-SourceAddressPrefix * `
-SourcePortRange * `
-DestinationAddressPrefix * `
-DestinationPortRange 53
$allowStorage = New-AzNetworkSecurityRuleConfig `
-Name AllowStorage `
-Description 'Allow Storage' `
-Access Allow `
-Protocol * `
-Direction Outbound `
-Priority 1010 `
-SourceAddressPrefix VirtualNetwork `
-SourcePortRange * `
-DestinationAddressPrefix Storage `
-DestinationPortRange *
$denyEverythingElse = New-AzNetworkSecurityRuleConfig `
-Name DenyElse `
-Description 'Deny everything else' `
-Access Deny `
-Protocol * `
-Direction Outbound `
-Priority 1011 `
-SourceAddressPrefix * `
-SourcePortRange * `
-DestinationAddressPrefix * `
-DestinationPortRange *
$NetworkSecurityGroupName = $ResourceGroupName + 'NetworkSecurity'
$NetworkSecurityGroup = New-AzNetworkSecurityGroup `
-Name $NetworkSecurityGroupName `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-SecurityRules @($allowHttp, $allowDns, $allowStorage, $denyEverythingElse)
$SubnetName = $ResourceGroupName + 'Subnet'
$Subnet = New-AzVirtualNetworkSubnetConfig `
-Name $SubnetName `
-AddressPrefix "10.0.0.0/16" `
-NetworkSecurityGroup $NetworkSecurityGroup
$VirtualNetworkName = $ResourceGroupName + 'Network'
$VirtualNetwork = New-AzVirtualNetwork `
-Name $VirtualNetworkName `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-AddressPrefix "10.0.0.0/16" `
-Subnet $Subnet
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Creating archives storage account' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$StorageAccountName = Sanitize-Name $ResourceGroupName
New-AzStorageAccount `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-Name $StorageAccountName `
-SkuName 'Standard_LRS' `
-Kind StorageV2
$StorageAccountKeys = Get-AzStorageAccountKey `
-ResourceGroupName $ResourceGroupName `
-Name $StorageAccountName
$StorageAccountKey = $StorageAccountKeys[0].Value
$StorageContext = New-AzStorageContext `
-StorageAccountName $StorageAccountName `
-StorageAccountKey $StorageAccountKey
$ArchivesFiles = New-AzStorageShare -Name 'archives' -Context $StorageContext
Set-AzStorageShareQuota -ShareName 'archives' -Context $StorageContext -Quota 5120
$LogFiles = New-AzStorageShare -Name 'logs' -Context $StorageContext
Set-AzStorageShareQuota -ShareName 'logs' -Context $StorageContext -Quota 64
####################################################################################################
Write-Progress `
-Activity 'Creating prototype VM' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$NicName = $ResourceGroupName + 'NIC'
$Nic = New-AzNetworkInterface `
-Name $NicName `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-Subnet $VirtualNetwork.Subnets[0]
$VM = New-AzVMConfig -Name $ProtoVMName -VMSize $VMSize
$VM = Set-AzVMOperatingSystem `
-VM $VM `
-Windows `
-ComputerName $ProtoVMName `
-Credential $Credential `
-ProvisionVMAgent `
-EnableAutoUpdate
$VM = Add-AzVMNetworkInterface -VM $VM -Id $Nic.Id
$VM = Set-AzVMSourceImage `
-VM $VM `
-PublisherName 'MicrosoftWindowsServer' `
-Offer 'WindowsServer' `
-Skus $WindowsServerSku `
-Version latest
$InstallDiskName = $ProtoVMName + "InstallDisk"
$VM = Add-AzVMDataDisk `
-Vm $VM `
-Name $InstallDiskName `
-Lun 0 `
-Caching ReadWrite `
-CreateOption Empty `
-DiskSizeInGB $InstalledDiskSizeInGB `
-StorageAccountType 'StandardSSD_LRS'
$VM = Set-AzVMBootDiagnostic -VM $VM -Disable
New-AzVm `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-VM $VM
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Running provisioning script provision-image.ps1 in VM' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Invoke-AzVMRunCommand `
-ResourceGroupName $ResourceGroupName `
-VMName $ProtoVMName `
-CommandId 'RunPowerShellScript' `
-ScriptPath "$PSScriptRoot\provision-image.ps1" `
-Parameter @{AdminUserPassword = $AdminPW; `
StorageAccountName=$StorageAccountName; `
StorageAccountKey=$StorageAccountKey;}
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Restarting VM' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Restart-AzVM -ResourceGroupName $ResourceGroupName -Name $ProtoVMName
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Running provisioning script sysprep.ps1 in VM' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Invoke-AzVMRunCommand `
-ResourceGroupName $ResourceGroupName `
-VMName $ProtoVMName `
-CommandId 'RunPowerShellScript' `
-ScriptPath "$PSScriptRoot\sysprep.ps1"
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Waiting for VM to shut down' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Wait-Shutdown -ResourceGroupName $ResourceGroupName -Name $ProtoVMName
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Converting VM to Image' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Stop-AzVM `
-ResourceGroupName $ResourceGroupName `
-Name $ProtoVMName `
-Force
Set-AzVM `
-ResourceGroupName $ResourceGroupName `
-Name $ProtoVMName `
-Generalized
$VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $ProtoVMName
$PrototypeOSDiskName = $VM.StorageProfile.OsDisk.Name
$ImageConfig = New-AzImageConfig -Location $Location -SourceVirtualMachineId $VM.ID
$Image = New-AzImage -Image $ImageConfig -ImageName $ProtoVMName -ResourceGroupName $ResourceGroupName
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Deleting unused VM and disk' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Remove-AzVM -Id $VM.ID -Force
Remove-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $PrototypeOSDiskName -Force
Remove-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $InstallDiskName -Force
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Creating scale set' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$VmssIpConfigName = $ResourceGroupName + 'VmssIpConfig'
$VmssIpConfig = New-AzVmssIpConfig -SubnetId $Nic.IpConfigurations[0].Subnet.Id -Primary -Name $VmssIpConfigName
$VmssName = $ResourceGroupName + 'Vmss'
$Vmss = New-AzVmssConfig `
-Location $Location `
-SkuCapacity 6 `
-SkuName $VMSize `
-SkuTier 'Standard' `
-Overprovision $false `
-UpgradePolicyMode Manual
$Vmss = Add-AzVmssNetworkInterfaceConfiguration `
-VirtualMachineScaleSet $Vmss `
-Primary $true `
-IpConfiguration $VmssIpConfig `
-NetworkSecurityGroupId $NetworkSecurityGroup.Id `
-Name $NicName
$Vmss = Set-AzVmssOsProfile `
-VirtualMachineScaleSet $Vmss `
-ComputerNamePrefix $LiveVMPrefix `
-AdminUsername 'AdminUser' `
-AdminPassword $AdminPW `
-WindowsConfigurationProvisionVMAgent $true `
-WindowsConfigurationEnableAutomaticUpdate $true
$Vmss = Set-AzVmssStorageProfile `
-VirtualMachineScaleSet $Vmss `
-OsDiskCreateOption 'FromImage' `
-OsDiskCaching ReadWrite `
-ImageReferenceId $Image.Id
New-AzVmss `
-ResourceGroupName $ResourceGroupName `
-Name $VmssName `
-VirtualMachineScaleSet $Vmss
####################################################################################################
Write-Progress -Activity $ProgressActivity -Completed
Write-Host "Location: $Location"
Write-Host "Resource group name: $ResourceGroupName"
Write-Host "User name: AdminUser"
Write-Host "Using generated password: $AdminPW"
Write-Host 'Finished!'