Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .github/actions/cross-repo-ci-relay-callback/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Cross-Repo CI Relay Callback

description: >
Report the status of a downstream CI workflow back to the Cross-Repo CI
Relay server. The job must have `id-token: write` permission so that a
GitHub OIDC token can be minted and used to authenticate the callback.

This action is meant to run in a workflow triggered by a `repository_dispatch`
event from the relay. It reads the dispatch payload (`github.event.client_payload`)
and the ambient `github` context directly, so workflow authors only need to
supply the relay URL, the status/conclusion, and optional structured test
results.

inputs:
status:
description: >
Workflow status to report. Must be either "in_progress" or "completed".
required: true
conclusion:
description: >
Conclusion of the workflow run. Required (and must be "success" or
"failure") when status is "completed". Ignored when status is
"in_progress".
required: false
default: ''
test-results:
description: >
Optional JSON string with structured test results produced by the
downstream job. Forwarded verbatim to the relay under `test_results`.
required: false
default: ''
callback-url:
description: >
Base URL of the result callback server.
required: false
default: https://zciswjsrynb6ksccc2nb3mckpa0ldzou.lambda-url.us-east-1.on.aws/github/result

runs:
using: composite
steps:
- name: Mint OIDC token
id: oidc
uses: actions/github-script@v7
with:
script: |
const audience = `https://github.com/${context.repo.owner}/${context.repo.repo}`;
const token = await core.getIDToken(audience);
core.setSecret(token);
core.setOutput('token', token);

- name: Send callback to relay server
shell: bash
env:
STATUS: ${{ inputs.status }}
CONCLUSION: ${{ inputs.conclusion }}
WORKFLOW_NAME: ${{ github.workflow }}
WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
TEST_RESULTS: ${{ inputs.test-results }}
CLIENT_PAYLOAD: ${{ toJson(github.event.client_payload) }}
OIDC_TOKEN: ${{ steps.oidc.outputs.token }}
CALLBACK_URL: ${{ inputs.callback-url }}
run: |
set -euo pipefail

PAYLOAD=$(python3 - <<'PYEOF'
import json, os, sys

status = os.environ["STATUS"]
if status not in ("in_progress", "completed"):
sys.exit(f"::error::status must be 'in_progress' or 'completed', got {status!r}")

conclusion = os.environ.get("CONCLUSION", "").strip() or None
if status == "completed" and conclusion not in ("success", "failure"):
sys.exit("::error::conclusion must be 'success' or 'failure' when status is 'completed'")
if status == "in_progress":
conclusion = None

try:
client_payload = json.loads(os.environ["CLIENT_PAYLOAD"])
except json.JSONDecodeError as exc:
sys.exit(f"::error::github.event.client_payload is not valid JSON: {exc}")

# Relay's original dispatch payload (event_type, delivery_id, payload) is
# forwarded verbatim. Downstream-reported fields live in a sibling
# `workflow` dict so the two sources stay clearly separated on the wire.
workflow: dict = {
"status": status,
"conclusion": conclusion,
"name": os.environ["WORKFLOW_NAME"],
"url": os.environ["WORKFLOW_URL"],
}

test_results = os.environ.get("TEST_RESULTS", "").strip()
if test_results:
try:
workflow["test_results"] = json.loads(test_results)
except json.JSONDecodeError as exc:
sys.exit(f"::error::test-results input is not valid JSON: {exc}")

client_payload["workflow"] = workflow
print(json.dumps(client_payload))
PYEOF
)

HTTP_CODE=$(
curl --silent --show-error --output /tmp/relay_response.json \
--write-out "%{http_code}" \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${OIDC_TOKEN}" \
--data "${PAYLOAD}" \
"${CALLBACK_URL%/}"
)

if [[ "${HTTP_CODE}" -lt 200 || "${HTTP_CODE}" -ge 300 ]]; then
echo "::error::Callback server returned HTTP ${HTTP_CODE}."
exit 1
fi

echo "Relay server response HTTP: ${HTTP_CODE}"
3 changes: 2 additions & 1 deletion .github/workflows/_lambda-do-release-runners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ jobs:
{ dir-name: 'keep-going-call-log-classifier', zip-name: 'keep-going-call-log-classifier' },
{ dir-name: 'buildkite-webhook-handler', zip-name: 'buildkite-webhook-handler' },
{ dir-name: 'benchmark_regression_summary_report', zip-name: 'benchmark-regression-summary-report' },
{ dir-name: 'cross_repo_ci_relay', zip-name: 'cross-repo-ci-webhook' },
{ dir-name: 'cross_repo_ci_relay/result', zip-name: 'cross-repo-ci-result' },
{ dir-name: 'cross_repo_ci_relay/webhook', zip-name: 'cross-repo-ci-webhook' },
]
name: Upload Release for ${{ matrix.dir-name }} lambda
runs-on: ubuntu-latest
Expand Down
33 changes: 18 additions & 15 deletions aws/lambda/cross_repo_ci_relay/Makefile
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
SHARED := config.py utils.py redis_helper.py allowlist.py gh_helper.py event_handler.py
PIP_FLAGS := --platform manylinux2014_x86_64 --only-binary=:all: --implementation cp --python-version 3.13
AWS_REGION := us-east-1
FUNCTION_NAME := cross_repo_ci_webhook
AWS_REGION ?= us-east-1
CALLBACK_FUNCTION_NAME ?= cross_repo_ci_callback
WEBHOOK_FUNCTION_NAME ?= cross_repo_ci_webhook

deployment.zip: clean
mkdir -p ./deployment
cp $(SHARED) lambda_function.py ./deployment/
pip3 install --target ./deployment -r requirements.txt $(PIP_FLAGS)
cd deployment && zip -r ../deployment.zip .

deploy: deployment.zip
aws lambda update-function-code --region $(AWS_REGION) --function-name $(FUNCTION_NAME) --zip-file fileb://deployment.zip
.PHONY: test deploy deploy-callback deploy-webhook clean

test:
python3 -m pytest tests -v

clean:
rm -rf deployment deployment.zip
# Deploy both; keep going on failure so a broken half doesn't block the other.
deploy:
@rc=0; \
for t in deploy-callback deploy-webhook; do $(MAKE) $$t || rc=$$?; done; \
exit $$rc

deploy-callback:
$(MAKE) -C callback deploy AWS_REGION=$(AWS_REGION) FUNCTION_NAME=$(CALLBACK_FUNCTION_NAME)

.PHONY: prepare deploy test clean
deploy-webhook:
$(MAKE) -C webhook deploy AWS_REGION=$(AWS_REGION) FUNCTION_NAME=$(WEBHOOK_FUNCTION_NAME)

clean:
$(MAKE) -C callback clean
$(MAKE) -C webhook clean
Loading