Skip to content
Merged
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
30 changes: 28 additions & 2 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -686,23 +686,49 @@ jobs:

- name: "[Report] Job summary"
if: ${{ always() }}
env:
GH_TOKEN: ${{ github.token }}
run: |
mkdir -p report
if [ ! -f out/readback-first.md ]; then
FAILED_STEP=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/attempts/$GITHUB_RUN_ATTEMPT/jobs" \
--jq ".jobs[] | select(.runner_name == \"$RUNNER_NAME\") | .steps[] | select(.conclusion == \"failure\") | .name" | head -1) || true
fi
{
echo "### $RUNTIME $ARCH \`$NAME\`"
echo
[ -z "${SOURCE_ID:-}" ] || echo "- source $SOURCE_ID, ${PACKAGE_VERSIONS:-}"
[ ! -f invocations.jsonl ] || jq -rs 'to_entries[] | "- invocation \(.key + 1) at \(.value.invoked_at / 1000 | strftime("%H:%M:%S")), took \((.value.returned_at - .value.invoked_at) / 1000) s\(if .value.init_ms then ", a cold start with \(.value.init_ms) ms of init" else "" end)"' invocations.jsonl
echo
for readback in out/readback-first.md out/readback-final.md; do [ ! -f "$readback" ] || { cat "$readback"; echo; }; done
[ -f out/readback-first.md ] || echo "No read-back, the job failed before it."
} >> "$GITHUB_STEP_SUMMARY"
[ -f out/readback-first.md ] || echo "❌ Stopped${FAILED_STEP:+ at **$FAILED_STEP**}, no read-back to show."
} > report/summary.md
cat report/summary.md >> "$GITHUB_STEP_SUMMARY"
# The Linear issue gets the cleanup only when it left something behind.
grep -q '❌' out/cleanup.md 2>/dev/null && { echo; cat out/cleanup.md; } >> report/summary.md
[ -s out/rows.json ] && [ "$(jq length out/rows.json)" -gt 0 ] && { echo; echo '<details><summary>stored rows</summary>'; echo; echo '```json'; cat out/rows.json; echo '```'; echo '</details>'; } >> "$GITHUB_STEP_SUMMARY"
[ -s out/function.log ] && { echo; echo "<details><summary>/aws/lambda/$NAME</summary>"; echo; echo '```'; cat out/function.log; echo '```'; echo '</details>'; } >> "$GITHUB_STEP_SUMMARY"
[ -f out/cleanup.md ] && { echo; cat out/cleanup.md; } >> "$GITHUB_STEP_SUMMARY"
true

- name: "[Report] Hand the summary to the Linear issue"
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: e2e-summary-${{ matrix.runtime }} ${{ matrix.arch }}
path: report/summary.md
retention-days: 7
overwrite: true

notify-linear:
if: ${{ failure() && (github.event_name == 'schedule' || (github.event_name == 'push' && github.ref == 'refs/heads/main')) }}
needs: [docs, layer, lambda]
permissions:
contents: read
actions: read
checks: read
uses: ./.github/workflows/notify_linear_failure.yml
with:
test_name: AWS Lambda extension
job_summaries: true
secrets: inherit
67 changes: 52 additions & 15 deletions .github/workflows/notify_linear_failure.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,71 @@
name: "Notify Linear on failure"

# A caller that sets neither input gets the plain issue: "E2E failure: <workflow name>" listing the failed jobs and steps.
on:
workflow_call:
inputs:
test_name:
description: "Names the test in the issue title instead of the workflow name"
type: string
default: ""
job_summaries:
description: >-
Adds each failed job's errors and summary to the issue. A job hands its summary over as an artifact named
"e2e-summary-<job name>" holding summary.md.
type: boolean
default: false

