mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 16:09:05 +00:00
284 lines
15 KiB
YAML
284 lines
15 KiB
YAML
name: Playwright E2E Crosscheck
|
|
|
|
# Pre-merge GUARD (runs in openobserve) that stops an OSS PR from silently breaking the
|
|
# enterprise-only e2e specs. web/tests live in OSS; the ENT e2e *triggers* live in o2-enterprise,
|
|
# so today an OSS frontend/test change only surfaces as an ENT breakage in some later ENT PR.
|
|
#
|
|
# This job IS the required status check. Make `Playwright E2E Crosscheck` a required check on main in OSS
|
|
# branch protection to actually block merges (one-time admin toggle).
|
|
#
|
|
# Behaviour (A+A+C design):
|
|
# - PR does NOT touch web/** or tests/ui-testing/** → pass instantly (never blocks).
|
|
# - Touches them + `skip-ent-e2e` label → pass (explicit, auditable opt-out).
|
|
# - Touches them (the e2e label gates the whole job, like every paid workflow):
|
|
# · open sister ENT PR (same-named branch) → DEFER to that PR's checks (link it, don't build here)
|
|
# · sister ENT branch only (no PR) → build the enterprise binary against that branch
|
|
# · PR base is a release line (not main) with a same-named ENT branch → build against THAT branch
|
|
# · no ENT counterpart → build against ENT main
|
|
# then POLL and mirror pass/fail.
|
|
#
|
|
# A single STICKY comment per PR (found by a hidden marker) reports running → passed / failed, lists the
|
|
# failing specs (or an enterprise build failure), and links the sister ENT PR/branch + the ENT run.
|
|
#
|
|
# Pure CI — no AI/LLM. Deterministic dispatch + poll of o2-enterprise/oss-pr-ent-gate.yml.
|
|
#
|
|
# Secrets/vars:
|
|
# secrets.ORG_ADMIN_TOKEN — PAT: o2-enterprise Actions R/W (dispatch+poll) + openobserve PRs R/W (comment as human).
|
|
# vars.ENT_GATE_REF — o2-enterprise ref that carries oss-pr-ent-gate.yml (default 'main';
|
|
# only override to test the engine itself from a branch).
|
|
# vars.ENT_E2E_GATE_DISABLED — 'true' kill switch: gate passes without dispatching.
|
|
|
|
on:
|
|
pull_request:
|
|
# Fires once per PR when "Merge when ready" is clicked, then for pushes/labels while auto-merge stays on.
|
|
types: [auto_merge_enabled, synchronize, labeled, unlabeled, ready_for_review, converted_to_draft]
|
|
# A required check must also report on merge-queue commits; the gate already passed on the PR head.
|
|
merge_group:
|
|
|
|
# PR-scoped: a new push/label supersedes the previous gate attempt for the same PR.
|
|
concurrency:
|
|
group: playwright-e2e-crosscheck-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
|
|
jobs:
|
|
playwright-e2e-crosscheck:
|
|
# A pre-merge gate: runs once auto-merge is enabled on a labelled PR, not on every push.
|
|
if: ${{ github.event_name == 'merge_group' || ((contains(github.event.pull_request.labels.*.name, 'ready-for-ci') && github.event.pull_request.draft == false) && github.event.pull_request.auto_merge != null) }}
|
|
name: Playwright E2E Crosscheck
|
|
runs-on: ubuntu-latest
|
|
# ~5 min to find the ENT run + up to ~75 min to poll. Generous on purpose: normal is ~20 min,
|
|
# but a cold Rust cache can push the ENT build+run toward its own timeouts — don't false-fail
|
|
# the gate ("timed out") while ENT is still legitimately running.
|
|
timeout-minutes: 90
|
|
steps:
|
|
- name: Triage — relevant paths? opted out?
|
|
id: triage
|
|
env:
|
|
GH_TOKEN: ${{ github.token }} # OSS is same-repo/public → default token can read PR files
|
|
PR: ${{ github.event.pull_request.number }}
|
|
HAS_SKIP: ${{ contains(github.event.pull_request.labels.*.name, 'skip-ent-e2e') }}
|
|
DISABLED: ${{ vars.ENT_E2E_GATE_DISABLED }}
|
|
IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "${{ github.event_name }}" = "merge_group" ]; then
|
|
echo "::notice::merge queue commit — gate already passed on the PR head."
|
|
echo "mode=pass" >> "$GITHUB_OUTPUT"; exit 0
|
|
fi
|
|
if [ "$DISABLED" = "true" ]; then
|
|
echo "::notice::ENT_E2E_GATE_DISABLED=true — passing without dispatch."
|
|
echo "mode=pass" >> "$GITHUB_OUTPUT"; exit 0
|
|
fi
|
|
# Fork PRs: head branch isn't in openobserve/openobserve and the PAT isn't available to
|
|
# forked-PR runs, so the ENT engine can't check it out. Pass with a notice; a maintainer
|
|
# re-runs from an internal branch if enterprise validation is needed.
|
|
if [ "$IS_FORK" = "true" ]; then
|
|
echo "::notice::Fork PR — Playwright E2E Crosscheck can't run (no PAT / branch not in main repo). Passing."
|
|
echo "mode=pass" >> "$GITHUB_OUTPUT"; exit 0
|
|
fi
|
|
|
|
# Relevant = any changed file under web/ or tests/ui-testing/
|
|
FILES=$(gh api "repos/${{ github.repository }}/pulls/$PR/files" --paginate -q '.[].filename')
|
|
if printf '%s\n' "$FILES" | grep -qE '^(web/|tests/ui-testing/)'; then
|
|
RELEVANT=true
|
|
else
|
|
RELEVANT=false
|
|
fi
|
|
echo "::notice::Relevant (web/** or tests/ui-testing/**) changes: $RELEVANT"
|
|
|
|
if [ "$RELEVANT" != "true" ]; then
|
|
echo "mode=pass" >> "$GITHUB_OUTPUT"
|
|
elif [ "$HAS_SKIP" = "true" ]; then
|
|
echo "::warning::PR carries 'skip-ent-e2e' — enterprise gate explicitly opted out (auditable)."
|
|
echo "mode=skip" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "mode=run" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Pass — no enterprise-relevant changes (or opted out / disabled)
|
|
if: steps.triage.outputs.mode == 'pass' || steps.triage.outputs.mode == 'skip'
|
|
run: echo "✅ Playwright E2E Crosscheck not required for this PR."
|
|
|
|
- name: Run — dispatch ENT engine, poll, mirror result
|
|
if: steps.triage.outputs.mode == 'run'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.ORG_ADMIN_TOKEN }} # PAT: dispatch+poll o2-enterprise, comment as human
|
|
PR: ${{ github.event.pull_request.number }}
|
|
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
|
ENT_REF: ${{ vars.ENT_GATE_REF || 'main' }}
|
|
run: |
|
|
set -euo pipefail
|
|
REPO="openobserve/o2-enterprise"; WF="oss-pr-ent-gate.yml"
|
|
MARKER="<!-- playwright-e2e-crosscheck -->"
|
|
|
|
# Upsert ONE sticky gate comment per PR (located by the hidden marker) so status updates in
|
|
# place — pass AND fail — instead of spamming a fresh comment each run.
|
|
sticky() {
|
|
local body="$1" cid
|
|
cid=$(gh api "repos/openobserve/openobserve/issues/$PR/comments" --paginate \
|
|
-q "[.[] | select(.body|contains(\"$MARKER\")) | .id][0]" 2>/dev/null || echo "")
|
|
if [ -n "$cid" ] && [ "$cid" != "null" ]; then
|
|
gh api -X PATCH "repos/openobserve/openobserve/issues/comments/$cid" -f body="$body" >/dev/null 2>&1 || true
|
|
else
|
|
gh api -X POST "repos/openobserve/openobserve/issues/$PR/comments" -f body="$body" >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
# Sister ENT resolution. An OPEN same-named ENT PR is the live counterpart — let ITS checks
|
|
# own enterprise validation; we DEFER (don't build here), just link. Only a branch (no PR) →
|
|
# build against that branch. Neither → the PR's base-line ENT branch if it exists, else main.
|
|
SISTER_PR=$(gh pr list --repo "$REPO" --head "$HEAD_REF" --state open --json number -q '.[0].number' 2>/dev/null || echo "")
|
|
if [ -n "$SISTER_PR" ] && [ "$SISTER_PR" != "null" ]; then
|
|
sticky "### ⏭️ Playwright E2E Crosscheck — handled by the sister ENT PR
|
|
|
|
$MARKER
|
|
This OSS PR has a matching enterprise PR on branch \`$HEAD_REF\`: **openobserve/o2-enterprise#$SISTER_PR**. The enterprise-only e2e specs run there, so this gate defers to that PR's checks and does not build here."
|
|
echo "::notice::Sister ENT PR #$SISTER_PR is open — deferring, not dispatching."
|
|
exit 0
|
|
fi
|
|
|
|
# No sister branch/PR → build against the PR's base line, not blindly main. A PR onto a
|
|
# release branch (e.g. branch-v1.0.0) must build against the ENT branch of the same name;
|
|
# ENT main would fail to compile the release-line OSS code. Fall back to main only when
|
|
# the base is main or has no ENT counterpart.
|
|
if gh api "repos/$REPO/branches/$HEAD_REF" >/dev/null 2>&1; then
|
|
ENT_CODE_REF="$HEAD_REF"
|
|
SISTER_NOTE="Built against the matching ENT branch \`$HEAD_REF\` (no ENT PR yet)."
|
|
echo "::notice::ENT branch '$HEAD_REF' exists (no PR) — building against it."
|
|
elif [ "$BASE_REF" != "main" ] && [ "$BASE_REF" != "master" ] && gh api "repos/$REPO/branches/$BASE_REF" >/dev/null 2>&1; then
|
|
ENT_CODE_REF="$BASE_REF"
|
|
SISTER_NOTE="No same-named ENT branch — built against the PR's base line \`$BASE_REF\` on ENT."
|
|
echo "::notice::No ENT branch '$HEAD_REF'; PR targets '$BASE_REF' which exists on ENT — building against it."
|
|
else
|
|
ENT_CODE_REF=main
|
|
SISTER_NOTE="No ENT counterpart — built against ENT \`main\`."
|
|
echo "::notice::No ENT counterpart for '$HEAD_REF' (base '$BASE_REF') — building against main."
|
|
fi
|
|
|
|
# Stamp the time BEFORE dispatch so correlation can reject stale prior runs for this PR.
|
|
DISPATCH_TS=$(date -u +%s)
|
|
echo "::notice::Dispatching ENT engine ($REPO@$ENT_REF, code=$ENT_CODE_REF) for OSS PR #$PR ($HEAD_REF)…"
|
|
gh workflow run "$WF" --repo "$REPO" --ref "$ENT_REF" \
|
|
-f oss_branch="$HEAD_REF" \
|
|
-f oss_pr="$PR" \
|
|
-f oss_sha="$HEAD_SHA" \
|
|
-f enterprise_ref="$ENT_CODE_REF"
|
|
|
|
# Correlate by PR-scoped run-name ("Playwright E2E Crosscheck — OSS PR #<PR> ("; the "(" boundary keeps
|
|
# #12 from matching #123) AND createdAt >= dispatch time (minus 60s clock-skew), so a stale
|
|
# prior run for this PR is never picked. Take the NEWEST qualifying run.
|
|
MATCH="OSS PR #$PR ("
|
|
MIN_TS=$((DISPATCH_TS - 60))
|
|
RUN_ID=""
|
|
sleep 8 # let the dispatched run register before the first lookup
|
|
for i in $(seq 1 30); do # ~5 min to appear
|
|
RUN_ID=$(gh run list --repo "$REPO" --workflow "$WF" -L 40 \
|
|
--json databaseId,displayTitle,createdAt \
|
|
-q "[.[] | select(.displayTitle | contains(\"$MATCH\")) | select((.createdAt|fromdateiso8601) >= $MIN_TS)] | sort_by(.createdAt) | last | .databaseId" 2>/dev/null || echo "")
|
|
if [ -n "$RUN_ID" ] && [ "$RUN_ID" != "null" ]; then break; fi
|
|
sleep 10
|
|
done
|
|
if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
|
|
sticky "### 🚫 Playwright E2E Crosscheck — could not start
|
|
|
|
$MARKER
|
|
Dispatching the enterprise e2e engine failed or the run never appeared (check \`ORG_ADMIN_TOKEN\` / \`ENT_GATE_REF=$ENT_REF\`). The gate stays red until it runs — re-push or re-label to retry.
|
|
|
|
$SISTER_NOTE"
|
|
echo "::error::Could not locate the dispatched ENT run within 5 min — failing the gate (unverified)."
|
|
exit 1
|
|
fi
|
|
|
|
RUN_URL="https://github.com/$REPO/actions/runs/$RUN_ID"
|
|
echo "::notice::Tracking ENT run: $RUN_URL"
|
|
|
|
sticky "### 🔄 Playwright E2E Crosscheck — running
|
|
|
|
$MARKER
|
|
Building the enterprise binary and running the enterprise-only e2e specs against this PR.
|
|
$SISTER_NOTE
|
|
|
|
[ENT run]($RUN_URL)"
|
|
|
|
# Poll to completion (~75 min budget — covers a cold-cache ENT build+run worst case).
|
|
STATUS=""; CONCL=""
|
|
for i in $(seq 1 300); do # 300 * 15s = 75 min
|
|
read -r STATUS CONCL < <(gh run view "$RUN_ID" --repo "$REPO" \
|
|
--json status,conclusion -q '.status + " " + (.conclusion // "")' 2>/dev/null || echo "unknown ")
|
|
if [ "$STATUS" = "completed" ]; then break; fi
|
|
sleep 15
|
|
done
|
|
|
|
if [ "$STATUS" != "completed" ]; then
|
|
sticky "### ⏳ Playwright E2E Crosscheck — timed out
|
|
|
|
$MARKER
|
|
The [enterprise e2e run]($RUN_URL) didn't finish within the poll window. The gate stays red — re-push to retry.
|
|
$SISTER_NOTE"
|
|
echo "::error::ENT run did not finish within the poll window."
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$CONCL" = "success" ]; then
|
|
sticky "### ✅ Playwright E2E Crosscheck — passed
|
|
|
|
$MARKER
|
|
The enterprise-only e2e specs passed against this PR. $SISTER_NOTE
|
|
|
|
[ENT run]($RUN_URL)"
|
|
echo "::notice::✅ Enterprise e2e specs passed against this PR."
|
|
exit 0
|
|
fi
|
|
|
|
# cancelled/skipped/empty ≈ a newer push superseded this run (engine cancel-in-progress).
|
|
# NOT a failure — the newer run is authoritative and updates the sticky itself. Defer.
|
|
if [ "$CONCL" = "cancelled" ] || [ "$CONCL" = "skipped" ] || [ -z "$CONCL" ]; then
|
|
echo "::notice::ENT run concluded '$CONCL' (likely superseded by a newer push) — deferring, not failing."
|
|
exit 0
|
|
fi
|
|
|
|
# Genuine failure — distinguish an enterprise BUILD failure (compile) from SPEC failures,
|
|
# and for specs list the failing files from the engine's failed-specs artifact.
|
|
BUILD_CONCL=$(gh run view "$RUN_ID" --repo "$REPO" --json jobs -q '.jobs[]|select(.name=="build_binary")|.conclusion' 2>/dev/null || echo "")
|
|
TEST_CONCL=$(gh run view "$RUN_ID" --repo "$REPO" --json jobs -q '.jobs[]|select(.name=="ent_gate_tests")|.conclusion' 2>/dev/null || echo "")
|
|
|
|
if [ "$TEST_CONCL" = "failure" ]; then
|
|
rm -rf /tmp/fs; SPEC_LIST=""
|
|
if gh run download "$RUN_ID" --repo "$REPO" -n failed-specs -D /tmp/fs >/dev/null 2>&1 && [ -f /tmp/fs/failed-specs.txt ]; then
|
|
SPEC_LIST=$(sed '/^[[:space:]]*$/d; s/^/- `/; s/$/`/' /tmp/fs/failed-specs.txt)
|
|
fi
|
|
[ -n "$SPEC_LIST" ] || SPEC_LIST="_(couldn't read the failed-spec list — open the run for details)_"
|
|
sticky "### 🚫 Playwright E2E Crosscheck — Needs Review
|
|
|
|
$MARKER
|
|
The **enterprise-only** e2e spec(s) below failed against this PR. They run only against the enterprise binary, so this breakage wouldn't show up in OSS CI:
|
|
|
|
$SPEC_LIST
|
|
|
|
$SISTER_NOTE · [ENT run]($RUN_URL)
|
|
|
|
Fix and re-push, or add \`skip-ent-e2e\` if this is a known-safe change."
|
|
elif [ "$BUILD_CONCL" = "failure" ]; then
|
|
sticky "### 🚫 Playwright E2E Crosscheck — enterprise build failed
|
|
|
|
$MARKER
|
|
The enterprise binary didn't compile against this PR, so the specs never ran. This usually means the OSS code references enterprise code not present on the ref it was built against.
|
|
|
|
$SISTER_NOTE · [ENT run]($RUN_URL)"
|
|
else
|
|
sticky "### 🚫 Playwright E2E Crosscheck — failed
|
|
|
|
$MARKER
|
|
The [enterprise e2e run]($RUN_URL) failed (conclusion: \`$CONCL\`).
|
|
|
|
$SISTER_NOTE"
|
|
fi
|
|
echo "::error::Enterprise gate failed (conclusion=$CONCL build=$BUILD_CONCL test=$TEST_CONCL)."
|
|
exit 1
|