Skip to content

Plan Rancher Sign-Off repo targets #585

Plan Rancher Sign-Off repo targets

Plan Rancher Sign-Off repo targets #585

Workflow file for this run

name: Plan Rancher Sign-Off
run-name: ${{ github.event.inputs.rancher_version && format('Plan Rancher Sign-Off {0}', github.event.inputs.rancher_version) || 'Plan Rancher Sign-Off repo targets' }}
on:
workflow_dispatch:
inputs:
rancher_version:
description: Optional Rancher alpha or RC, for example v2.15.0-rc2. Leave blank to use signoff-targets.json.
required: false
type: string
previous_rancher_version:
description: Previous Rancher release. Leave blank to resolve automatically.
required: false
type: string
webhook_image:
description: Candidate webhook image. Leave blank to use Docker Hub image from target build.yaml.
required: false
type: string
signing_policy:
description: Signing policy to apply.
required: false
default: auto
type: choice
options:
- auto
- required
- report-only
- skip
dispatch_runs:
description: Dispatch uncovered sign-off lanes after planning.
required: true
default: true
type: boolean
max_parallel_lanes:
description: Maximum active sign-off lane runners to allow.
required: false
default: 4
type: number
rerun_successful_lanes:
description: Dispatch lanes again even if a matching run already succeeded on this branch.
required: true
default: false
type: boolean
rke2_server_count:
description: RKE2 server nodes per HA for dispatched lane runners.
required: true
default: "3"
type: choice
options:
- "1"
- "3"
- "5"
ignore_active_runner_id:
description: Active Run Rancher Sign-Off Lane run id to ignore when this planner was woken by that runner.
required: false
type: string
permissions:
actions: write
contents: read
concurrency:
group: signoff-plan
cancel-in-progress: false
jobs:
plan:
runs-on: ubuntu-latest
environment: rancher-signoff
env:
GH_TOKEN: ${{ github.token }}
AWS_PREFIX: ${{ vars.AWS_PREFIX }}
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
- name: Build automation tools
run: |
go build -o "$RUNNER_TEMP/signoff-plan" ./automation/signoff-plan
- name: Generate sign-off plan
env:
INPUT_RANCHER_VERSION: ${{ github.event.inputs.rancher_version || '' }}
INPUT_PREVIOUS_RANCHER_VERSION: ${{ github.event.inputs.previous_rancher_version || '' }}
INPUT_WEBHOOK_IMAGE: ${{ github.event.inputs.webhook_image || '' }}
INPUT_SIGNING_POLICY: ${{ github.event.inputs.signing_policy || 'auto' }}
run: |
set -euo pipefail
args=()
if [ -n "$INPUT_RANCHER_VERSION" ]; then
args+=("-rancher-version" "$INPUT_RANCHER_VERSION")
else
args+=("-targets" "signoff-targets.json")
fi
if [ -n "$INPUT_PREVIOUS_RANCHER_VERSION" ]; then
args+=("-previous-rancher-version" "$INPUT_PREVIOUS_RANCHER_VERSION")
fi
if [ -n "$INPUT_WEBHOOK_IMAGE" ]; then
args+=("-webhook-image" "$INPUT_WEBHOOK_IMAGE")
fi
args+=("-signing-policy" "$INPUT_SIGNING_POLICY")
args+=("-run-id" "${{ github.run_id }}")
args+=("-ignore-ledger")
args+=("-output" "signoff-plan.json")
if [ -n "${AWS_PREFIX:-}" ]; then
args+=("-aws-base-prefix" "$AWS_PREFIX")
fi
"$RUNNER_TEMP/signoff-plan" "${args[@]}"
- name: Build dispatch queue
id: dispatch-queue
env:
DISPATCH_REQUESTED: ${{ github.event.inputs.dispatch_runs == 'true' }}
MAX_PARALLEL_LANES: ${{ github.event.inputs.max_parallel_lanes || '4' }}
RERUN_SUCCESSFUL_LANES: ${{ github.event.inputs.rerun_successful_lanes == 'true' }}
RKE2_SERVER_COUNT: ${{ github.event.inputs.rke2_server_count || '3' }}
IGNORE_ACTIVE_RUNNER_ID: ${{ github.event.inputs.ignore_active_runner_id || '' }}
REF_NAME: ${{ github.ref_name }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir -p automation-output
jq -c '
def planned:
if has("plans") then .plans[] else . end;
[planned as $plan
| ($plan.lanes // [])[]
| {
rancher_version: $plan.target_version,
lane: .name,
previous_rancher_version: ($plan.previous_version // ""),
webhook_image: ($plan.webhook_image // ""),
signing_policy: ($plan.signing_policy_input // "auto"),
rke2_server_count: env.RKE2_SERVER_COUNT
}]
' signoff-plan.json > automation-output/signoff-dispatch-queue-raw.json
gh run list \
--workflow run-rancher-signoff-lane.yml \
--limit 200 \
--json databaseId,status,conclusion,displayTitle,event,headBranch,createdAt,url \
> automation-output/signoff-lane-runs.json
jq '
[.[] | select((.databaseId | tostring) != env.IGNORE_ACTIVE_RUNNER_ID) | select(.status == "queued" or .status == "in_progress" or .status == "pending" or .status == "requested" or .status == "waiting")]
' automation-output/signoff-lane-runs.json \
> automation-output/signoff-active-runners.json
jq '
[.[] | select(.event == "workflow_dispatch") | select(.headBranch == env.REF_NAME) | select(.status == "completed" and .conclusion == "success")]
' automation-output/signoff-lane-runs.json \
> automation-output/signoff-successful-runners.json
jq -n \
--slurpfile queue automation-output/signoff-dispatch-queue-raw.json \
--slurpfile active automation-output/signoff-active-runners.json \
--slurpfile successful automation-output/signoff-successful-runners.json \
--argjson rerun_successful "$RERUN_SUCCESSFUL_LANES" \
'
def title($lane): "Run \($lane.rancher_version) / \($lane.lane)";
($active[0] | map(.displayTitle) | unique) as $active_titles
| ($successful[0] | map(.displayTitle) | unique) as $successful_titles
| [$queue[0][]
| title(.) as $display_title
| select(($active_titles | index($display_title)) | not)
| select($rerun_successful or (($successful_titles | index($display_title)) | not))]
' > automation-output/signoff-dispatch-queue.json
raw_queue_count="$(jq 'length' automation-output/signoff-dispatch-queue-raw.json)"
active_blocked_count="$(jq -n --slurpfile queue automation-output/signoff-dispatch-queue-raw.json --slurpfile active automation-output/signoff-active-runners.json '
def title($lane): "Run \($lane.rancher_version) / \($lane.lane)";
($active[0] | map(.displayTitle) | unique) as $active_titles
| [$queue[0][]
| title(.) as $display_title
| select(($active_titles | index($display_title)) != null)] | length
')"
successful_skip_count="$(jq -n --argjson rerun_successful "$RERUN_SUCCESSFUL_LANES" --slurpfile queue automation-output/signoff-dispatch-queue-raw.json --slurpfile successful automation-output/signoff-successful-runners.json '
if $rerun_successful then 0
else
def title($lane): "Run \($lane.rancher_version) / \($lane.lane)";
($successful[0] | map(.displayTitle) | unique) as $successful_titles
| [$queue[0][]
| title(.) as $display_title
| select(($successful_titles | index($display_title)) != null)] | length
end
')"
queue_count="$(jq 'length' automation-output/signoff-dispatch-queue.json)"
active_runner_count="$(jq 'length' automation-output/signoff-active-runners.json)"
if ! [[ "$MAX_PARALLEL_LANES" =~ ^[0-9]+$ ]] || [ "$MAX_PARALLEL_LANES" -lt 1 ]; then
echo "max_parallel_lanes must be a positive integer, got '$MAX_PARALLEL_LANES'"
exit 1
fi
max_parallel_lanes="$MAX_PARALLEL_LANES"
if [ "$max_parallel_lanes" -gt 4 ]; then
max_parallel_lanes=4
fi
available_runner_count=$((max_parallel_lanes - active_runner_count))
if [ "$available_runner_count" -lt 0 ]; then
available_runner_count=0
fi
dispatch_enabled="$DISPATCH_REQUESTED"
dispatch_reason="Dispatching up to ${available_runner_count} uncovered sign-off lane(s)."
if [ "$dispatch_enabled" = "true" ] && [ "$available_runner_count" = "0" ]; then
dispatch_enabled="false"
dispatch_reason="Dispatch blocked because active Run Rancher Sign-Off Lane workflows reached max_parallel_lanes=${max_parallel_lanes}."
elif [ "$dispatch_enabled" != "true" ]; then
dispatch_reason="Dispatch was not requested for this event/input."
elif [ "$queue_count" = "0" ]; then
dispatch_reason="No pending sign-off lanes remain after removing active and already successful lanes."
fi
if [ "$dispatch_enabled" = "true" ]; then
jq --argjson limit "$available_runner_count" '.[0:$limit]' automation-output/signoff-dispatch-queue.json > automation-output/signoff-dispatch-selected.json
else
printf '[]\n' > automation-output/signoff-dispatch-selected.json
fi
printf '[]\n' > automation-output/signoff-dispatch-results.json
selected_count="$(jq 'length' automation-output/signoff-dispatch-selected.json)"
if [ "$dispatch_enabled" = "true" ] && [ "$selected_count" = "0" ] && [ "$queue_count" != "0" ]; then
dispatch_reason="Dispatch was enabled, but no lane was selected."
fi
jq -n \
--argjson dispatch_requested "$DISPATCH_REQUESTED" \
--argjson dispatch_enabled "$dispatch_enabled" \
--arg dispatch_reason "$dispatch_reason" \
--arg ignored_active_runner_id "$IGNORE_ACTIVE_RUNNER_ID" \
--argjson rerun_successful_lanes "$RERUN_SUCCESSFUL_LANES" \
--argjson max_parallel_lanes "$max_parallel_lanes" \
--argjson available_runner_count "$available_runner_count" \
--argjson active_runner_count "$active_runner_count" \
--argjson raw_queue_count "$raw_queue_count" \
--argjson active_blocked_count "$active_blocked_count" \
--argjson successful_skip_count "$successful_skip_count" \
--argjson queue_count "$queue_count" \
--argjson selected_count "$selected_count" \
--slurpfile active automation-output/signoff-active-runners.json \
--slurpfile successful automation-output/signoff-successful-runners.json \
--slurpfile raw_queue automation-output/signoff-dispatch-queue-raw.json \
--slurpfile queue automation-output/signoff-dispatch-queue.json \
--slurpfile selected automation-output/signoff-dispatch-selected.json \
'{
dispatch_requested: $dispatch_requested,
dispatch_enabled: $dispatch_enabled,
dispatch_reason: $dispatch_reason,
rerun_successful_lanes: $rerun_successful_lanes,
max_parallel_lanes: $max_parallel_lanes,
available_runner_count: $available_runner_count,
active_runner_count: $active_runner_count,
ignored_active_runner_id: $ignored_active_runner_id,
raw_queue_count: $raw_queue_count,
active_blocked_count: $active_blocked_count,
successful_skip_count: $successful_skip_count,
queue_count: $queue_count,
selected_count: $selected_count,
active_runners: $active[0],
successful_runners: $successful[0],
raw_lanes: $raw_queue[0],
queued_lanes: $queue[0],
selected_lanes: $selected[0]
}' > automation-output/signoff-dispatch-summary.json
echo "Dispatch decision: $dispatch_reason"
echo "Raw dispatch queue:"
jq . automation-output/signoff-dispatch-queue-raw.json
echo "Dispatch queue:"
jq . automation-output/signoff-dispatch-queue.json
echo "Selected lanes:"
jq . automation-output/signoff-dispatch-selected.json
echo "Active runners:"
jq . automation-output/signoff-active-runners.json
echo "Successful runners considered for skips:"
jq . automation-output/signoff-successful-runners.json
{
echo "requested=$DISPATCH_REQUESTED"
echo "enabled=$dispatch_enabled"
echo "reason=$dispatch_reason"
echo "rerun_successful_lanes=$RERUN_SUCCESSFUL_LANES"
echo "max_parallel_lanes=$max_parallel_lanes"
echo "available_runner_count=$available_runner_count"
echo "active_runner_count=$active_runner_count"
echo "ignored_active_runner_id=$IGNORE_ACTIVE_RUNNER_ID"
echo "raw_queue_count=$raw_queue_count"
echo "active_blocked_count=$active_blocked_count"
echo "successful_skip_count=$successful_skip_count"
echo "queue_count=$queue_count"
echo "selected_count=$selected_count"
} >> "$GITHUB_OUTPUT"
- name: Dispatch selected sign-off lanes
if: steps.dispatch-queue.outputs.enabled == 'true' && steps.dispatch-queue.outputs.selected_count != '0'
env:
GH_TOKEN: ${{ github.token }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
{
echo ""
echo "## Dispatched sign-off lane"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
jq -c '.[]' automation-output/signoff-dispatch-selected.json | while IFS= read -r item; do
rancher_version="$(jq -r '.rancher_version' <<<"$item")"
lane="$(jq -r '.lane' <<<"$item")"
previous_rancher_version="$(jq -r '.previous_rancher_version' <<<"$item")"
webhook_image="$(jq -r '.webhook_image' <<<"$item")"
signing_policy="$(jq -r '.signing_policy' <<<"$item")"
rke2_server_count="$(jq -r '.rke2_server_count' <<<"$item")"
args=(
workflow run run-rancher-signoff-lane.yml
--ref "$REF_NAME"
-f "rancher_version=$rancher_version"
-f "lane=$lane"
-f "signing_policy=$signing_policy"
-f "rke2_server_count=$rke2_server_count"
-f "keep_infra_on_failure=false"
-f "run_rancher_tests=true"
)
if [ -n "$previous_rancher_version" ]; then
args+=(-f "previous_rancher_version=$previous_rancher_version")
fi
if [ -n "$webhook_image" ]; then
args+=(-f "webhook_image=$webhook_image")
fi
gh "${args[@]}"
sleep 3
dispatched_run="$(TARGET_RUN_TITLE="Run $rancher_version / $lane" gh run list \
--workflow run-rancher-signoff-lane.yml \
--limit 20 \
--json databaseId,status,displayTitle,event,createdAt,url \
--jq '[.[] | select(.event == "workflow_dispatch") | select(.displayTitle == env.TARGET_RUN_TITLE)] | sort_by(.createdAt) | reverse | .[0] // {}')"
tmp="$(mktemp)"
jq \
--arg rancher_version "$rancher_version" \
--arg lane "$lane" \
--argjson dispatched_run "$dispatched_run" \
'. + [{
rancher_version: $rancher_version,
lane: $lane,
dispatch_command_completed: true,
observed_run: $dispatched_run
}]' automation-output/signoff-dispatch-results.json > "$tmp"
mv "$tmp" automation-output/signoff-dispatch-results.json
if [ "$(jq 'has("databaseId")' <<<"$dispatched_run")" = "true" ]; then
run_url="$(jq -r '.url' <<<"$dispatched_run")"
echo "- \`$rancher_version\` / \`$lane\`: $run_url" >> "$GITHUB_STEP_SUMMARY"
else
echo "- \`$rancher_version\` / \`$lane\`: dispatch command completed, but no matching run was observed yet." >> "$GITHUB_STEP_SUMMARY"
fi
done
- name: Write step summary
if: ${{ always() && hashFiles('signoff-plan.json') != '' && hashFiles('automation-output/signoff-dispatch-summary.json') != '' }}
run: |
{
echo "## Rancher sign-off plan"
echo ""
echo '```json'
cat signoff-plan.json
echo '```'
echo ""
echo "Dispatch requested: \`${{ steps.dispatch-queue.outputs.requested }}\`."
echo "Dispatch reason: \`${{ steps.dispatch-queue.outputs.reason }}\`."
echo "Rerun successful lanes: \`${{ steps.dispatch-queue.outputs.rerun_successful_lanes }}\`."
echo "Max parallel lanes: \`${{ steps.dispatch-queue.outputs.max_parallel_lanes }}\`."
echo "RKE2 server count for dispatched lanes: \`${{ github.event.inputs.rke2_server_count || '3' }}\`."
echo "Available runner slots: \`${{ steps.dispatch-queue.outputs.available_runner_count }}\`."
echo "Active lane runners: \`${{ steps.dispatch-queue.outputs.active_runner_count }}\`."
echo "Ignored active runner id: \`${{ steps.dispatch-queue.outputs.ignored_active_runner_id }}\`."
echo "Planned lanes: \`${{ steps.dispatch-queue.outputs.raw_queue_count }}\`; active skipped: \`${{ steps.dispatch-queue.outputs.active_blocked_count }}\`; successful skipped: \`${{ steps.dispatch-queue.outputs.successful_skip_count }}\`."
echo "Pending dispatch queue: \`${{ steps.dispatch-queue.outputs.queue_count }}\` lane(s); selected: \`${{ steps.dispatch-queue.outputs.selected_count }}\`."
echo ""
echo '```json'
cat automation-output/signoff-dispatch-summary.json
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Write plan receipt
id: public_artifacts
if: ${{ always() && hashFiles('signoff-plan.json') != '' }}
run: |
set -euo pipefail
mkdir -p public-plan-artifacts
dispatch_summary='{}'
if [ -s automation-output/signoff-dispatch-summary.json ]; then
dispatch_summary="$(jq -c . automation-output/signoff-dispatch-summary.json)"
fi
jq -n \
--slurpfile plan signoff-plan.json \
--argjson dispatch "$dispatch_summary" \
--arg completed_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{
completed_at: $completed_at,
plan: $plan[0],
dispatch: $dispatch
}' > public-plan-artifacts/signoff-plan-receipt.json
- name: Upload plan artifact
if: ${{ always() && steps.public_artifacts.outcome == 'success' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: signoff-plan
path: public-plan-artifacts/signoff-plan-receipt.json