fix: eliminate race conditions in release workflow chain

The promote-floating-tags and helm-pages workflows now trigger
automatically via workflow_run when publish-docker.yml completes,
instead of being dispatched immediately by create-release.yml.

This ensures Docker images are fully available before:
- Floating tags (rc, latest, major.minor) are promoted
- Helm chart smoke tests try to pull the image

Key changes:
- promote-floating-tags.yml: Add workflow_run trigger, extract tag
  from triggering workflow, wait for BOTH pulse and agent images
- helm-pages.yml: Add workflow_run trigger, extract version from
  triggering workflow
- create-release.yml: Remove manual dispatch for these workflows
This commit is contained in:
rcourtman 2025-12-18 19:33:27 +00:00
parent 9ba4a55520
commit bd7d2c22f7
3 changed files with 126 additions and 43 deletions

View file

@ -493,33 +493,10 @@ jobs:
echo "✓ Docker publish workflow dispatched"
- name: Trigger floating tag promotion
if: ${{ github.event.inputs.draft_only != 'true' }}
continue-on-error: true # Non-fatal if dispatch fails
env:
GH_TOKEN: ${{ secrets.WORKFLOW_PAT }}
run: |
TAG="${{ needs.extract_version.outputs.tag }}"
IS_PRERELEASE="${{ needs.extract_version.outputs.is_prerelease }}"
echo "Triggering floating tag promotion for ${TAG} (prerelease: ${IS_PRERELEASE})..."
gh workflow run promote-floating-tags.yml -f tag="${TAG}" -f prerelease="${IS_PRERELEASE}"
echo "✓ Floating tag promotion workflow dispatched"
- name: Trigger Helm chart release
if: ${{ github.event.inputs.draft_only != 'true' }}
continue-on-error: true # Non-fatal if dispatch fails
env:
GH_TOKEN: ${{ secrets.WORKFLOW_PAT }}
run: |
TAG="${{ needs.extract_version.outputs.tag }}"
VERSION="${TAG#v}"
echo "Triggering Helm chart release for ${VERSION}..."
gh workflow run helm-pages.yml -f chart_version="${VERSION}"
echo "✓ Helm chart release workflow dispatched"
# NOTE: Floating tag promotion and Helm chart release workflows now trigger
# automatically when publish-docker.yml completes via workflow_run.
# No need to dispatch them manually - this eliminates race conditions.
- name: Trigger demo server update
if: ${{ github.event.inputs.draft_only != 'true' }}

View file

@ -1,8 +1,11 @@
name: Release Helm Chart to GitHub Pages
# Only triggered manually or by create-release.yml
# Removing release: published trigger prevents race conditions
# Triggered automatically when publish-docker.yml completes, or manually
# We wait for Docker publish because the smoke test pulls the Docker image
on:
workflow_run:
workflows: ["Publish Docker Images"]
types: [completed]
workflow_dispatch:
inputs:
chart_version:
@ -15,6 +18,8 @@ permissions:
jobs:
release:
runs-on: ubuntu-latest
# Only run if workflow_dispatch OR if workflow_run completed successfully
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v4
@ -56,9 +61,31 @@ jobs:
- name: Determine chart version
id: version
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ inputs.chart_version }}"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
# Manual dispatch - use input directly
VERSION="${{ inputs.chart_version }}"
else
# workflow_run trigger - extract version from the triggering workflow
RUN_ID="${{ github.event.workflow_run.id }}"
echo "Extracting version from workflow run ${RUN_ID}..."
WORKFLOW_DATA=$(gh api repos/${{ github.repository }}/actions/runs/${RUN_ID})
TAG=$(echo "$WORKFLOW_DATA" | jq -r '.display_title' | grep -oP 'v?\d+\.\d+\.\d+(-[a-zA-Z]+\.\d+)?' || echo "")
if [ -z "$TAG" ]; then
echo "::error::Could not extract version from workflow_run"
exit 1
fi
# Remove leading 'v' if present
VERSION="${TAG#v}"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Chart version: $VERSION"
- name: Update Chart.yaml version
run: |

View file

