create-azure-self-hosted-runners #711
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: create-azure-self-hosted-runners | |
on: | |
workflow_dispatch: | |
inputs: | |
runner_scope: | |
type: choice | |
required: true | |
description: Scope of the runner. On personal accounts, only "repo-level" works | |
options: | |
- org-level | |
- repo-level | |
default: repo-level | |
runner_org: | |
type: string | |
required: false | |
description: Organization or personal account to deploy the runner to (defaults to the repository owner) | |
runner_repo: | |
type: string | |
required: false | |
description: Repo to deploy the runner to. Only needed if runner_scope is set to "repo-level" (defaults to current repository) | |
deallocate_immediately: | |
type: string | |
required: true | |
description: Deallocate the runner immediately after creating it (useful for spinning up runners preemptively) | |
default: "false" | |
env: | |
ACTIONS_RUNNER_SCOPE: ${{ github.event.inputs.runner_scope }} | |
ACTIONS_RUNNER_ORG: "${{ github.event.inputs.runner_org || github.repository_owner }}" | |
ACTIONS_RUNNER_REPO: "${{ github.event.inputs.runner_repo || github.event.repository.name }}" | |
DEALLOCATE_IMMEDIATELY: ${{ github.event.inputs.deallocate_immediately }} | |
# This has to be a public URL that the VM can access after creation | |
POST_DEPLOYMENT_SCRIPT_URL: https://raw.githubusercontent.com/${{ github.repository }}/${{ github.ref }}/azure-self-hosted-runners/post-deployment-script.ps1 | |
# Note that you'll need "p" (arm64 processor) and ideally "d" (local temp disk). The number 4 stands for 4 CPU-cores. | |
# For a convenient overview of all arm64 VM types, see e.g. https://azureprice.net/?_cpuArchitecture=Arm64 | |
AZURE_VM_TYPE: Standard_D4plds_v5 | |
# At the time of writing, "eastus", "eastus2" and "westus2" were among the cheapest region for the VM type we're using. | |
# For more information, see https://learn.microsoft.com/en-us/azure/virtual-machines/dplsv5-dpldsv5-series (which | |
# unfortunately does not have more information about price by region) | |
AZURE_VM_REGION: westus2 | |
AZURE_VM_IMAGE: win11-24h2-ent | |
# The following secrets are required for this workflow to run: | |
# AZURE_CREDENTIALS - Credentials for the Azure CLI. It's recommended to set up a resource | |
# group specifically for self-hosted Actions Runners. | |
# az ad sp create-for-rbac --name "{YOUR_DESCRIPTIVE_NAME_HERE}" --role contributor \ | |
# --scopes /subscriptions/{SUBSCRIPTION_ID_HERE}/resourceGroups/{RESOURCE_GROUP_HERE} \ | |
# --sdk-auth | |
# AZURE_RESOURCE_GROUP - Resource group to create the runner(s) in | |
# AZURE_VM_USERNAME - Username of the VM so you can RDP into it | |
# AZURE_VM_PASSWORD - Password of the VM so you can RDP into it | |
jobs: | |
create-runner: | |
runs-on: ubuntu-latest | |
outputs: | |
vm_name: ${{ steps.generate-vm-name.outputs.vm_name }} | |
steps: | |
- name: Generate VM name | |
id: generate-vm-name | |
run: | | |
VM_NAME="actions-runner-$(date +%Y%m%d%H%M%S%N)" | |
echo "Will be using $VM_NAME as the VM name" | |
echo "vm_name=$VM_NAME" >> $GITHUB_OUTPUT | |
- uses: actions/checkout@v4 | |
- name: Obtain installation token | |
id: setup | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const appId = ${{ secrets.GH_APP_ID }} | |
const privateKey = `${{ secrets.GH_APP_PRIVATE_KEY }}` | |
const getAppInstallationId = require('./get-app-installation-id') | |
const installationId = await getAppInstallationId( | |
console, | |
appId, | |
privateKey, | |
process.env.ACTIONS_RUNNER_ORG, | |
process.env.ACTIONS_RUNNER_REPO | |
) | |
const getInstallationAccessToken = require('./get-installation-access-token') | |
const { token: accessToken } = await getInstallationAccessToken( | |
console, | |
appId, | |
privateKey, | |
installationId | |
) | |
core.setSecret(accessToken) | |
core.setOutput('token', accessToken) | |
# We can't use the octokit/request-action as we can't properly mask the runner token with it | |
# https://github.com/actions/runner/issues/475 | |
- name: Generate Actions Runner token and registration URL | |
run: | | |
case "$ACTIONS_RUNNER_SCOPE" in | |
"org-level") | |
ACTIONS_API_URL="https://api.github.com/repos/$ACTIONS_RUNNER_ORG/actions/runners/registration-token" | |
ACTIONS_RUNNER_REGISTRATION_URL="https://github.com/$ACTIONS_RUNNER_ORG" | |
;; | |
"repo-level") | |
ACTIONS_API_URL="https://api.github.com/repos/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO/actions/runners/registration-token" | |
ACTIONS_RUNNER_REGISTRATION_URL="https://github.com/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO" | |
;; | |
*) | |
echo "Unsupported runner scope: $ACTIONS_RUNNER_SCOPE" | |
exit 1 | |
;; | |
esac | |
ACTIONS_RUNNER_TOKEN=$(curl \ | |
-X POST \ | |
-H "Accept: application/vnd.github+json" \ | |
-H "Authorization: Bearer ${{ steps.setup.outputs.token }}"\ | |
-H "X-GitHub-Api-Version: 2022-11-28" \ | |
$ACTIONS_API_URL \ | |
| jq --raw-output .token) | |
echo "::add-mask::$ACTIONS_RUNNER_TOKEN" | |
# The Azure VM type we use has blazing-fast local, temporary storage available as the D:\ drive. | |
# The only downside is that, after dellocation, the contents of this disk (including the Actions Runner), | |
# are destroyed. Let's only use it when we don't immediately deallocate the VM. | |
if [[ "$DEALLOCATE_IMMEDIATELY" == "true" ]]; then | |
ACTIONS_RUNNER_PATH="C:\a" | |
else | |
ACTIONS_RUNNER_PATH="D:\a" | |
fi | |
AZURE_ARM_PARAMETERS=$(tr '\n' ' ' <<-END | |
githubActionsRunnerRegistrationUrl="$ACTIONS_RUNNER_REGISTRATION_URL" | |
githubActionsRunnerToken="$ACTIONS_RUNNER_TOKEN" | |
postDeploymentPsScriptUrl="$POST_DEPLOYMENT_SCRIPT_URL" | |
virtualMachineImage="$AZURE_VM_IMAGE" | |
virtualMachineName="${{ steps.generate-vm-name.outputs.vm_name }}" | |
virtualMachineSize="$AZURE_VM_TYPE" | |
publicIpAddressName1="${{ steps.generate-vm-name.outputs.vm_name }}-ip" | |
adminUsername="${{ secrets.AZURE_VM_USERNAME }}" | |
adminPassword="${{ secrets.AZURE_VM_PASSWORD }}" | |
stopService="$DEALLOCATE_IMMEDIATELY" | |
githubActionsRunnerPath="$ACTIONS_RUNNER_PATH" | |
location="$AZURE_VM_REGION" | |
END | |
) | |
echo "AZURE_ARM_PARAMETERS=$AZURE_ARM_PARAMETERS" >> $GITHUB_ENV | |
- name: Azure Login | |
uses: ./.github/workflows/azure-login | |
with: | |
credentials: ${{ secrets.AZURE_CREDENTIALS }} | |
- uses: azure/arm-deploy@v2 | |
id: deploy-arm-template | |
with: | |
resourceGroupName: ${{ secrets.AZURE_RESOURCE_GROUP }} | |
deploymentName: deploy-${{ steps.generate-vm-name.outputs.vm_name }} | |
template: ./azure-self-hosted-runners/azure-arm-template.json | |
parameters: ./azure-self-hosted-runners/azure-arm-template-example-parameters.json ${{ env.AZURE_ARM_PARAMETERS }} | |
scope: resourcegroup | |
- name: Show some more information on failure | |
if: failure() | |
run: | | |
echo "::group::VM status" | |
az vm get-instance-view --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} --name ${{ steps.generate-vm-name.outputs.vm_name }} --query "instanceView.statuses" | |
az vm get-instance-view --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} --name ${{ steps.generate-vm-name.outputs.vm_name }} --query "statuses" | |
echo "::endgroup::" | |
echo "::group::Deployment logs" | |
az group deployment show --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} --name deploy-${{ steps.generate-vm-name.outputs.vm_name }} | |
echo "::endgroup::" | |
echo "::group::Extension logs" | |
az vm extension show --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} --vm-name ${{ steps.generate-vm-name.outputs.vm_name }} --name CustomScriptExtension | |
echo "::endgroup::" | |
- name: Show post-deployment script output | |
if: always() | |
env: | |
CUSTOM_SCRIPT_OUTPUT: ${{ steps.deploy-arm-template.outputs.customScriptInstanceView }} | |
run: echo "$CUSTOM_SCRIPT_OUTPUT" | jq -r '.substatuses[0].message' | |
- name: Deallocate the VM for later use | |
if: env.DEALLOCATE_IMMEDIATELY == 'true' | |
run: az vm deallocate -n ${{ steps.generate-vm-name.outputs.vm_name }} -g ${{ secrets.AZURE_RESOURCE_GROUP }} --verbose |