Skip to content

Commit b407951

Browse files
Refactor VMSS infra: manage Key Vault and images in Terraform
Moves Key Vault and custom image resource group management into Terraform, replacing data sources with resource definitions. Updates GitHub Actions workflow to import additional resources and use a subscription variable. Adds RBAC role assignments and a wait for RBAC propagation before storing the GitHub runner token in Key Vault. Updates documentation to clarify test duration and cost calculation.
1 parent f45812a commit b407951

File tree

5 files changed

+114
-31
lines changed

5 files changed

+114
-31
lines changed

.github/workflows/vmss-deploy.yml

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
echo "ARM_CLIENT_SECRET=$(echo '${{ secrets.VMSS_AZURE_CREDENTIALS }}' | jq -r '.clientSecret')" >> $GITHUB_ENV
3434
echo "ARM_SUBSCRIPTION_ID=$(echo '${{ secrets.VMSS_AZURE_CREDENTIALS }}' | jq -r '.subscriptionId')" >> $GITHUB_ENV
3535
echo "ARM_TENANT_ID=$(echo '${{ secrets.VMSS_AZURE_CREDENTIALS }}' | jq -r '.tenantId')" >> $GITHUB_ENV
36+
echo "ARM_USE_AZUREAD=true" >> $GITHUB_ENV
3637
3738
- name: Setup Terraform
3839
uses: hashicorp/setup-terraform@v3
@@ -82,20 +83,38 @@ jobs:
8283
continue-on-error: true
8384
working-directory: ./gh-runners
8485
run: |
86+
SUBSCRIPTION_ID="65a430fb-5a9a-49ff-969e-05d1beaa88fb"
87+
88+
# Import main resource group if it exists outside Terraform state
89+
terraform import azurerm_resource_group.rg /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/dbatools-ci-runners 2>/dev/null || true
90+
91+
# Import images resource group if it exists outside Terraform state
92+
terraform import azurerm_resource_group.images /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/dbatools-ci-images 2>/dev/null || true
93+
94+
# Import storage account if it exists outside Terraform state
95+
terraform import azurerm_storage_account.tfstate /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/dbatools-ci-runners/providers/Microsoft.Storage/storageAccounts/dbatoolstfstate 2>/dev/null || true
96+
97+
# Import storage container if it exists outside Terraform state
98+
terraform import azurerm_storage_container.tfstate https://dbatoolstfstate.blob.core.windows.net/tfstate 2>/dev/null || true
99+
100+
# Import Key Vault if it exists outside Terraform state
101+
terraform import azurerm_key_vault.vmss /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/dbatools-ci-runners/providers/Microsoft.KeyVault/vaults/dbatoolsci 2>/dev/null || true
102+
85103
# Import VNet if it exists outside Terraform state
86-
terraform import azurerm_virtual_network.vnet /subscriptions/65a430fb-5a9a-49ff-969e-05d1beaa88fb/resourceGroups/dbatools-ci-runners/providers/Microsoft.Network/virtualNetworks/dbatools-runner-vmss-vnet 2>/dev/null || true
104+
terraform import azurerm_virtual_network.vnet /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/dbatools-ci-runners/providers/Microsoft.Network/virtualNetworks/dbatools-runner-vmss-vnet 2>/dev/null || true
87105
88106
# Import subnet if it exists outside Terraform state
89-
terraform import azurerm_subnet.subnet /subscriptions/65a430fb-5a9a-49ff-969e-05d1beaa88fb/resourceGroups/dbatools-ci-runners/providers/Microsoft.Network/virtualNetworks/dbatools-runner-vmss-vnet/subnets/dbatools-runner-vmss-subnet 2>/dev/null || true
107+
terraform import azurerm_subnet.subnet /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/dbatools-ci-runners/providers/Microsoft.Network/virtualNetworks/dbatools-runner-vmss-vnet/subnets/dbatools-runner-vmss-subnet 2>/dev/null || true
90108
91109
# Import VMSS if it exists outside Terraform state
92-
terraform import azurerm_windows_virtual_machine_scale_set.vmss /subscriptions/65a430fb-5a9a-49ff-969e-05d1beaa88fb/resourceGroups/dbatools-ci-runners/providers/Microsoft.Compute/virtualMachineScaleSets/dbatools-runner-vmss 2>/dev/null || true
110+
terraform import azurerm_windows_virtual_machine_scale_set.vmss /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/dbatools-ci-runners/providers/Microsoft.Compute/virtualMachineScaleSets/dbatools-runner-vmss 2>/dev/null || true
93111
94112
# Import VMSS extension if it exists outside Terraform state
95-
terraform import azurerm_virtual_machine_scale_set_extension.vmss /subscriptions/65a430fb-5a9a-49ff-969e-05d1beaa88fb/resourceGroups/dbatools-ci-runners/providers/Microsoft.Compute/virtualMachineScaleSets/dbatools-runner-vmss/extensions/CustomScriptExtension 2>/dev/null || true
113+
terraform import azurerm_virtual_machine_scale_set_extension.vmss /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/dbatools-ci-runners/providers/Microsoft.Compute/virtualMachineScaleSets/dbatools-runner-vmss/extensions/CustomScriptExtension 2>/dev/null || true
96114
97-
# Import role assignment if it exists outside Terraform state
98-
terraform import azurerm_role_assignment.vmss_kv_secrets_user /subscriptions/65a430fb-5a9a-49ff-969e-05d1beaa88fb/resourceGroups/dbatools-ci-runners/providers/Microsoft.KeyVault/vaults/dbatoolsci|Key_Vault_Secrets_User 2>/dev/null || true
115+
# Import role assignments if they exist outside Terraform state
116+
# Note: Role assignment IDs are GUIDs and may need to be discovered first
117+
echo "Note: Role assignments for Key Vault access will be created if they don't exist"
99118
100119
- name: Terraform Apply
101120
if: github.event_name == 'push'