@ -1,8 +1,10 @@
name: Promote Floating Tags
# Only triggered by create-release.yml after Docker images are published
# This prevents race conditions with release: published event
# Triggered automatically when publish-docker.yml completes, or manually
on:
workflow_run:
workflows: ["Publish Docker Images"]
types: [completed]
workflow_dispatch:
inputs:
tag:
@ -18,15 +20,56 @@ on:
jobs:
promote-images:
runs-on: ubuntu-latest
# Only run if workflow_dispatch OR if workflow_run completed successfully
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
permissions:
contents: read
packages: write
env:
OWNER: ${{ github.repository_owner }}
TAG: ${{ inputs.tag }}
PRERELEASE: ${{ inputs.prerelease }}
steps:
- name: Extract tag from trigger
id: extract
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
# Manual dispatch - use inputs directly
TAG="${{ inputs.tag }}"
PRERELEASE="${{ inputs.prerelease }}"
else
# workflow_run trigger - extract from the triggering workflow's inputs
# The publish-docker workflow was triggered with a tag input
RUN_ID="${{ github.event.workflow_run.id }}"
echo "Extracting inputs from workflow run ${RUN_ID}..."
# Get the workflow run details to extract the tag
WORKFLOW_DATA=$(gh api repos/${{ github.repository }}/actions/runs/${RUN_ID})
TAG=$(echo "$WORKFLOW_DATA" | jq -r '.head_branch // ""')
# If head_branch is main, we need to get it from the run's inputs
# The inputs are stored in the run's display_title or we parse from artifacts
if [ "$TAG" = "main" ] || [ -z "$TAG" ]; then
# Try to get from run name which typically includes the tag
TAG=$(echo "$WORKFLOW_DATA" | jq -r '.display_title' | grep -oP 'v\d+\.\d+\.\d+(-[a-zA-Z]+\.\d+)?' || echo "")
fi
if [ -z "$TAG" ]; then
echo "::error::Could not extract tag from workflow_run"
exit 1
fi
# Detect prerelease from tag
if [[ "$TAG" =~ -rc\.[0-9]+$ ]] || [[ "$TAG" =~ -alpha\.[0-9]+$ ]] || [[ "$TAG" =~ -beta\.[0-9]+$ ]]; then
PRERELEASE="true"
else
PRERELEASE="false"
fi
fi
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "prerelease=${PRERELEASE}" >> $GITHUB_OUTPUT
echo "Promoting floating tags for ${TAG} (prerelease: ${PRERELEASE})"
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
@ -40,27 +83,52 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Wait for Docker image to be available
- name: Wait for Docker images to be available
env:
TAG: ${{ steps.extract.outputs.tag }}
run: |
echo "Waiting for rcourtman/pulse:${TAG} to be available..."
echo "Waiting for Docker images with tag ${TAG} to be available..."
MAX_ATTEMPTS=30
ATTEMPT=0
# Wait for main pulse image
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
if docker manifest inspect rcourtman/pulse:${TAG} > /dev/null 2>&1; then
echo "Image rcourtman/pulse:${TAG} is available!"
break
fi
ATTEMPT=$((ATTEMPT + 1))
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS - pulse image not yet available, waiting 10s..."
sleep 10
done
if [ $ATTEMPT -ge $MAX_ATTEMPTS ]; then
echo "Timeout waiting for pulse Docker image"
exit 1
fi
# Also wait for agent image
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
if docker manifest inspect rcourtman/pulse-docker-agent:${TAG} > /dev/null 2>&1; then
echo "Image rcourtman/pulse-docker-agent:${TAG} is available!"
exit 0
fi
ATTEMPT=$((ATTEMPT + 1))
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS - Image not yet available, waiting 10s..."
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS - agent image not yet available, waiting 10s..."
sleep 10
done
echo "Timeout waiting for Docker image"
echo "Timeout waiting for agent Docker image"
exit 1
- name: Promote Pulse server image tags
env:
TAG: ${{ steps.extract.outputs.tag }}
PRERELEASE: ${{ steps.extract.outputs.prerelease }}
OWNER: ${{ github.repository_owner }}
run: |
set -euo pipefail
TAG="${TAG}"
VERSION="${TAG#v}"
BASE_VERSION="${VERSION%%-*}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION"
@ -90,9 +158,12 @@ jobs:
fi
- name: Promote Docker agent image tags
env:
TAG: ${{ steps.extract.outputs.tag }}
PRERELEASE: ${{ steps.extract.outputs.prerelease }}
OWNER: ${{ github.repository_owner }}
run: |
set -euo pipefail
TAG="${TAG}"
VERSION="${TAG#v}"
BASE_VERSION="${VERSION%%-*}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION"
@ -120,9 +191,17 @@ jobs:
fi
- name: Promotion summary
env:
TAG: ${{ steps.extract.outputs.tag }}
PRERELEASE: ${{ steps.extract.outputs.prerelease }}
run: |
VERSION="${TAG#v}"
BASE_VERSION="${VERSION%%-*}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION"
MAJOR_MINOR="$MAJOR.${MINOR:-0}"
if [ "$PRERELEASE" = "true" ]; then
echo "Updated rc tags to point to ${TAG} for both server and agent images."
echo "Updated :rc tags to point to ${TAG} for both server and agent images."
else
echo "Updated latest/${MAJOR_MINOR}/${MAJOR} tags to point to ${TAG} for both server and agent images."
echo "✅ Updated :latest, :${MAJOR_MINOR}, :${MAJOR} tags to point to ${TAG} for both server and agent images."
fi