From c17b3b9f30de0eefc84b4bd632e20c1b51d035a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Sch=C3=B6dl?= Date: Sun, 19 Jul 2026 16:33:08 +0200 Subject: [PATCH] fix: run az in pinned azure/cli container to avoid Python 3.14 deadlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runner's bundled Azure CLI is on Python 3.14, which deadlocks on concurrent command-module loading (Azure/azure-cli#32980) — Start environment failed at "Attach public IP". Run all az commands in the pinned azure/cli@v2 container (known-good CLI/Python) instead. Date math stays on the host since the container image is Alpine (no GNU date). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/deploy-ansible.yml | 30 +++++++---- .github/workflows/vm-start.yml | 80 +++++++++++++--------------- .github/workflows/vm-stop.yml | 42 +++++++-------- 3 files changed, 75 insertions(+), 77 deletions(-) diff --git a/.github/workflows/deploy-ansible.yml b/.github/workflows/deploy-ansible.yml index 1f75f4c..2858be6 100644 --- a/.github/workflows/deploy-ansible.yml +++ b/.github/workflows/deploy-ansible.yml @@ -35,17 +35,23 @@ jobs: tenant-id: ${{ vars.AZURE_TENANT_ID }} subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + # az runs in the pinned azure/cli container to avoid the Python 3.14 + # command-module deadlock in the runner's bundled CLI (Azure/azure-cli#32980). - name: Resolve VM public IP - run: | - IP=$(az vm show -d \ - --resource-group "$RESOURCE_GROUP" \ - --name "$VM_NAME" \ - --query publicIps -o tsv) - if [ -z "$IP" ]; then - echo "::error::VM has no public IP. Run 'Start environment' first." - exit 1 - fi - echo "VM_IP=$IP" >> "$GITHUB_ENV" + id: ip + uses: azure/cli@v2 + with: + azcliversion: "2.64.0" + inlineScript: | + IP=$(az vm show -d \ + --resource-group "$RESOURCE_GROUP" \ + --name "$VM_NAME" \ + --query publicIps -o tsv) + if [ -z "$IP" ]; then + echo "::error::VM has no public IP. Run 'Start environment' first." + exit 1 + fi + echo "ip=$IP" >> "$GITHUB_OUTPUT" - name: Set up Python uses: actions/setup-python@v5 @@ -64,9 +70,13 @@ jobs: known_hosts: 'placeholder' # Will be overwritten by ssh-keyscan next - name: Scan VM Host Key + env: + VM_IP: ${{ steps.ip.outputs.ip }} run: ssh-keyscan -H "$VM_IP" >> ~/.ssh/known_hosts - name: Write inventory with the resolved IP + env: + VM_IP: ${{ steps.ip.outputs.ip }} run: | cat > infra/inventory.ini <> "$GITHUB_ENV" + run: echo "shutdown=$(date -u -d "+${MINUTES} minutes" +%H%M)" >> "$GITHUB_OUTPUT" - - name: Publish the URL + - name: Start the environment + uses: azure/cli@v2 env: + SHUTDOWN: ${{ steps.t.outputs.shutdown }} MINUTES: ${{ github.event.inputs.minutes }} - run: | - IP=$(az network public-ip show \ - --resource-group "$RESOURCE_GROUP" \ - --name "$PIP_NAME" \ - --query ipAddress -o tsv) - { - echo "### 🚀 Environment starting" - echo "" - echo "**URL:** http://$IP (allow ~1–2 min for containers to come up)" - echo "" - echo "Auto-shutdown armed for **${SHUTDOWN_UTC} UTC** (in ${MINUTES} min)." - echo "Run **Stop environment** to end early and release the IP." - } >> "$GITHUB_STEP_SUMMARY" + with: + azcliversion: "2.64.0" + inlineScript: | + set -e + az network public-ip create \ + --resource-group "$RESOURCE_GROUP" --name "$PIP_NAME" \ + --location "$LOCATION" --sku Standard --allocation-method Static \ + --only-show-errors + az network nic ip-config update \ + --resource-group "$RESOURCE_GROUP" --nic-name "$NIC_NAME" \ + --name "$IP_CONFIG" --public-ip-address "$PIP_NAME" --only-show-errors + az vm start --resource-group "$RESOURCE_GROUP" --name "$VM_NAME" + az vm auto-shutdown \ + --resource-group "$RESOURCE_GROUP" --name "$VM_NAME" --time "$SHUTDOWN" + IP=$(az network public-ip show \ + --resource-group "$RESOURCE_GROUP" --name "$PIP_NAME" \ + --query ipAddress -o tsv) + echo "Environment URL: http://$IP (auto-shutdown ${SHUTDOWN} UTC, in ${MINUTES} min)" + { + echo "### 🚀 Environment starting" + echo "" + echo "**URL:** http://$IP (allow ~1–2 min for containers to come up)" + echo "" + echo "Auto-shutdown armed for **${SHUTDOWN} UTC** (in ${MINUTES} min)." + echo "Run **Stop environment** to end early and release the IP." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/vm-stop.yml b/.github/workflows/vm-stop.yml index 8ef571b..d4ed29b 100644 --- a/.github/workflows/vm-stop.yml +++ b/.github/workflows/vm-stop.yml @@ -3,6 +3,9 @@ name: Stop environment # Deallocates the VM (stops compute billing) and deletes the public IP, so the # only ongoing cost while idle is the OS disk. Safe to run anytime; the VM's # data and containers survive on the disk and return on the next start. +# +# az runs inside the pinned azure/cli container to avoid the Python 3.14 +# command-module deadlock in the runner's bundled CLI (Azure/azure-cli#32980). on: workflow_dispatch: @@ -29,26 +32,19 @@ jobs: tenant-id: ${{ vars.AZURE_TENANT_ID }} subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} - - name: Deallocate the VM - run: az vm deallocate --resource-group "$RESOURCE_GROUP" --name "$VM_NAME" - - - name: Detach and delete the public IP - run: | - az network nic ip-config update \ - --resource-group "$RESOURCE_GROUP" \ - --nic-name "$NIC_NAME" \ - --name "$IP_CONFIG" \ - --remove publicIpAddress \ - --only-show-errors || true - az network public-ip delete \ - --resource-group "$RESOURCE_GROUP" \ - --name "$PIP_NAME" \ - --only-show-errors || true - - - name: Summary - run: | - { - echo "### 🛑 Environment stopped" - echo "" - echo "VM deallocated and public IP released." - } >> "$GITHUB_STEP_SUMMARY" + - name: Stop the environment + uses: azure/cli@v2 + with: + azcliversion: "2.64.0" + inlineScript: | + az vm deallocate --resource-group "$RESOURCE_GROUP" --name "$VM_NAME" + az network nic ip-config update \ + --resource-group "$RESOURCE_GROUP" --nic-name "$NIC_NAME" \ + --name "$IP_CONFIG" --remove publicIpAddress --only-show-errors || true + az network public-ip delete \ + --resource-group "$RESOURCE_GROUP" --name "$PIP_NAME" --only-show-errors || true + { + echo "### 🛑 Environment stopped" + echo "" + echo "VM deallocated and public IP released. Idle cost is now disk-only (~€0.04/day)." + } >> "$GITHUB_STEP_SUMMARY"