MIGRATION-COMPLETE.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ Successfully migrated from AppVeyor to GitHub Actions with Azure VMSS self-hoste
5555
2. **CI tests queue** (ci.yml) - 10 jobs waiting for runners
5656
3. **Autoscaler detects queue** (autoscaler.yml) - Scales VMSS to 3 instances within 1 minute
5757
4. **Runners register** - VMs boot, register with GitHub (3 minutes)
58-
5. **Tests run** - Jobs execute automatically (2.5 hours for full matrix)
58+
5. **Tests run** - Jobs execute automatically (duration depends on your test suite)
5959
6. **Runners destroy** - Ephemeral runners self-destruct after each job
6060
7. **Autoscaler scales down** - Detects empty queue, scales VMSS to 0 (2 minutes after completion)
6161

62-
**Total cost per push: ~$1.25** (3 runners × 2.5 hours × $0.166/hr)
62+
**Cost per push:** 3 runners × actual_test_hours × $0.166/hr
63+
- **Each job times out at 60 minutes max**
64+
- **Actual cost depends on real test duration** (unknown until first run)
6365

6466
**Your involvement: Push code. That's it.**
6567

gh-runners/backend.tf

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
1-
# Resource group for Terraform state storage
2-
resource "azurerm_resource_group" "tfstate" {
3-
name = "dbatools-ci-runners"
4-
location = var.location
5-
6-
tags = {
7-
Environment = "CI"
8-
ManagedBy = "Terraform"
9-
Purpose = "Terraform-State"
10-
}
11-
}
12-
131
# Storage account for Terraform state
2+
# Note: Resource group is created in vmss.tf and bootstrapped via GitHub Actions
143
resource "azurerm_storage_account" "tfstate" {
154
name = "dbatoolstfstate"
16-
resource_group_name = azurerm_resource_group.tfstate.name
17-
location = azurerm_resource_group.tfstate.location
5+
resource_group_name = azurerm_resource_group.rg.name
6+
location = azurerm_resource_group.rg.location
187
account_tier = "Standard"
198
account_replication_type = "LRS"
209
min_tls_version = "TLS1_2"

gh-runners/version.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ terraform {
1717
source = "integrations/github"
1818
version = "~> 5.0"
1919
}
20+
time = {
21+
source = "hashicorp/time"
22+
version = "~> 0.9"
23+
}
2024
}
2125
}
2226

