Skip to content

dependencies: add shared lemonade-ready readiness check #41

dependencies: add shared lemonade-ready readiness check

dependencies: add shared lemonade-ready readiness check #41

# Copyright Advanced Micro Devices, Inc.
#
# SPDX-License-Identifier: MIT
#
# ChatOps: comment `!orc` on a PR to run that PR's playbooks via OrchestrAI and
# get a sticky pass/fail comment back.
#
# !orc -> the playbooks changed in the PR
# !orc all -> every playbook
# !orc a,b,c -> the named playbooks
#
# Only users with write access (OWNER/MEMBER/COLLABORATOR) can trigger it, since
# it runs internal hardware with the OrchestrAI pipeline secrets. issue_comment runs from the
# default branch, so this file must be on the default branch to take effect.
name: OrchestrAI PR command
on:
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
issues: write
concurrency:
# Only real `!orc` runs share the per-PR group so a newer `!orc` supersedes an
# older one. Every OTHER issue_comment on the PR (a human reply, the bot's own
# status comment, an unauthorized commenter) gets its own throwaway group and
# cannot cancel an in-flight `!orc` — previously they all shared this group with
# cancel-in-progress:true, so any stray comment killed a running `!orc`.
group: ${{ startsWith(github.event.comment.body, '!orc') && format('orchestrai-orc-{0}', github.event.issue.number) || format('orchestrai-orc-other-{0}', github.event.comment.id) }}
cancel-in-progress: ${{ startsWith(github.event.comment.body, '!orc') }}
jobs:
command:
name: "!orc"
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '!orc') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
runs-on: ${{ vars.ORCHESTRAI_CONTROL_RUNNER || 'ubuntu-latest' }}
outputs:
matrix: ${{ steps.build-matrix.outputs.matrix }}
has_entries: ${{ steps.build-matrix.outputs.has_entries }}
build_urls: ${{ steps.trigger.outputs.build_urls }}
head_sha: ${{ steps.pr.outputs.head_sha }}
head_ref: ${{ steps.pr.outputs.head_ref }}
steps:
- name: Resolve PR + acknowledge
id: pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
API: ${{ github.api_url }}
REPO: ${{ github.repository }}
PR: ${{ github.event.issue.number }}
COMMENT_ID: ${{ github.event.comment.id }}
run: |
curl -s -X POST -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
"$API/repos/$REPO/issues/comments/$COMMENT_ID/reactions" -d '{"content":"eyes"}' >/dev/null || true
data=$(curl -s -H "Authorization: Bearer $GH_TOKEN" "$API/repos/$REPO/pulls/$PR")
py() { printf '%s' "$data" | python3 -c "import sys,json;print(json.load(sys.stdin)$1)"; }
# SECURITY: only same-repo PRs. issue_comment carries the repo secrets
# even for fork PRs, so allowing forks would let untrusted PR code run
# on internal hardware with the OrchestrAI pipeline token. Refuse forks outright.
HEAD_REPO_FULL=$(py "['head']['repo']['full_name']")
if [ "$HEAD_REPO_FULL" != "$REPO" ]; then
echo "fork=true" >> "$GITHUB_OUTPUT"
echo "::warning::!orc refused: PR head '$HEAD_REPO_FULL' is a fork of '$REPO'"
curl -s -X POST -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
"$API/repos/$REPO/issues/$PR/comments" \
-d '{"body":"🔒 `!orc` is only available for pull requests from branches in this repository, not forks."}' >/dev/null || true
exit 0
fi
{
echo "fork=false"
echo "head_sha=$(py "['head']['sha']")"
echo "base_sha=$(py "['base']['sha']")"
echo "head_ref=$(py "['head']['ref']")"
echo "head_repo=$(py "['head']['repo']['clone_url']")"
} >> "$GITHUB_OUTPUT"
- if: steps.pr.outputs.fork == 'false'
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ steps.pr.outputs.head_sha }}
fetch-depth: 0
- if: steps.pr.outputs.fork == 'false'
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.12'
- if: steps.pr.outputs.fork == 'false'
run: pip install --quiet pyyaml
- name: Require OrchestrAI config
if: steps.pr.outputs.fork == 'false'
env:
JU: ${{ secrets.ORCHESTRAI_PIPELINE_USER }}
JT: ${{ secrets.ORCHESTRAI_PIPELINE_TOKEN }}
run: |
if [ -z "$JU" ] || [ -z "$JT" ]; then
echo "::error::OrchestrAI secrets (ORCHESTRAI_PIPELINE_USER/ORCHESTRAI_PIPELINE_TOKEN) are not set"; exit 1
fi
- name: Parse command into playbook list
id: detect
if: steps.pr.outputs.fork == 'false'
env:
BODY: ${{ github.event.comment.body }}
BASE_SHA: ${{ steps.pr.outputs.base_sha }}
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
run: |
arg=$(printf '%s' "$BODY" | head -n1 | sed -E 's/^!orc[[:space:]]*//')
if [ "$arg" = "all" ]; then
PB=$(find playbooks/core playbooks/supplemental -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sed 's|playbooks/[^/]*/||' | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))')
elif [ -n "$arg" ]; then
PB=$(printf '%s' "$arg" | tr ', ' '\n\n' | sed 's/^ *//;s/ *$//' | jq -R -s -c 'split("\n") | map(select(length > 0))')
else
if ! CHANGED=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'playbooks/core/*' 'playbooks/supplemental/*'); then
echo "::error::git diff failed (base $BASE_SHA not in history?)"; exit 1
fi
PB=$(echo "$CHANGED" | grep -E '^playbooks/(core|supplemental)/[^/]+/' | sed 's|playbooks/[^/]*/\([^/]*\)/.*|\1|' | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))')
fi
echo "playbooks=${PB:-[]}" >> "$GITHUB_OUTPUT"
- name: Build matrix and batches
id: build-matrix
if: steps.pr.outputs.fork == 'false'
env:
PLAYBOOKS: ${{ steps.detect.outputs.playbooks }}
ORCHESTRAI_DEVICE_TAGS: ${{ vars.ORCHESTRAI_DEVICE_TAGS }}
run: python3 .github/scripts/orchestrai_matrix.py
- name: Trigger OrchestrAI pipeline builds (PR code)
id: trigger
if: steps.pr.outputs.fork == 'false' && steps.build-matrix.outputs.has_entries == 'true'
env:
BATCHES_JSON: ${{ steps.build-matrix.outputs.batches }}
GIT_REF: ${{ steps.pr.outputs.head_ref }}
ORCHESTRAI_PIPELINE_USER: ${{ secrets.ORCHESTRAI_PIPELINE_USER }}
ORCHESTRAI_PIPELINE_TOKEN: ${{ secrets.ORCHESTRAI_PIPELINE_TOKEN }}
ORCHESTRAI_PIPELINE_URL: ${{ vars.ORCHESTRAI_PIPELINE_URL }}
ORCHESTRAI_PIPELINE_JOB: ${{ vars.ORCHESTRAI_PIPELINE_JOB }}
ORCHESTRAI_THEROCK_URL: ${{ vars.ORCHESTRAI_THEROCK_URL }}
ORCHESTRAI_ROCM_MULTI_ARCH_INDEX_URL: ${{ vars.ORCHESTRAI_ROCM_MULTI_ARCH_INDEX_URL }}
ORCHESTRAI_LINUX_DRIVER_SOURCE: ${{ vars.ORCHESTRAI_LINUX_DRIVER_SOURCE }}
ORCHESTRAI_LINUX_DRIVER_SOURCES_JSON: ${{ vars.ORCHESTRAI_LINUX_DRIVER_SOURCES_JSON }}
ORCHESTRAI_RYZENAI_NPU_XRT_URL: ${{ secrets.ORCHESTRAI_RYZENAI_NPU_XRT_URL }}
ORCHESTRAI_WINDOWS_DRIVER_SOURCE: ${{ secrets.ORCHESTRAI_WINDOWS_DRIVER_SOURCE }}
# Test the PR's own code, not the configured repo@main.
ORCHESTRAI_PLAYBOOK_REPO: ${{ steps.pr.outputs.head_repo }}
ORCHESTRAI_PLAYBOOK_SHA: ${{ steps.pr.outputs.head_sha }}
run: python3 .github/scripts/orchestrai_trigger.py
test-playbooks:
needs: command
if: needs.command.outputs.has_entries == 'true'
runs-on: ${{ vars.ORCHESTRAI_WAIT_RUNNER || 'ubuntu-latest' }}
continue-on-error: ${{ !matrix.required }}
strategy:
fail-fast: false
max-parallel: 16
matrix:
include: ${{ fromJson(needs.command.outputs.matrix) }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ needs.command.outputs.head_sha }}
- name: Wait for OrchestrAI pipeline build
id: wait
env:
BUILD_URLS_JSON: ${{ needs.command.outputs.build_urls }}
BATCH_ID: ${{ matrix.batch_id }}
ORCHESTRAI_PIPELINE_USER: ${{ secrets.ORCHESTRAI_PIPELINE_USER }}
ORCHESTRAI_PIPELINE_TOKEN: ${{ secrets.ORCHESTRAI_PIPELINE_TOKEN }}
run: |
BUILD_URL=$(python3 -c "import json,os;print(json.loads(os.environ.get('BUILD_URLS_JSON','{}') or '{}').get(os.environ['BATCH_ID'],''))")
if [ -z "$BUILD_URL" ]; then
echo "::error::No OrchestrAI pipeline build URL for batch ${BATCH_ID}"; exit 1
fi
echo "build_url=${BUILD_URL}" >> "$GITHUB_OUTPUT"
echo "Waiting for OrchestrAI pipeline build ..."
# SECURITY: pass credentials via a 0600 netrc file, never as `curl -u
# user:token` — argv is world-readable (/proc/<pid>/cmdline, ps) for the
# life of each curl, which would leak the pipeline token to any other
# process on a shared/persistent self-hosted runner. mktemp creates the
# file 0600; printf is a shell builtin, so the token never hits argv.
NETRC="$(mktemp)"
trap 'rm -f "$NETRC"' EXIT
NETRC_HOST=$(printf '%s' "$BUILD_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://##; s#[:/].*$##')
printf 'machine %s login %s password %s\n' \
"$NETRC_HOST" "$ORCHESTRAI_PIPELINE_USER" "$ORCHESTRAI_PIPELINE_TOKEN" > "$NETRC"
MAX=1080; FAIL_MAX=20; i=0; fails=0
while [ "$i" -lt "$MAX" ]; do
if data=$(curl -sf --connect-timeout 10 --max-time 30 --netrc-file "$NETRC" "${BUILD_URL}api/json?tree=result,building" 2>/dev/null); then
fails=0
building=$(echo "$data" | python3 -c "import sys,json;print(json.load(sys.stdin).get('building',True))" 2>/dev/null || echo "True")
result=$(echo "$data" | python3 -c "import sys,json;print(json.load(sys.stdin).get('result') or '')" 2>/dev/null || echo "")
if [ "$building" = "False" ] && [ -n "$result" ]; then
echo "Batch build finished: $result"; break
fi
else
fails=$((fails + 1))
if [ "$fails" -ge "$FAIL_MAX" ]; then
echo "::error::OrchestrAI pipeline unreachable"; exit 1
fi
fi
i=$((i + 1)); sleep 15
done
if [ "$i" -ge "$MAX" ]; then echo "::error::Timed out waiting for OrchestrAI pipeline build"; exit 1; fi
- name: Resolve per-playbook verdict
if: always()
env:
BUILD_URL: ${{ steps.wait.outputs.build_url }}
PLAYBOOK_ID: ${{ matrix.playbook }}
PLATFORM: ${{ matrix.platform }}
ORCHESTRAI_PIPELINE_USER: ${{ secrets.ORCHESTRAI_PIPELINE_USER }}
ORCHESTRAI_PIPELINE_TOKEN: ${{ secrets.ORCHESTRAI_PIPELINE_TOKEN }}
run: python3 .github/scripts/orchestrai_verdict.py
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-results-${{ matrix.playbook }}-${{ matrix.platform }}-${{ matrix.arch }}
path: test-results/
if-no-files-found: ignore
report:
needs: [command, test-playbooks]
if: always() && needs.command.result == 'success' && needs.command.outputs.head_sha != ''
runs-on: ${{ vars.ORCHESTRAI_CONTROL_RUNNER || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ needs.command.outputs.head_sha }}
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.12'
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
path: artifacts
continue-on-error: true
- name: Build + upsert sticky comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
API: ${{ github.api_url }}
REPO: ${{ github.repository }}
PR: ${{ github.event.issue.number }}
HEAD_SHA: ${{ needs.command.outputs.head_sha }}
HEAD_REF: ${{ needs.command.outputs.head_ref }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
python3 .github/scripts/orchestrai_report.py \
--artifacts artifacts --run-url "$RUN_URL" --sha "$HEAD_SHA" --ref "$HEAD_REF" > /tmp/body.md
python3 -c "import json;print(json.dumps({'body':open('/tmp/body.md').read()}))" > /tmp/payload.json
MARKER='<!-- orchestrai-orc-report -->'
existing=$(curl -s -H "Authorization: Bearer $GH_TOKEN" "$API/repos/$REPO/issues/$PR/comments?per_page=100" \
| python3 -c "import sys,json;c=[x for x in json.load(sys.stdin) if str(x.get('body','')).startswith('$MARKER')];print(c[0]['id'] if c else '')")
if [ -n "$existing" ]; then
curl -s -X PATCH -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
"$API/repos/$REPO/issues/comments/$existing" -d @/tmp/payload.json >/dev/null
else
curl -s -X POST -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
"$API/repos/$REPO/issues/$PR/comments" -d @/tmp/payload.json >/dev/null
fi
echo "Posted results to PR #$PR"