-
Notifications
You must be signed in to change notification settings - Fork 62
388 lines (333 loc) · 14.5 KB
/
azure-test.yml
File metadata and controls
388 lines (333 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
name: "Azure: Test Image"
# Required repository configuration:
# secrets: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID,
# MATTERMOST_WEBHOOK_URL
# vars: MATTERMOST_CHANNEL
#
# Required Azure RBAC actions for the service principal behind AZURE_CLIENT_ID
# (assign at the rg-alma-images resource-group scope):
# Microsoft.Compute/galleries/images/read
# Microsoft.Compute/virtualMachines/write
# Microsoft.Compute/virtualMachines/delete
# Microsoft.Compute/virtualMachines/deletePreservedOSDisk/action
# Microsoft.Compute/disks/delete
# Microsoft.Network/networkInterfaces/write
# Microsoft.Network/networkInterfaces/join/action
# Microsoft.Network/networkInterfaces/delete
# Microsoft.Network/networkSecurityGroups/read
# Microsoft.Network/networkSecurityGroups/write
# Microsoft.Network/networkSecurityGroups/join/action
# Microsoft.Network/networkSecurityGroups/delete
# Microsoft.Network/publicIPAddresses/read
# Microsoft.Network/publicIPAddresses/write
# Microsoft.Network/publicIPAddresses/join/action
# Microsoft.Network/publicIPAddresses/delete
# Microsoft.Network/virtualNetworks/write
# Microsoft.Network/virtualNetworks/subnets/join/action
# Microsoft.Resources/deployments/read
# Microsoft.Resources/deployments/write
# Microsoft.Resources/deployments/operationStatuses/read
on:
workflow_dispatch:
inputs:
compute_gallery_path:
description: "Compute Gallery Path: gallery_name/vm_image_definition/vm_image_version"
required: true
type: string
default: 'almalinux/almalinux-9-gen2/9.7.2026050101'
notify_mattermost:
description: "Send notification to Mattermost"
required: true
type: boolean
default: true
permissions:
id-token: write
contents: read
env:
RESOURCE_GROUP: rg-alma-images
AZURE_LOCATION: East US
AZURE_PORTAL_BASE_URL: https://portal.azure.com/#@/resource
jobs:
test-image:
name: "Test Azure Compute Gallery Image"
runs-on: ubuntu-24.04
env:
SSH_USER: almalinux
steps:
- uses: actions/checkout@v6
- name: Validate input
run: |
COMPUTE_GALLERY_PATH="${{ inputs.compute_gallery_path }}"
if [[ ! "${COMPUTE_GALLERY_PATH}" =~ ^[^/]+/[^/]+/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "[Error] Invalid Compute Gallery Path: '${COMPUTE_GALLERY_PATH}'"
echo "Expected format: gallery_name/vm_image_definition/vm_image_version"
echo "Example: almalinux/almalinux-9-gen2/9.7.2026050101"
exit 1
fi
echo "COMPUTE_GALLERY_PATH=${COMPUTE_GALLERY_PATH}" >> "$GITHUB_ENV"
- name: Parse Compute Gallery Path
run: |
IFS='/' read -r GALLERY_NAME VM_IMAGE_DEFINITION VM_IMAGE_VERSION <<< "${COMPUTE_GALLERY_PATH}"
IFS='.' read -r ALMA_MAJOR V_PART2 V_PART3 <<< "${VM_IMAGE_VERSION}"
# Kitten image-definitions use Major.Datestamp.Iteration with no minor
# (e.g. 10.20260501.0). Stable AlmaLinux uses Major.Minor.Patch where
# Patch encodes the datestamp+iteration (e.g. 9.7.2026050101).
case "${VM_IMAGE_DEFINITION}" in
*kitten*)
ALMA_VERSION="${ALMA_MAJOR}"
DATESTAMP_ITERATION="${V_PART2}.${V_PART3}"
RELEASE_STRING="AlmaLinux Kitten release ${ALMA_MAJOR}"
;;
*)
ALMA_VERSION="${ALMA_MAJOR}.${V_PART2}"
DATESTAMP_ITERATION="${V_PART3}"
RELEASE_STRING="AlmaLinux release ${ALMA_VERSION}"
;;
esac
echo "GALLERY_NAME=${GALLERY_NAME}"
echo "VM_IMAGE_DEFINITION=${VM_IMAGE_DEFINITION}"
echo "VM_IMAGE_VERSION=${VM_IMAGE_VERSION}"
echo "ALMA_VERSION=${ALMA_VERSION}"
echo "DATESTAMP_ITERATION=${DATESTAMP_ITERATION}"
echo "RELEASE_STRING=${RELEASE_STRING}"
{
echo "GALLERY_NAME=${GALLERY_NAME}"
echo "VM_IMAGE_DEFINITION=${VM_IMAGE_DEFINITION}"
echo "VM_IMAGE_VERSION=${VM_IMAGE_VERSION}"
echo "ALMA_VERSION=${ALMA_VERSION}"
echo "DATESTAMP_ITERATION=${DATESTAMP_ITERATION}"
echo "RELEASE_STRING=${RELEASE_STRING}"
} >> "$GITHUB_ENV"
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y netcat-openbsd
- name: Azure login
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Resolve gallery image version and architecture
run: |
JSON=$(az sig image-version show \
--resource-group "${RESOURCE_GROUP}" \
--gallery-name "${GALLERY_NAME}" \
--gallery-image-definition "${VM_IMAGE_DEFINITION}" \
--gallery-image-version "${VM_IMAGE_VERSION}" \
--output json) || {
echo "[Error] Gallery image version not found: ${COMPUTE_GALLERY_PATH}"
exit 1
}
IMAGE_ID=$(jq -r '.id // empty' <<< "${JSON}")
# az sig image-version show keeps a 'properties' wrapper on some CLI /
# API combinations and flattens it on others; query both shapes.
SOURCE_URI=$(jq -r '
.storageProfile.osDiskImage.source.uri //
.properties.storageProfile.osDiskImage.source.uri //
empty
' <<< "${JSON}")
if [ -z "${IMAGE_ID}" ] || [ -z "${SOURCE_URI}" ]; then
echo "[Error] Could not extract image-version metadata for ${COMPUTE_GALLERY_PATH}"
echo "Raw JSON:"
echo "${JSON}"
exit 1
fi
VHD_FILE="${SOURCE_URI##*/}"
echo "Image ID: ${IMAGE_ID}"
echo "Source VHD: ${VHD_FILE}"
# Reverse-engineer architecture from the VHD source filename, using the
# same regex pair as the VHD path in .github/workflows/azure-to-gallery.yml.
regex_azure='-([0-9]+\.?[0-9]*)-([0-9]{8,9}(\.[0-9])?).*\.(x86_64|aarch64|arm64)'
regex_simple='almalinux-([0-9]+\.[0-9]+)-(x86_64|aarch64|arm64)\.([0-9]{8})'
if [[ $VHD_FILE =~ $regex_azure ]]; then
ALMA_ARCH="${BASH_REMATCH[4]}"
elif [[ $VHD_FILE =~ $regex_simple ]]; then
ALMA_ARCH="${BASH_REMATCH[2]}"
else
echo "[Error] Could not parse architecture from VHD source: '${VHD_FILE}'"
exit 1
fi
# Normalize arm64 -> aarch64 (the in-VM tests grep rpm output that uses
# the uname-style arch).
[ "${ALMA_ARCH}" = "arm64" ] && ALMA_ARCH="aarch64"
# Map arch -> Azure VM size
case "${ALMA_ARCH}" in
x86_64) VM_SIZE="Standard_D2as_v5" ;;
aarch64) VM_SIZE="Standard_D2ps_v5" ;;
esac
# CUSTOM_IMAGE_NAME (used as artifact name and notification label) is
# the source VHD filename without the .vhd extension.
CUSTOM_IMAGE_NAME="${VHD_FILE%.vhd}"
echo "ALMA_ARCH=${ALMA_ARCH}"
echo "VM_SIZE=${VM_SIZE}"
echo "CUSTOM_IMAGE_NAME=${CUSTOM_IMAGE_NAME}"
{
echo "IMAGE_ID=${IMAGE_ID}"
echo "VHD_FILE=${VHD_FILE}"
echo "ALMA_ARCH=${ALMA_ARCH}"
echo "VM_SIZE=${VM_SIZE}"
echo "CUSTOM_IMAGE_NAME=${CUSTOM_IMAGE_NAME}"
} >> "$GITHUB_ENV"
- name: Generate ephemeral SSH keypair
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -N '' -C "azure-test-${GITHUB_RUN_ID}" -f ~/.ssh/azure_test
- name: Launch test VM
run: |
VM_NAME="azure-test-${ALMA_VERSION}-${DATESTAMP_ITERATION}-${ALMA_ARCH}-${GITHUB_RUN_ID}"
echo "VM Name: ${VM_NAME}"
VM_ID=$(az vm create \
--resource-group "${RESOURCE_GROUP}" \
--location "${AZURE_LOCATION}" \
--name "${VM_NAME}" \
--image "${IMAGE_ID}" \
--size "${VM_SIZE}" \
--admin-username "${SSH_USER}" \
--ssh-key-values ~/.ssh/azure_test.pub \
--public-ip-sku Standard \
--os-disk-size-gb 100 \
--nsg-rule SSH \
--query id --output tsv)
echo "VM ID: ${VM_ID}"
{
echo "VM_NAME=${VM_NAME}"
echo "VM_ID=${VM_ID}"
} >> "$GITHUB_ENV"
- name: Resolve VM public IP
run: |
PUBLIC_IP=$(az vm show \
--resource-group "${RESOURCE_GROUP}" \
--name "${VM_NAME}" \
--show-details \
--query publicIps --output tsv)
if [ -z "${PUBLIC_IP}" ] || [ "${PUBLIC_IP}" = "null" ]; then
echo "[Error] VM has no public IP"
exit 1
fi
echo "Public IP: ${PUBLIC_IP}"
echo "PUBLIC_IP=${PUBLIC_IP}" >> "$GITHUB_ENV"
- name: Wait for SSH
run: |
for i in $(seq 1 60); do
if nc -z -w2 "${PUBLIC_IP}" 22; then
echo "[Info] SSH port reachable after ${i} attempt(s)"
break
fi
if [ "${i}" -eq 60 ]; then
echo "[Error] SSH did not become reachable within 10 minutes"
exit 1
fi
sleep 10
done
ssh-keyscan -T 5 "${PUBLIC_IP}" >> ~/.ssh/known_hosts 2>/dev/null
- name: Run image tests
run: |
SSH=(ssh -i ~/.ssh/azure_test -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 "${SSH_USER}@${PUBLIC_IP}")
echo "[Debug] AlmaLinux release:"
ALMA_RELEASE=$("${SSH[@]}" "grep '${RELEASE_STRING}' /etc/almalinux-release")
echo "${ALMA_RELEASE}"
echo "[Debug] AlmaLinux release package:"
RELEASE_PACKAGE=$("${SSH[@]}" "rpm -qf /etc/almalinux-release")
echo "${RELEASE_PACKAGE}"
echo "[Debug] System architecture:"
SYSTEM_ARCH=$("${SSH[@]}" "rpm -q --qf='%{ARCH}\n' ${RELEASE_PACKAGE} | grep '${ALMA_ARCH}'")
echo "${SYSTEM_ARCH}"
echo "[Debug] Azure-specific RPM packages:"
# Installed by ansible/roles/azure_guest/tasks/main.yml. rpm -q exits
# non-zero (and prints "package X is not installed") if any are missing,
# which fails the step under set -e.
"${SSH[@]}" "rpm -q WALinuxAgent hyperv-daemons NetworkManager-cloud-setup cifs-utils"
echo "[Debug] Azure Linux Agent (waagent) service:"
"${SSH[@]}" "systemctl is-enabled waagent.service"
echo "[Debug] Disk and filesystems:"
"${SSH[@]}" "sudo lsblk"
"${SSH[@]}" 'ROOT_SIZE_BYTES=$(df -B1 --output=size / | tail -n 1 | tr -d " "); MIN_SIZE_BYTES=$((98*1024*1024*1024)); [ "${ROOT_SIZE_BYTES}" -gt "${MIN_SIZE_BYTES}" ] || { echo "[Error] Root filesystem resize check failed: ${ROOT_SIZE_BYTES} bytes (expected > ${MIN_SIZE_BYTES} bytes)"; exit 1; }'
echo "[Debug] Check for updates:"
# dnf check-update returns 100 when updates are available — treat as success
rc=0
"${SSH[@]}" "sudo dnf check-update" || rc=$?
if [ "${rc}" -ne 0 ] && [ "${rc}" -ne 100 ]; then
echo "[Error] dnf check-update failed with exit code ${rc}"
exit "${rc}"
fi
PKG_FILE="${VHD_FILE}.txt"
"${SSH[@]}" "rpm -qa --queryformat '%{NAME}\n' | sort > /tmp/${PKG_FILE}"
scp -i ~/.ssh/azure_test -o StrictHostKeyChecking=accept-new \
"${SSH_USER}@${PUBLIC_IP}:/tmp/${PKG_FILE}" "./${PKG_FILE}"
{
echo "PKG_FILE=${PKG_FILE}"
echo "ALMA_RELEASE=${ALMA_RELEASE}"
echo "SYSTEM_ARCH=${SYSTEM_ARCH}"
} >> "$GITHUB_ENV"
- name: Upload packages list artifact
if: env.PKG_FILE != ''
uses: actions/upload-artifact@v7
with:
name: ${{ env.PKG_FILE }}
path: ./${{ env.PKG_FILE }}
- name: Job summary
if: always() && env.CUSTOM_IMAGE_NAME != ''
run: |
{
echo "## Azure Image Test"
echo ""
echo "- **Compute Gallery Image**: [${COMPUTE_GALLERY_PATH}](${AZURE_PORTAL_BASE_URL}${IMAGE_ID})"
echo "- **Size**: \`${VM_SIZE}\`"
if [ -n "${VM_ID:-}" ]; then
echo "- **Test VM**: [${VM_NAME}](${AZURE_PORTAL_BASE_URL}${VM_ID})"
echo "- **Public IP**: \`${PUBLIC_IP:-n/a}\`"
fi
if [ -n "${ALMA_RELEASE:-}" ]; then
echo "- **AlmaLinux release**: \`${ALMA_RELEASE}\`"
fi
if [ -n "${SYSTEM_ARCH:-}" ]; then
echo "- **System architecture**: \`${SYSTEM_ARCH}\`"
fi
echo "- **Test**: ${{ job.status == 'success' && 'passed ✅' || 'failed ❌' }}"
} >> "$GITHUB_STEP_SUMMARY"
- name: Terminate test VM
if: always() && env.VM_NAME != ''
run: |
OS_DISK=$(az vm show \
--resource-group "${RESOURCE_GROUP}" \
--name "${VM_NAME}" \
--query 'storageProfile.osDisk.name' --output tsv 2>/dev/null || true)
az vm delete \
--resource-group "${RESOURCE_GROUP}" \
--name "${VM_NAME}" \
--yes --force-deletion true || true
if [ -n "${OS_DISK}" ]; then
az disk delete \
--resource-group "${RESOURCE_GROUP}" \
--name "${OS_DISK}" \
--yes --no-wait || true
fi
az network nic delete \
--resource-group "${RESOURCE_GROUP}" \
--name "${VM_NAME}VMNic" \
--no-wait || true
az network public-ip delete \
--resource-group "${RESOURCE_GROUP}" \
--name "${VM_NAME}PublicIP" \
--no-wait || true
az network nsg delete \
--resource-group "${RESOURCE_GROUP}" \
--name "${VM_NAME}NSG" \
--no-wait || true
- name: Send notification to Mattermost
uses: mattermost/action-mattermost-notify@master
if: always() && inputs.notify_mattermost && env.CUSTOM_IMAGE_NAME != ''
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }}
MATTERMOST_CHANNEL: ${{ vars.MATTERMOST_CHANNEL }}
MATTERMOST_USERNAME: ${{ github.triggering_actor }}
TEXT: |
:almalinux: **${{ env.CUSTOM_IMAGE_NAME }}**, Azure image test, by the GitHub [Action](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
**Compute Gallery Image**: [${{ env.COMPUTE_GALLERY_PATH }}](${{ env.AZURE_PORTAL_BASE_URL }}${{ env.IMAGE_ID }})
${{ env.VM_ID && format('**Test VM**: [{0}]({1}{2})', env.VM_NAME, env.AZURE_PORTAL_BASE_URL, env.VM_ID) || '' }}
**Size**: `${{ env.VM_SIZE }}`
${{ env.ALMA_RELEASE && format('**AlmaLinux release**: `{0}`', env.ALMA_RELEASE) || '' }}
${{ env.SYSTEM_ARCH && format('**System architecture**: `{0}`', env.SYSTEM_ARCH) || '' }}
**Test**: ${{ job.status == 'success' && 'passed ✅' || 'failed ❌' }}