gh-runners/vmss.tf

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,54 @@ resource "azurerm_resource_group" "rg" {
1313
}
1414
}
1515

16-
# Reference existing Key Vault
17-
data "azurerm_key_vault" "vmss" {
18-
name = var.keyvault_name
19-
resource_group_name = data.azurerm_resource_group.rg.name
16+
# Create Key Vault for storing GitHub runner token
17+
resource "azurerm_key_vault" "vmss" {
18+
name = var.keyvault_name
19+
resource_group_name = azurerm_resource_group.rg.name
20+
location = azurerm_resource_group.rg.location
21+
tenant_id = data.azurerm_client_config.current.tenant_id
22+
sku_name = "standard"
23+
soft_delete_retention_days = 7
24+
purge_protection_enabled = false
25+
26+
# Enable RBAC authorization
27+
enable_rbac_authorization = true
28+
29+
# Network ACLs - allow Azure services
30+
network_acls {
31+
bypass = "AzureServices"
32+
default_action = "Allow"
33+
}
34+
35+
tags = {
36+
Environment = "CI"
37+
ManagedBy = "Terraform"
38+
}
2039
}
2140

22-
# Reference existing custom image
41+
# Resource group for custom images
42+
# This must exist and contain the golden image with GitHub Actions runner pre-installed
43+
resource "azurerm_resource_group" "images" {
44+
name = var.image_resource_group
45+
location = var.location
46+
47+
tags = {
48+
Environment = "CI"
49+
ManagedBy = "Terraform"
50+
Purpose = "Custom-Images"
51+
}
52+
}
53+
54+
# Reference custom golden image
55+
# PREREQUISITE: The golden image must be created separately and contain:
56+
# - Windows Server with SQL Server pre-installed
57+
# - GitHub Actions runner binaries at C:\actions-runner
58+
# - All required dependencies (PowerShell modules, etc.)
2359
data "azurerm_image" "golden" {
2460
name = var.image_name
25-
resource_group_name = var.image_resource_group
61+
resource_group_name = azurerm_resource_group.images.name
62+
63+
depends_on = [azurerm_resource_group.images]
2664
}
2765

2866
# Create virtual network for VMSS
@@ -116,10 +154,41 @@ resource "azurerm_virtual_machine_scale_set_extension" "vmss" {
116154

117155
# Grant VMSS managed identity access to Key Vault secrets
118156
resource "azurerm_role_assignment" "vmss_kv_secrets_user" {
119-
scope = data.azurerm_key_vault.vmss.id
157+
scope = azurerm_key_vault.vmss.id
120158
role_definition_name = "Key Vault Secrets User"
121159
principal_id = azurerm_windows_virtual_machine_scale_set.vmss.identity[0].principal_id
122160

123-
# Wait for VMSS to be created
124-
depends_on = [azurerm_windows_virtual_machine_scale_set.vmss]
161+
depends_on = [
162+
azurerm_windows_virtual_machine_scale_set.vmss,
163+
azurerm_key_vault.vmss
164+
]
165+
}
166+
167+
# Grant Terraform service principal access to manage Key Vault secrets
168+
resource "azurerm_role_assignment" "terraform_kv_secrets_officer" {
169+
scope = azurerm_key_vault.vmss.id
170+
role_definition_name = "Key Vault Secrets Officer"
171+
principal_id = data.azurerm_client_config.current.object_id
172+
173+
depends_on = [azurerm_key_vault.vmss]
174+
}
175+
176+
# Wait for role assignment to propagate
177+
resource "time_sleep" "wait_for_rbac" {
178+
create_duration = "60s"
179+
180+
depends_on = [azurerm_role_assignment.terraform_kv_secrets_officer]
181+
}
182+
183+
# Store GitHub runner token in Key Vault
184+
resource "azurerm_key_vault_secret" "github_runner_token" {
185+
name = "GITHUB-RUNNER-TOKEN"
186+
value = var.github_token
187+
key_vault_id = azurerm_key_vault.vmss.id
188+
189+
depends_on = [
190+
azurerm_key_vault.vmss,
191+
azurerm_role_assignment.terraform_kv_secrets_officer,
192+
time_sleep.wait_for_rbac
193+
]
125194
}

0 commit comments

Comments
 (0)