jobs:
create-linear-issue:
runs-on: ubuntu-latest
# A caller must grant checks: read as well (the errors come from the annotations), or this job cannot start.
permissions:
contents: read
actions: read
checks: read
steps:
- name: "Gather failure context"
id: ctx
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_ID: ${{ github.event.workflow_run.id || github.run_id }}
RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt || github.run_attempt }}
JOB_SUMMARIES: ${{ inputs.job_summaries }}
run: |
FAILED=$(gh api \
JOBS=$(gh api \
"repos/${{ github.repository }}/actions/runs/$RUN_ID/attempts/$RUN_ATTEMPT/jobs" \
--paginate \
--jq '[.jobs[]
--jq '.jobs[]
| select(.conclusion == "failure")
| "- **\(.name)** — failed at _\([.steps[] | select(.conclusion == "failure") | .name] | join(", "))_"]
| join("\n")')

| {id, name, steps: ([.steps[] | select(.conclusion == "failure") | .name] | join(", "))}
| @json')

FAILED=""
while read -r job; do
[ -n "$job" ] || continue
NAME=$(jq -r .name <<<"$job")
STEPS=$(jq -r .steps <<<"$job")
if [ "$JOB_SUMMARIES" != true ]; then
FAILED="$FAILED- **$NAME** — failed at _${STEPS}_"$'\n'
continue
fi
# The job's ::error:: lines, without the runner's closing "Process completed with exit code 1."
ERRORS=$(gh api "repos/${{ github.repository }}/check-runs/$(jq -r .id <<<"$job")/annotations" \
--jq '.[] | select(.annotation_level == "failure" and (.message | startswith("Process completed with exit code") | not)) | "- \(.message | gsub("\n"; " "))"') || ERRORS=""
FAILED="$FAILED### $NAME"$'\n\n'"Failed at **${STEPS}**"$'\n\n'"${ERRORS:+$ERRORS$'\n\n'}"
# A job that failed before writing its summary has no artifact and gets the errors alone.
SUMMARY_DIR="summaries/$(jq -r .id <<<"$job")"
gh run download "$RUN_ID" -R "${{ github.repository }}" -n "e2e-summary-$NAME" -D "$SUMMARY_DIR" >/dev/null 2>&1 || true
[ ! -f "$SUMMARY_DIR/summary.md" ] || FAILED="$FAILED$(cat "$SUMMARY_DIR/summary.md")"$'\n\n'
done <<<"$JOBS"

while [[ "$FAILED" == *$'\n' ]]; do FAILED="${FAILED%$'\n'}"; done
if [ -z "$FAILED" ]; then
FAILED="_(no failed jobs reported by the API yet)_"
elif [ "${#FAILED}" -gt 20000 ]; then
FAILED="${FAILED:0:20000}"$'\n\n'"_(cut at 20,000 characters, the rest is in the run)_"
fi

{
echo "failed_jobs<<CTXEOF"
echo "$FAILED"
Expand All @@ -41,7 +78,7 @@ jobs:
LINEAR_STATUS_PLAN_ID: ${{ secrets.LINEAR_STATUS_PLAN_ID }}
LINEAR_PROJECT_INGESTION_FOR_EVERYTHING_ID: ${{ secrets.LINEAR_PROJECT_INGESTION_FOR_EVERYTHING_ID }}
LINEAR_ASSIGNEE_PETR_ID: ${{ secrets.LINEAR_ASSIGNEE_PETR_ID }}
WORKFLOW: ${{ github.event.workflow_run.name || github.workflow }}
WORKFLOW: ${{ inputs.test_name || github.event.workflow_run.name || github.workflow }}
REF_NAME: ${{ github.event.workflow_run.head_branch || github.ref_name }}
RUN_URL: ${{ github.event.workflow_run.html_url || format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }}
RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt || github.run_attempt }}
Expand All @@ -51,32 +88,32 @@ jobs:
FAILED_JOBS: ${{ steps.ctx.outputs.failed_jobs }}
run: |
SHORT_SHA="${SHA:0:7}"

# head_commit is only populated on push events; skip the line otherwise
COMMIT_LINE="**Commit:** \`$SHORT_SHA\`"
if [ -n "$COMMIT_MESSAGE" ]; then
COMMIT_SUBJECT="$(printf '%s' "$COMMIT_MESSAGE" | head -n1)"
COMMIT_LINE="**Commit:** \`$SHORT_SHA\` — $COMMIT_SUBJECT"
fi

ATTEMPT_NOTE=""
if [ "$RUN_ATTEMPT" != "1" ]; then
ATTEMPT_NOTE=" (attempt #$RUN_ATTEMPT)"
fi

TITLE="E2E failure: $WORKFLOW"
DESCRIPTION=$(cat <<EOF
**$WORKFLOW** failed on \`$REF_NAME\` via \`$EVENT_NAME\`$ATTEMPT_NOTE

**Failing jobs:**
$FAILED_JOBS

$COMMIT_LINE

[View failing run]($RUN_URL)
EOF
)

PAYLOAD=$(jq -n \
--arg title "$TITLE" \
--arg description "$DESCRIPTION" \
Expand All @@ -98,7 +135,7 @@ jobs:
}
}
}')

curl -sSf -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H 'Content-Type: application/json' \
Expand Down
Loading