Skip to content

self-hosted runner: always use latest Git for Windows and GitHub Actions version (plus some PowerShell cleanups) #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 15, 2023
Merged
179 changes: 148 additions & 31 deletions azure-self-hosted-runners/post-deployment-script.ps1
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
param (
# GitHub Actions Runner registration token. Note that these tokens are only valid for one hour after creation, so we always expect the user to provide one.
# https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners
[Parameter(Mandatory=$true)]
[Parameter(Mandatory = $true, HelpMessage = "GitHub Actions Runner registration token. Note that these tokens are only valid for one hour after creation, so we always expect the user to provide one.")]
[string]$GitHubActionsRunnerToken,

# GitHub Actions Runner repository. E.g. "https://github.com/MY_ORG" (org-level) or "https://github.com/MY_ORG/MY_REPO" (repo-level)
# https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners
[Parameter(Mandatory=$true)]
[Parameter(Mandatory = $true)]
[ValidateScript({ $_ -like "https://*" })]
[string]$GithubActionsRunnerRegistrationUrl,

# Actions Runner name. Needs to be unique in the org/repo
[Parameter(Mandatory=$true)]
[Parameter(Mandatory = $true, HelpMessage = "Name of the runner. Needs to be unique in the org/repo")]
[ValidateNotNullOrEmpty()]
[string]$GithubActionsRunnerName,

# Stop Service immediately (useful for spinning up runners preemptively)
[Parameter(Mandatory=$false)]
[Parameter(Mandatory = $false, HelpMessage = "Stop Service immediately (useful for spinning up runners preemptively)")]
[ValidateSet('true', 'false')]
[string]$StopService = 'true',

# Path to the Actions Runner. Keep this path short to prevent Long Path issues, e.g. D:\a
[Parameter(Mandatory=$true)]
[Parameter(Mandatory = $true, HelpMessage = "Path to the Actions Runner. Keep this path short to prevent Long Path issues, e.g. D:\a")]
[ValidateNotNullOrEmpty()]
[string]$GitHubActionsRunnerPath
)

Expand All @@ -28,15 +27,89 @@ Write-Output "Starting post-deployment script."
# =================================
# TOOL VERSIONS AND OTHER VARIABLES
# =================================
#
# This header is used for both Git for Windows and GitHub Actions Runner
[hashtable]$GithubHeaders = @{
"Accept" = "application/vnd.github.v3+json"
"X-GitHub-Api-Version" = "2022-11-28"
}

$GitForWindowsVersion = "2.39.0"
$GitForWindowsTag = "2.39.0.windows.1"
$GitForWindowsHash = "2eaba567e17784654be77ba997329742d87845c6f15e33c9620f9a331c69a976"
# =================================
# Get download and hash information for the latest release of Git for Windows
# =================================
#
# This will return the latest release of Git for Windows download link, hash and the name of the outfile
# Everything will be saved in the object $GitHubGit
#
# url for Github API to get the latest release
[string]$GitHubUrl = "https://api.github.com/repos/git-for-windows/git/releases/latest"
#
# Name of the exe file that should be verified and downloaded
[string]$GithubExeName = "Git-.*-64-bit.exe"

