modularize workflow

This commit is contained in:
zack 2024-10-19 22:58:49 -04:00
parent b0c8f2f969
commit 9855142340
No known key found for this signature in database
GPG key ID: 5F873416BCF59F35
3 changed files with 201 additions and 183 deletions

40
.github/actions/trigger-hydra-build.yml vendored Normal file
View file

@ -0,0 +1,40 @@
name: 'Trigger Hydra Build'
description: 'Triggers a build on a Hydra CI server'
inputs:
hydra_instance:
description: 'URL of the Hydra instance'
required: true
hydra_project:
description: 'Name of the Hydra project'
required: true
hydra_jobset:
description: 'Name of the Hydra jobset'
required: true
hydra_username:
description: 'Username for Hydra authentication'
required: true
hydra_password:
description: 'Password for Hydra authentication'
required: true
runs:
using: "composite"
steps:
- name: Trigger Hydra build
shell: bash
run: |
AUTH_HEADER="Authorization: Basic $(echo -n '${{ inputs.hydra_username }}:${{ inputs.hydra_password }}' | base64)"
response=$(curl -s -X POST \
-H "Content-Type: application/json" \
-H "$AUTH_HEADER" \
-H "Origin: ${{ inputs.hydra_instance }}" \
-d '{"jobsets": ["${{ inputs.hydra_project }}:${{ inputs.hydra_jobset }}"]}' \
"${{ inputs.hydra_instance }}/api/push")
if [[ $response == *"\"submitted\":true"* ]]; then
echo "Hydra build triggered successfully"
else
echo "Failed to trigger Hydra build"
echo "Response: $response"
exit 1
fi

View file

@ -0,0 +1,91 @@
name: 'Wait for Hydra Build'
description: 'Waits for a Hydra build to complete and returns the build status'
inputs:
hydra_instance:
description: 'URL of the Hydra instance'
required: true
hydra_project:
description: 'Name of the Hydra project'
required: true
hydra_jobset:
description: 'Name of the Hydra jobset'
required: true
hydra_username:
description: 'Username for Hydra authentication'
required: true
hydra_password:
description: 'Password for Hydra authentication'
required: true
max_attempts:
description: 'Maximum number of attempts to check build status'
required: false
default: '60'
wait_interval:
description: 'Interval in seconds between status checks'
required: false
default: '120'
outputs:
build_success:
description: 'Whether the build succeeded (true) or failed (false)'
value: ${{ steps.wait-for-build.outputs.build_success }}
runs:
using: "composite"
steps:
- name: Wait for Hydra build
id: wait-for-build
shell: bash
run: |
AUTH_HEADER="Authorization: Basic $(echo -n '${{ inputs.hydra_username }}:${{ inputs.hydra_password }}' | base64)"
attempt=0
max_attempts=${{ inputs.max_attempts }}
wait_interval=${{ inputs.wait_interval }}
while [ $attempt -lt $max_attempts ]; do
echo "Attempt $((attempt + 1))/$max_attempts"
response=$(curl -s -H "$AUTH_HEADER" \
"${{ inputs.hydra_instance }}/api/jobsets?project=${{ inputs.hydra_project }}")
jobset=$(echo "$response" | jq -r '.[] | select(.name == "${{ inputs.hydra_jobset }}")')
if [ -z "$jobset" ]; then
echo "Jobset ${{ inputs.hydra_jobset }} not found. Waiting..."
sleep $wait_interval
attempt=$((attempt + 1))
continue
fi
nrscheduled=$(echo "$jobset" | jq -r '.nrscheduled')
nrfailed=$(echo "$jobset" | jq -r '.nrfailed')
nrsucceeded=$(echo "$jobset" | jq -r '.nrsucceeded')
nrtotal=$(echo "$jobset" | jq -r '.nrtotal')
echo "Status: nrscheduled=$nrscheduled, nrfailed=$nrfailed, nrsucceeded=$nrsucceeded, nrtotal=$nrtotal"
if [ "$nrtotal" = "0" ]; then
echo "Build not started yet. Waiting..."
elif [ "$nrfailed" != "0" ]; then
echo "build_success=false" >> $GITHUB_OUTPUT
echo "Build failed"
exit 0
elif [ "$nrsucceeded" = "$nrtotal" ] && [ "$nrtotal" != "0" ]; then
echo "build_success=true" >> $GITHUB_OUTPUT
echo "Build succeeded"
exit 0
elif [ "$nrscheduled" = "0" ] && [ "$nrsucceeded" != "0" ] && [ "$nrsucceeded" = "$nrtotal" ]; then
echo "build_success=true" >> $GITHUB_OUTPUT
echo "Build succeeded"
exit 0
else
echo "Build in progress. Waiting..."
fi
sleep $wait_interval
attempt=$((attempt + 1))
done
echo "build_success=false" >> $GITHUB_OUTPUT
echo "Timeout reached. Build considered failed."
exit 1

