Skip to content

Build missing images (after new windows updates) #591

Build missing images (after new windows updates)

Build missing images (after new windows updates) #591

name: Build missing images (after new windows updates)
on:
workflow_dispatch:
inputs:
GenericTag:
description: Generic Tag (leave empty to use value in generic/tag.txt)
required: false
default: ''
Agents:
description: Number of agents to create (0 = use existing)
required: false
default: '2'
PushToProd:
description: Push to production (Y/N)
required: false
default: 'Y'
env:
resGroup: "buildgeneric"
resLocation: "Sweden Central"
ARMbranch: "master"
ARMtemplate: "buildagent"
permissions:
contents: read
defaults:
run:
shell: PowerShell
jobs:
AnalyzeImages:
runs-on: [ windows-latest ]
outputs:
generic: ${{ steps.Analyze.outputs.generic }}
genericcount: ${{ steps.Analyze.outputs.genericcount }}
filesonly: ${{ steps.Analyze.outputs.filesonly }}
filesonlycount: ${{ steps.Analyze.outputs.filesonlycount }}
steps:
- uses: actions/checkout@v4
- name: Analyze
id: Analyze
run: |
$erroractionpreference = "STOP"
try {
$unsupportedWindowsVersions = @("10.0.14300.*","10.0.16299.*","10.0.18363.*","10.0.18362.*","10.0.17134.*") # "10.0.14393.*","10.0.19041.*","10.0.19042.*"
Set-Location "generic"
$genericTag = ''
if ($env:GITHUB_EVENT_NAME -eq "workflow_dispatch") {
$genericTag = '${{ github.event.inputs.GenericTag }}'
}
if ($genericTag -eq '') { $genericTag = Get-Content -path 'tag.txt' }
[System.Version]$genericTag | Out-Null
Write-Host "Using generic Tag $genericTag"
$genericTag = "-$genericTag"
$webclient = New-Object System.Net.WebClient
$webclient.Headers.Add('Accept', "application/json")
$alltags = (($webclient.DownloadString("https://mcr.microsoft.com/v2/businesscentral/tags/list") | ConvertFrom-Json)).tags
$version = [System.Version]"0.0.0.0"
$versions = @($alltags | Where-Object { $_ -like "*$generictag" } | Where-Object { [System.Version]::TryParse($_.SubString(0,$_.Length-$generictag.Length), [ref] $version) } | Where-Object { -not ($UnsupportedWindowsVersions | Where-Object { "$version" -like $_ }) } | ForEach-Object { $version })
$filesOnlyVersions = @($alltags | Where-Object { $_ -like "*$generictag-filesonly" } | Where-Object { [System.Version]::TryParse($_.SubString(0,$_.Length-$generictag.Length-10), [ref] $version) } | Where-Object { -not ($UnsupportedWindowsVersions | Where-Object { "$version" -like $_ }) } | ForEach-Object { $version })
$missingImages = @{}
$missingFilesOnlyImages = @{}
$alldotnetFrameworkTags = (($webclient.DownloadString("https://mcr.microsoft.com/v2/dotnet/framework/runtime/tags/list") | ConvertFrom-Json)).tags
$alldotnetFrameworkTags | Where-Object { $_.startswith("4.8-w") -or $_.startswith("4.8-20") } | ForEach-Object {
$osVersion = [System.Version](($webclient.DownloadString("https://mcr.microsoft.com/v2/dotnet/framework/runtime/manifests/$_") | ConvertFrom-Json).history[0].v1Compatibility | ConvertFrom-Json)."os.version"
if (-not ($UnsupportedWindowsVersions | Where-Object { "$osVersion" -like $_ })) {
Write-Host -NoNewline "Test $_ ($OSVersion)"
if ($versions.Contains($osversion)) {
Write-Host -NoNewline " - OK"
}
else {
$missingImages."$osversion" = $_
Write-Host -NoNewline " - Missing"
}
if ($filesOnlyVersions.Contains($osversion)) {
Write-Host " - OK"
}
else {
$missingFilesOnlyImages."$osversion" = $_
Write-Host " - Missing"
}
}
}
$genericarray = @($missingImages.GetEnumerator() | Where-Object { $_ } | ForEach-Object { "$($_.name)|$($_.value)" })
$genericcount = $genericarray.Count
$generic = $genericarray | ConvertTo-Json -Compress
Add-Content -Path $ENV:GITHUB_OUTPUT -Value "generic=[$("$generic".Trim('[]'))]"
Write-Host "generic=[$("$generic".Trim('[]'))]"
Add-Content -Path $ENV:GITHUB_OUTPUT -Value "genericcount=$genericcount"
Write-Host "genericcount=$genericcount"
$filesonlyarray = @($missingFilesOnlyImages.GetEnumerator() | Where-Object { $_ } | ForEach-Object { "$($_.name)-filesonly|$($_.value)" })
$filesonlycount = $filesonlyarray.Count
$filesonly = $filesonlyarray | ConvertTo-Json -Compress
Add-Content -Path $ENV:GITHUB_OUTPUT -Value "filesonly=[$("$filesonly".Trim('[]'))]"
Write-Host "filesonly=[$("$filesonly".Trim('[]'))]"
Add-Content -Path $ENV:GITHUB_OUTPUT -Value "filesonlycount=$filesonlycount"
Write-Host "filesonlycount=$filesonlycount"
}
catch {
Write-Host "::Error::Error analyzing images. Error was $($_.Exception.Message)"
$host.SetShouldExit(1)
}
CreateAgents:
runs-on: [ windows-latest ]
needs: [ AnalyzeImages ]
if: ${{ needs.AnalyzeImages.outputs.genericcount > 0 || needs.AnalyzeImages.outputs.filesonlycount > 0 }}
steps:
- uses: actions/checkout@v4
- uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
enable-AzPSSession: true
- name: Run Azure PowerShell script
uses: azure/powershell@v1
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
agents: ${{ github.event.inputs.Agents }}
with:
azPSVersion: "latest"
inlineScript: |
if ($env:agents -eq "0") {
Write-Host "Using existing agents"
}
else {
$resGroup = $env:resGroup
$resLocation = $env:resLocation
$repo = $env:GITHUB_REPOSITORY
$agents = [int]$env:agents
Write-Host "Resource Group is $resGroup"
$resourceGroup = Get-AzResourceGroup -name $resGroup -ErrorAction Ignore
if ($resourceGroup) {
Write-Host "Removing Resource Group $resGroup"
Remove-AzResourceGroup -Name $resGroup -Force | Out-Host
Write-Host "Done"
}
$runners = (gh api /repos/$repo/actions/runners | ConvertFrom-Json).runners
$runners | Where-Object { $_.status -eq "offline" } | ForEach-Object {
Write-host "Unregistering runner $($_.name)"
$id = $_.id
gh api -X DELETE /repos/$repo/actions/runners/$id
Write-Host "Done"
}
Write-Host "Creating resource group $resGroup in $resLocation"
$resourceGroup = New-AzResourceGroup -Name $resGroup -Location $resLocation -Force
Write-Host "Done"
1..$agents | ForEach-Object {
# Deployment
$no = $_
# ARM template
$templateUri = "https://raw.githubusercontent.com/microsoft/nav-arm-templates/$($env:ARMbranch)/$($env:ARMtemplate).json"
$registrationToken = (gh api -X POST /repos/$repo/actions/runners/registration-token | ConvertFrom-Json).token
$headers = @{
"Accept" = "application/json"
"Authorization" = "token $env:GH_TOKEN"
}
$uri = "https://api.github.com/repos/actions/runner/releases"
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
$result = Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri $uri
$releases = $result.Content | ConvertFrom-Json
$asset = $releases[0].assets | Where-Object { $_.name -like "actions-runner-win-x64-*.*.?.zip" }
$Parameters = @{
"VmName" = "$resgroup$no"
"Remotedesktopaccess" = "-"
"OperatingSystem" = "Windows Server 2022"
"VmSize" = "Standard_D4as_v5"
"OSDiskSize" = 128
"StorageAccountType" = "Premium_LRS"
"AdminPassword" = [SecureString](ConvertTo-SecureString -String $env:GH_TOKEN -AsPlainText -Force)
"Count" = 1
"Token" = $registrationToken
"Organization" = "https://github.com/$repo"
"LabelsOrPool" = "buildgeneric"
"AgentUrl" = $asset.browser_download_url
"InstallHyperV" = "Yes"
"RunInsideDocker" = "No"
"FinalSetupScriptUrl" = "additional-installforbuildagent.ps1"
}
$err = $resourceGroup | Test-AzResourceGroupDeployment -TemplateUri $templateUri -TemplateParameterObject $Parameters
if ($err) {
$err
throw "stop"
}
Write-Host "Creating Azure VM $($parameters.VmName)"
$resourceGroup | New-AzResourceGroupDeployment -TemplateUri $templateUri -TemplateParameterObject $Parameters -Name $Parameters.vmName -ErrorAction Ignore -AsJob
Start-Sleep -Seconds 60
Write-Host "VM creation started"
}
}
Generic:
runs-on: [ buildgeneric ]
needs: [ AnalyzeImages, CreateAgents ]
strategy:
matrix:
version: ${{fromJson(needs.AnalyzeImages.outputs.generic)}}
fail-fast: false
max-parallel: 5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build Image
run: |
$erroractionpreference = "STOP"
Set-StrictMode -version 2.0
try {
$pushRegistry = "mcrbusinesscentral.azurecr.io"
$job = start-job -ScriptBlock { Param($username, $token, $registry)
Write-Output $token | docker login --username $username --password-stdin $registry
} -ArgumentList '${{ secrets.PushUsername }}', '${{ secrets.PushToken }}', $pushRegistry
$job | Wait-Job -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
$result = Receive-Job -ErrorAction SilentlyContinue -WarningAction SilentlyContinue $job 2> $NULL
if ($result -ne 'Login Succeeded') {
throw "docker login failed"
}
Set-Location "generic"
$genericTag = ''
$pushToProd = $true
if ($env:GITHUB_EVENT_NAME -eq "workflow_dispatch") {
$genericTag = '${{ github.event.inputs.GenericTag }}'
$pushToProd = '${{ github.event.inputs.PushToProd }}' -eq 'Y'
}
if ($genericTag -eq '') { $genericTag = Get-Content -path 'tag.txt' }
[System.Version]$genericTag | Out-Null
$osversion = '${{ matrix.version }}'.split('|')[0].split('-')[0]
$filesonly = ''
if ('${{ matrix.version }}' -like '*-filesonly|*') {
$filesonly = '-filesonly'
}
$dotnetFrameworkTag = '${{ matrix.version }}'.split('|')[1]
$baseimage = "mcr.microsoft.com/dotnet/framework/runtime:$dotnetFrameworkTag"
$rootPath = Get-Location
$dockerfile = Join-Path $rootPath "DOCKERFILE$filesonly"
$image = "my:$osversion-$genericTag$filesonly"
$created = [DateTime]::Now.ToUniversalTime().ToString("yyyyMMddHHmm")
docker pull $baseimage
$inspect = docker inspect $baseimage | ConvertFrom-Json
$success = $false
docker build --build-arg baseimage=$baseimage `
--build-arg created=$created `
--build-arg tag="$genericTag" `
--build-arg osversion="$osversion" `
--isolation=hyperv `
--memory 8G `
--tag $image `
--file $dockerfile `
$RootPath | % {
$_ | Out-Host
if ($_ -like "Successfully built*") {
$success = $true
}
}
if (!$success) {
throw "Error building image"
}
$newtags = @(
"$pushRegistry/public/businesscentral:$osversion$filesonly-dev"
)
if ($pushToProd) {
$newtags += @(
"$pushRegistry/public/businesscentral:$osversion$filesonly"
"$pushRegistry/public/businesscentral:$osversion-$genericTag$filesonly"
)
}
$newtags | ForEach-Object {
Write-Host "Push $_"
docker tag $image $_
docker push $_
}
$newtags | ForEach-Object {
docker rmi $_
}
docker rmi $image
docker rmi $baseimage
docker system prune --force
}
catch {
Write-Host "::Error::Error building images. Error was $($_.Exception.Message)"
$host.SetShouldExit(1)
}
FilesOnly:
runs-on: [ buildgeneric ]
needs: [ AnalyzeImages, CreateAgents ]
strategy:
matrix:
version: ${{fromJson(needs.AnalyzeImages.outputs.filesonly)}}
fail-fast: false
max-parallel: 5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build Image
run: |
$erroractionpreference = "STOP"
Set-StrictMode -version 2.0
try {
$pushRegistry = "mcrbusinesscentral.azurecr.io"
$job = start-job -ScriptBlock { Param($username, $token, $registry)
Write-Output $token | docker login --username $username --password-stdin $registry
} -ArgumentList '${{ secrets.PushUsername }}', '${{ secrets.PushToken }}', $pushRegistry
$job | Wait-Job -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
$result = Receive-Job -ErrorAction SilentlyContinue -WarningAction SilentlyContinue $job 2> $NULL
if ($result -ne 'Login Succeeded') {
throw "docker login failed"
}
Set-Location "generic"
$genericTag = ''
$pushToProd = $true
if ($env:GITHUB_EVENT_NAME -eq "workflow_dispatch") {
$genericTag = '${{ github.event.inputs.GenericTag }}'
$pushToProd = '${{ github.event.inputs.PushToProd }}' -eq 'Y'
}
if ($genericTag -eq '') { $genericTag = Get-Content -path 'tag.txt' }
[System.Version]$genericTag | Out-Null
$osversion = '${{ matrix.version }}'.split('|')[0].split('-')[0]
$filesonly = ''
if ('${{ matrix.version }}' -like '*-filesonly|*') {
$filesonly = '-filesonly'
}
$dotnetFrameworkTag = '${{ matrix.version }}'.split('|')[1]
$baseimage = "mcr.microsoft.com/dotnet/framework/runtime:$dotnetFrameworkTag"
$rootPath = Get-Location
$dockerfile = Join-Path $rootPath "DOCKERFILE$filesonly"
$image = "my:$osversion-$genericTag$filesonly"
$created = [DateTime]::Now.ToUniversalTime().ToString("yyyyMMddHHmm")
docker pull $baseimage
$inspect = docker inspect $baseimage | ConvertFrom-Json
$success = $false
docker build --build-arg baseimage=$baseimage `
--build-arg created=$created `
--build-arg tag="$genericTag" `
--build-arg osversion="$osversion" `
--isolation=hyperv `
--memory 8G `
--tag $image `
--file $dockerfile `
$RootPath | % {
$_ | Out-Host
if ($_ -like "Successfully built*") {
$success = $true
}
}
if (!$success) {
throw "Error building image"
}
$newtags = @(
"$pushRegistry/public/businesscentral:$osversion$filesonly-dev"
)
if ($pushToProd) {
$newtags += @(
"$pushRegistry/public/businesscentral:$osversion$filesonly"
"$pushRegistry/public/businesscentral:$osversion-$genericTag$filesonly"
)
}
$newtags | ForEach-Object {
Write-Host "Push $_"
docker tag $image $_
docker push $_
}
$newtags | ForEach-Object {
docker rmi $_
}
docker rmi $image
docker rmi $baseimage
docker system prune --force
}
catch {
Write-Host "::Error::Error building images. Error was $($_.Exception.Message)"
$host.SetShouldExit(1)
}
RemoveAgents:
runs-on: [ windows-latest ]
needs: [ AnalyzeImages, CreateAgents, Generic, FilesOnly ]
steps:
- uses: actions/checkout@v4
- uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
enable-AzPSSession: true
- name: Run Azure PowerShell script
uses: azure/powershell@v1
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
agents: ${{ github.event.inputs.Agents }}
with:
azPSVersion: "latest"
inlineScript: |
if ($env:agents -eq "0") {
Write-Host "Used existing agents"
}
else {
$resGroup = $env:resGroup
$resLocation = $env:resLocation
$repo = $env:GITHUB_REPOSITORY
Write-Host "Resource Group is $resGroup"
$resourceGroup = Get-AzResourceGroup -name $resGroup -ErrorAction Ignore
if ($resourceGroup) {
Write-Host "Removing Resource Group $resGroup"
Remove-AzResourceGroup -Name $resGroup -Force | Out-Host
Write-Host "Done"
}
$runners = (gh api /repos/$repo/actions/runners | ConvertFrom-Json).runners
$runners | Where-Object { $_.status -eq "offline" } | ForEach-Object {
Write-host "Unregistering runner $($_.name)"
$id = $_.id
gh api -X DELETE /repos/$repo/actions/runners/$id
Write-Host "Done"
}
}