try {
[System.Object]$GithubRestData = Invoke-RestMethod -Uri $GitHubUrl -Method Get -Headers $GithubHeaders -TimeoutSec 10 | Select-Object -Property assets, body
[System.Object]$GitHubAsset = $GithubRestData.assets | Where-Object { $_.name -match $GithubExeName }
if ($GithubRestData.body -match "\b${[Regex]::Escape($GitHubAsset.name)}.*?\|.*?([a-zA-Z0-9]{64})" -eq $True) {
[System.Object]$GitHubGit = [PSCustomObject]@{
DownloadUrl = [string]$GitHubAsset.browser_download_url
Hash = [string]$Matches[1].ToUpper()
OutFile = "./git-for-windows-installer.exe"
}
}
else {
Write-Error "Could not find hash for $GithubExeName"
exit 1
}
}
catch {
Write-Error @"
"Message: "$($_.Exception.Message)`n
"Error Line: "$($_.InvocationInfo.Line)`n
"Line Number: "$($_.InvocationInfo.ScriptLineNumber)`n
"@
exit 1
}

# =================================
# Obtain the latest GitHub Actions Runner and other GitHub Actions information
# =================================
#
# Note that the GitHub Actions Runner auto-updates itself by default, but do try to reference a relatively new version here.
$GitHubActionsRunnerVersion = "2.300.2"
$GithubActionsRunnerArch = "arm64"
$GithubActionsRunnerHash = "9409e50d9ad33d8031355ed079b8f56cf3699f35cf5d0ca51e54deed432758ef"
$GithubActionsRunnerLabels = "self-hosted,Windows,ARM64"
#
# This will return the latest release of GitHub Actions Runner download link, hash, Tag, RunnerArch, RunnerLabels and the name of the outfile.
# Everything will be saved in the object $GitHubAction
#
# url for Github API to get the latest release of actions runner
[string]$GitHubActionUrl = "https://api.github.com/repos/actions/runner/releases/latest"

try {
[System.Object]$GithubActionRestData = Invoke-RestMethod -Uri $GitHubActionUrl -Method Get -Headers $GithubHeaders -TimeoutSec 10 | Select-Object -Property assets, body, tag_name
if ($GithubActionRestData.body -match "<!-- BEGIN SHA win-arm64 -->(.*)<!-- END SHA win-arm64 -->" -eq $True) {
[string]$ActionZipName = "actions-runner-win-arm64-" + [string]$($GithubActionRestData.tag_name.Substring(1)) + ".zip"

[System.Object]$GitHubAction = [PSCustomObject]@{
Tag = $GithubActionRestData.tag_name.Substring(1)
Hash = $Matches[1].ToUpper()
RunnerArch = "arm64"
RunnerLabels = "self-hosted,Windows,ARM64"
DownloadUrl = $GithubActionRestData.assets | where-object { $_.name -match $ActionZipName } | Select-Object -ExpandProperty browser_download_url
OutFile = "$($GitHubActionsRunnerPath)\$($ActionZipName)"
}
}
else {
Write-Error "Error: Could not find hash for Github Actions Runner"
exit 1
}
}
catch {
Write-Error @"
"Message: "$($_.Exception.Message)`n
"Error Line: "$($_.InvocationInfo.Line)`n
"Line Number: "$($_.InvocationInfo.ScriptLineNumber)`n
"@
exit 1
}

# ======================
# WINDOWS DEVELOPER MODE
Expand All @@ -60,12 +133,14 @@ Write-Output "Finished adding Microsoft Defender Exclusions."
# ======================

Write-Output "Downloading Git for Windows..."
$GitForWindowsOutputFile = "./git-for-windows-installer.exe"
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/git-for-windows/git/releases/download/v${GitForWindowsTag}/Git-${GitForWindowsVersion}-64-bit.exe" -OutFile $GitForWindowsOutputFile
Invoke-WebRequest -UseBasicParsing -Uri $GitHubGit.DownloadUrl -OutFile $GitHubGit.OutFile
$ProgressPreference = 'Continue'

if((Get-FileHash -Path $GitForWindowsOutputFile -Algorithm SHA256).Hash.ToUpper() -ne $GitForWindowsHash.ToUpper()){ throw 'Computed checksum did not match' }
if ((Get-FileHash -Path $GitHubGit.OutFile -Algorithm SHA256).Hash.ToUpper() -ne $GitHubGit.Hash) {
Write-Error "Computed checksum for $($GitHubGit.OutFile) did not match $($GitHubGit.Hash)"
exit 1
}

Write-Output "Installing Git for Windows..."
@"
Expand Down Expand Up @@ -94,7 +169,7 @@ EnablePseudoConsoleSupport=Disabled
EnableFSMonitor=Disabled
"@ | Out-File -FilePath "./git-installer-config.inf"

Start-Process -Wait $GitForWindowsOutputFile '/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /LOADINF="./git-installer-config.inf"'
Start-Process -Wait $GitHubGit.OutFile '/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /LOADINF="./git-installer-config.inf"'

Write-Output "Finished installing Git for Windows."

Expand All @@ -106,31 +181,73 @@ Write-Output "Downloading GitHub Actions runner..."

mkdir $GitHubActionsRunnerPath | Out-Null
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -UseBasicParsing -Uri https://github.com/actions/runner/releases/download/v${GitHubActionsRunnerVersion}/actions-runner-win-${GithubActionsRunnerArch}-${GitHubActionsRunnerVersion}.zip -OutFile ${GitHubActionsRunnerPath}\actions-runner-win-${GithubActionsRunnerArch}-${GitHubActionsRunnerVersion}.zip
Invoke-WebRequest -UseBasicParsing -Uri $GitHubAction.DownloadUrl -OutFile $GitHubAction.OutFile
$ProgressPreference = 'Continue'
if((Get-FileHash -Path ${GitHubActionsRunnerPath}\actions-runner-win-${GithubActionsRunnerArch}-${GitHubActionsRunnerVersion}.zip -Algorithm SHA256).Hash.ToUpper() -ne $GithubActionsRunnerHash.ToUpper()){ throw 'Computed checksum did not match' }