View file

@ -16,24 +16,30 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
pr_exists: ${{ steps.check-pr.outputs.pr_exists }} pr_exists: ${{ steps.check-pr.outputs.pr_exists }}
pr_number: ${{ steps.check-pr.outputs.pr_number }}
steps: steps:
- name: Check for existing PR - name: Check for existing PR
id: check-pr id: check-pr
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
existing_pr=$(gh pr list --repo ${{ github.repository }} --head "auto-update-flake-" --state open --json number --jq length) existing_pr=$(gh pr list --repo ${{ github.repository }} --head "auto-update-flake-" --state open --json number --jq '.[0].number')
echo "pr_exists=$existing_pr" >> $GITHUB_OUTPUT if [ -n "$existing_pr" ]; then
echo "pr_exists=true" >> $GITHUB_OUTPUT
echo "pr_number=$existing_pr" >> $GITHUB_OUTPUT
else
echo "pr_exists=false" >> $GITHUB_OUTPUT
fi
update-flake: update-flake:
needs: check-existing-pr needs: check-existing-pr
if: needs.check-existing-pr.outputs.pr_exists == '0' if: needs.check-existing-pr.outputs.pr_exists == 'false'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 # Fetch all history for all branches and tags fetch-depth: 0
- name: Install Nix - name: Install Nix
uses: cachix/install-nix-action@v30 uses: cachix/install-nix-action@v30
@ -47,8 +53,6 @@ jobs:
git diff git diff
if [[ -n $(git status -s) ]]; then if [[ -n $(git status -s) ]]; then
echo "CHANGED=true" >> $GITHUB_OUTPUT echo "CHANGED=true" >> $GITHUB_OUTPUT
echo "Changes detected:"
git status -s
else else
echo "CHANGED=false" >> $GITHUB_OUTPUT echo "CHANGED=false" >> $GITHUB_OUTPUT
echo "No changes detected." echo "No changes detected."
@ -64,154 +68,53 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
if: steps.update-flake.outputs.CHANGED == 'true' if: steps.update-flake.outputs.CHANGED == 'true'
id: create-pr
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
gh pr create --title "Auto-update Nix flake dependencies" \ pr_number=$(gh pr create --title "Auto-update Nix flake dependencies" \
--body "This PR updates the Nix flake dependencies." \ --body "This PR updates the Nix flake dependencies." \
--base main \ --base main \
--head ${{ env.BRANCH_NAME }} --head ${{ env.BRANCH_NAME }} \
--json number --jq .number)
- name: Get Hydra session token echo "pr_number=$pr_number" >> $GITHUB_OUTPUT
id: hydra-session
run: |
response=$(curl -X POST -i \
'${{ env.HYDRA_INSTANCE }}/login' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Origin: ${{ env.HYDRA_INSTANCE }}' \
-d '{
"username": "${{ secrets.HYDRA_USERNAME }}",
"password": "${{ secrets.HYDRA_PASSWORD }}"
}')
session_cookie=$(echo "$response" | grep -i 'set-cookie' | sed -n 's/.*hydra_session=\([^;]*\).*/\1/p')
echo "SESSION_COOKIE=$session_cookie" >> $GITHUB_OUTPUT
- name: Create Hydra jobset
if: steps.update-flake.outputs.CHANGED == 'true'
env:
SESSION_COOKIE: ${{ steps.hydra-session.outputs.SESSION_COOKIE }}
run: |
curl -X PUT -H "Content-Type: application/json" \
-H "Cookie: hydra_session=$SESSION_COOKIE" \
-d '{
"enabled": 1,
"visible": false,
"keepnr": 3,
"schedulingshares": 100,
"checkinterval": 60,
"description": "PR #${{ github.event.pull_request.number }} - Auto-update flake dependencies",
"flake": "github:${{ github.repository }}/${{ env.BRANCH_NAME }}",
"type": 1
}' \
"${{ env.HYDRA_INSTANCE }}/jobset/${{ env.HYDRA_PROJECT }}/${{ env.HYDRA_JOBSET }}"
- name: Trigger Hydra build - name: Trigger Hydra build
if: steps.update-flake.outputs.CHANGED == 'true' if: steps.update-flake.outputs.CHANGED == 'true'
env: uses: ./.github/actions/trigger-hydra-build
SESSION_COOKIE: ${{ steps.hydra-session.outputs.SESSION_COOKIE }} with:
run: | hydra_instance: ${{ env.HYDRA_INSTANCE }}
curl -X POST -H "Content-Type: application/json" \ hydra_project: ${{ env.HYDRA_PROJECT }}
-H "Cookie: hydra_session=$SESSION_COOKIE" \ hydra_jobset: ${{ env.HYDRA_JOBSET }}
-H "Origin: ${{ env.HYDRA_INSTANCE }}" \ hydra_username: ${{ secrets.HYDRA_USERNAME }}
-d '{"jobsets": ["${{ env.HYDRA_PROJECT }}:${{ env.HYDRA_JOBSET }}"]}' \ hydra_password: ${{ secrets.HYDRA_PASSWORD }}
"${{ env.HYDRA_INSTANCE }}/api/push"
- name: Wait for Hydra build - name: Wait for Hydra build
if: steps.update-flake.outputs.CHANGED == 'true' if: steps.update-flake.outputs.CHANGED == 'true'
id: wait-for-build id: wait-for-build
env: uses: ./.github/actions/wait-for-hydra-build
SESSION_COOKIE: ${{ steps.hydra-session.outputs.SESSION_COOKIE }} with:
run: | hydra_instance: ${{ env.HYDRA_INSTANCE }}
max_attempts=60 # 30 minutes (30 * 2 minutes) hydra_project: ${{ env.HYDRA_PROJECT }}
attempt=0 hydra_jobset: ${{ env.HYDRA_JOBSET }}
build_status="unknown" hydra_username: ${{ secrets.HYDRA_USERNAME }}
set -e # Exit immediately if a command exits with a non-zero status hydra_password: ${{ secrets.HYDRA_PASSWORD }}
while [ $attempt -lt $max_attempts ]; do
echo "Attempt $((attempt + 1))/$max_attempts"
response=$(curl -s -H "Cookie: hydra_session=$SESSION_COOKIE" \
"${{ env.HYDRA_INSTANCE }}/api/jobsets?project=${{ env.HYDRA_PROJECT }}")
jobset=$(echo "$response" | jq -r '.[] | select(.name == "${{ env.HYDRA_JOBSET }}")')
if [ -z "$jobset" ]; then
echo "Jobset ${{ env.HYDRA_JOBSET }} not found. Waiting..."
sleep 120
attempt=$((attempt + 1))
continue
fi
echo "here"
nrscheduled=$(echo "$jobset" | jq -r '.nrscheduled')
echo "nrscheduled passed"
nrfailed=$(echo "$jobset" | jq -r '.nrfailed')
echo "nrfailed passed"
nrsucceeded=$(echo "$jobset" | jq -r '.nrsucceeded')
echo "nrsucceeded passed"
nrtotal=$(echo "$jobset" | jq -r '.nrtotal')
echo "nrtotal passed"
echo "Status: nrscheduled=$nrscheduled, nrfailed=$nrfailed, nrsucceeded=$nrsucceeded, nrtotal=$nrtotal"
if [ "$nrtotal" = "0" ]; then
echo "Build not started yet. Waiting..."
elif [ "$nrfailed" != "0" ]; then
build_status="failed"
break
elif [ "$nrsucceeded" = "$nrtotal" ] && [ "$nrtotal" != "0" ]; then
build_status="succeeded"
break
elif [ "$nrscheduled" = "0" ] && [ "$nrsucceeded" != "0" ] && [ "$nrsucceeded" = "$nrtotal" ]; then
build_status="succeeded"
break
else
echo "Build in progress. Waiting..."
fi
sleep 120 # Wait for 2 minutes before checking again
attempt=$((attempt + 1))
done
if [ "$build_status" = "unknown" ]; then
echo "Timeout reached. Considering build as failed."
build_status="failed"
fi
echo "BUILD_SUCCESS=$([ "$build_status" = "succeeded" ] && echo "true" || echo "false")" >> $GITHUB_OUTPUT
if [ "$build_status" = "succeeded" ]; then
echo "Build succeeded!"
exit 0
else
echo "Build failed or timed out."
exit 1
fi
- name: Merge PR if build succeeds - name: Merge PR if build succeeds
if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS == 'true' if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS == 'true'
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
pr_number=$(gh pr list --head ${{ env.BRANCH_NAME }} --json number --jq '.[0].number') gh pr merge ${{ steps.create-pr.outputs.pr_number }} --merge
gh pr merge $pr_number --merge
- name: Comment on PR if build fails - name: Exit if build fails
if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS != 'true' if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS != 'true'
env: run: exit 1
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pr_number=$(gh pr list --head ${{ env.BRANCH_NAME }} --json number --jq '.[0].number')
gh pr comment $pr_number --body "The Hydra build failed. This PR will be updated and retried in 24 hours."
- name: Schedule retry
if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS != 'true'
uses: peter-evans/repository-dispatch@v2
with:
event-type: retry-flake-update
client-payload: '{"pr_number": "${{ steps.create-pr.outputs.pull-request-number }}"}'
retry-update: retry-update:
needs: check-existing-pr
if: needs.check-existing-pr.outputs.pr_exists == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.action == 'retry-flake-update'
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -219,77 +122,61 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Install Nix - name: Install Nix
uses: cachix/install-nix-action@v22 uses: cachix/install-nix-action@v30
- name: Checkout PR branch - name: Checkout PR branch
run: | run: |
pr_number="${{ github.event.client_payload.pr_number }}" pr_number="${{ needs.check-existing-pr.outputs.pr_number }}"
branch_name=$(gh pr view $pr_number --json headRefName -q .headRefName) branch_name=$(gh pr view $pr_number --json headRefName -q .headRefName)
git checkout $branch_name git checkout $branch_name
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update flake dependencies - name: Update flake dependencies
run: nix flake update id: update-flake
- name: Commit and push changes
run: | run: |
git config user.name github-actions git config user.name github-actions
git config user.email github-actions@github.com git config user.email github-actions@github.com
git add flake.lock nix flake update --accept-flake-config
git diff
if [[ -n $(git status -s) ]]; then
echo "CHANGED=true" >> $GITHUB_OUTPUT
git add .
git commit -m "Auto-update flake dependencies (retry)" git commit -m "Auto-update flake dependencies (retry)"
git push origin HEAD git push origin HEAD
else
echo "CHANGED=false" >> $GITHUB_OUTPUT
echo "No changes detected."
fi
- name: Trigger Hydra build - name: Trigger Hydra build
run: | if: steps.update-flake.outputs.CHANGED == 'true'
AUTH_HEADER="Authorization: Basic $(echo -n '${{ secrets.HYDRA_USERNAME }}:${{ secrets.HYDRA_PASSWORD }}' | base64)" uses: ./.github/actions/trigger-hydra-build
curl -X POST -H "Content-Type: application/json" \ with:
-H "$AUTH_HEADER" \ hydra_instance: ${{ env.HYDRA_INSTANCE }}
-H "Origin: ${{ env.HYDRA_INSTANCE }}" \ hydra_project: ${{ env.HYDRA_PROJECT }}
-d '{"jobsets": ["${{ env.HYDRA_PROJECT }}:${{ env.HYDRA_JOBSET }}"]}' \ hydra_jobset: ${{ env.HYDRA_JOBSET }}
"${{ env.HYDRA_INSTANCE }}/api/push" hydra_username: ${{ secrets.HYDRA_USERNAME }}
hydra_password: ${{ secrets.HYDRA_PASSWORD }}
- name: Wait for Hydra build - name: Wait for Hydra build
if: steps.update-flake.outputs.CHANGED == 'true'
id: wait-for-build id: wait-for-build
run: | uses: ./.github/actions/wait-for-hydra-build
AUTH_HEADER="Authorization: Basic $(echo -n '${{ secrets.HYDRA_USERNAME }}:${{ secrets.HYDRA_PASSWORD }}' | base64)" with:
max_attempts=60 # 30 minutes (30 * 2 minutes) hydra_instance: ${{ env.HYDRA_INSTANCE }}
attempt=0 hydra_project: ${{ env.HYDRA_PROJECT }}
while [ $attempt -lt $max_attempts ]; do hydra_jobset: ${{ env.HYDRA_JOBSET }}
response=$(curl -s -H "$AUTH_HEADER" \ hydra_username: ${{ secrets.HYDRA_USERNAME }}
"${{ env.HYDRA_INSTANCE }}/api/jobsets?project=${{ env.HYDRA_PROJECT }}") hydra_password: ${{ secrets.HYDRA_PASSWORD }}
status=$(echo "$response" | jq -r '.[] | select(.name == "${{ env.HYDRA_JOBSET }}") | .nrfailed')
if [ "$status" = "0" ]; then
echo "BUILD_SUCCESS=true" >> $GITHUB_OUTPUT
exit 0
elif [ "$status" != "null" ] && [ "$status" != "0" ]; then
echo "BUILD_SUCCESS=false" >> $GITHUB_OUTPUT
exit 0
fi
sleep 120 # Wait for 2 minutes before checking again
((attempt++))
done
echo "BUILD_SUCCESS=false" >> $GITHUB_OUTPUT # Timeout, consider as failure
- name: Merge PR if build succeeds - name: Merge PR if build succeeds
if: steps.wait-for-build.outputs.BUILD_SUCCESS == 'true' if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS == 'true'
run: |
pr_number="${{ github.event.client_payload.pr_number }}"
gh pr merge $pr_number --merge
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr merge ${{ needs.check-existing-pr.outputs.pr_number }} --merge
- name: Schedule another retry if build fails - name: Exit if build fails
if: steps.wait-for-build.outputs.BUILD_SUCCESS != 'true' if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS != 'true'
uses: peter-evans/create-or-update-comment@v3 run: exit 1
with:
issue-number: ${{ github.event.client_payload.pr_number }}
body: |
The Hydra build failed again. This PR will be updated and retried in 24 hours.
- name: Retry update after 24 hours
if: steps.wait-for-build.outputs.BUILD_SUCCESS != 'true'
uses: peter-evans/repository-dispatch@v2
with:
event-type: retry-flake-update
client-payload: '{"pr_number": "${{ github.event.client_payload.pr_number }}"}'