Write-Output "Installing GitHub Actions runner ${GitHubActionsRunnerVersion} as a Windows service with labels ${GithubActionsRunnerLabels}..."
if ((Get-FileHash -Path $GitHubAction.OutFile -Algorithm SHA256).Hash.ToUpper() -ne $GitHubAction.hash) {
Write-Error "Computed checksum for $($GitHubAction.OutFile) did not match $($GitHubAction.hash)"
exit 1
}

Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::ExtractToDirectory("${GitHubActionsRunnerPath}\actions-runner-win-${GithubActionsRunnerArch}-${GitHubActionsRunnerVersion}.zip", $GitHubActionsRunnerPath)
Write-Output "Installing GitHub Actions runner $($GitHubAction.Tag) as a Windows service with labels $($GitHubAction.RunnerLabels)..."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really interesting information. Since the post-deployment script is run on Azure, and the GitHub workflow that kicks off that deployment has no connection to the actual deployment, I wonder whether there is any way we could manually mirror it into the GitHub workflow run's logs... @dennisameling any ideas?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean? Do you want to log information from the script? If so, that's possible. I can even make somethign that can send you a email if you want.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh, ooh, I think I found something: https://learn.microsoft.com/en-us/powershell/module/az.resources/get-azdeploymentscriptlog?view=azps-9.3.0

Nice try, but Deployment Scripts are something else than the Custom Script Extension 😉

Let's follow up in this PR for getting the post-deployment script output 👍🏼


Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::ExtractToDirectory($GitHubAction.OutFile, $GitHubActionsRunnerPath)

Write-Output "Configuring the runner to shut down automatically after running"
Set-Content -Path "${GitHubActionsRunnerPath}\shut-down.ps1" -Value "shutdown -s -t 60 -d p:4:0 -c `"workflow job is done`""
[System.Environment]::SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", "${GitHubActionsRunnerPath}\shut-down.ps1", [System.EnvironmentVariableTarget]::Machine)

Write-Output "Configuring the runner"
cmd.exe /c "${GitHubActionsRunnerPath}\config.cmd" --unattended --ephemeral --name ${GithubActionsRunnerName} --runasservice --labels ${GithubActionsRunnerLabels} --url ${GithubActionsRunnerRegistrationUrl} --token ${GitHubActionsRunnerToken}
cmd.exe /c "${GitHubActionsRunnerPath}\config.cmd" --unattended --ephemeral --name ${GithubActionsRunnerName} --runasservice --labels $($GitHubAction.RunnerLabels) --url ${GithubActionsRunnerRegistrationUrl} --token ${GitHubActionsRunnerToken}

# Ensure that the service was created. If not, exit with error code.
$MatchedServices = Get-Service -Name "actions.runner.*"
if ($MatchedServices.count -eq 0) {
Write-Error "GitHub Actions service not found (should start with actions.runner). Check the logs in ${GitHubActionsRunnerPath}\_diag for more details."
exit 1
if ($null -eq (Get-Service -Name "actions.runner.*")) {
Write-Output "Could not find service actions.runner.*, making three more attempts with a 3 second delay in between each attempt..."

[int]$RetryCountService = 0
do {
Write-Output "Attempt $($RetryCountService) of 3: Looking for service actions.runner.*..."
$RetryCountService++
Start-Sleep -Seconds 3
}
while ($null -eq (Get-Service -Name "actions.runner.*") -or $RetryCountService -gt 3)

if ($RetryCountService -gt 3) {
Write-Error "GitHub Actions service not found (should start with actions.runner). Check the logs in ${GitHubActionsRunnerPath}\_diag for more details."
exit 1
}
else {
Write-Output "Found service actions.runner.*"
}
}

# Immediately stop the service as we want to leave the VM in a deallocated state for later use. The service will automatically be started when Windows starts.
if (${StopService} -eq 'true') {
Stop-Service -Name "actions.runner.*" -Verbose
if ($StopService -eq 'true') {
#Collects all running services named actions.runner.*
$GetActionRunnerServices = Get-Service -Name "actions.runner.*" | Where-Object { $_.Status -eq 'Running' } | Select-Object -ExpandProperty Name

# Loops trough all services and stopping them one by one
foreach ($Service in $GetActionRunnerServices) {
Write-Output "Stopping service $Service"
Stop-Service -Name $Service

# Making sure that all of the services has been stopped before moving forward
[int]$RetryCount = 0
do {
Write-Output "Attempt: $($RetryCount) of 5: Waiting for service $Service to stop..."
$RetryCount++
Start-Sleep -Seconds 5
}
while ((Get-Service -Name $Service).Status -eq 'running' -or $RetryCount -gt 5)

if ($RetryCount -gt 5) {
Write-Error "Service $Service failed to stop"
exit 1
}
else {
Write-Output "Service $Service has been stopped"
}
}
}

Write-Output "Finished installing GitHub Actions runner."