mirror of
https://github.com/Pacerino/CaddyProxyManager.git
synced 2026-07-22 05:31:41 -04:00
Deploy the PR image to the self-hosted runner in API mode and run a full end-to-end test (login, host creation, live reverse-proxy to whoami, basic auth plugin, module discovery). The environment is published on a fixed port 3001 and left running until the PR is closed or a /teardown comment is posted. - docker-compose.e2e.yml: cpm (API mode) + whoami upstream, per-PR isolation - test/e2e/run.sh: full api-mode E2E assertions against the live container - .github/workflows/pr-e2e.yml: deploy/E2E, teardown on close, /teardown comment
146 lines
5.6 KiB
YAML
146 lines
5.6 KiB
YAML
name: PR E2E
|
|
|
|
# Deploys the PR's image to the self-hosted runner and runs a full end-to-end
|
|
# test against it in API mode. The environment is left running until the PR is
|
|
# closed (or torn down early via a "/teardown" comment) so it can be inspected
|
|
# through a Cloudflare Tunnel.
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened, closed]
|
|
issue_comment:
|
|
types: [created]
|
|
|
|
# Only ever run one PR environment at a time (fixed port 3001).
|
|
concurrency:
|
|
group: pr-e2e
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
COMPOSE_FILE: docker-compose.e2e.yml
|
|
CPM_PORT: "3001"
|
|
|
|
jobs:
|
|
deploy-e2e:
|
|
name: Deploy & E2E
|
|
runs-on: self-hosted
|
|
# Own PRs only (not forks), and not on the closed event.
|
|
if: >
|
|
github.event_name == 'pull_request' &&
|
|
github.event.action != 'closed' &&
|
|
github.event.pull_request.head.repo.full_name == github.repository
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
CPM_ADMIN_EMAIL: ${{ secrets.E2E_ADMIN_EMAIL || 'admin@example.com' }}
|
|
CPM_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD || 'changeme' }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# Fail fast if a *different* PR's stack already holds the fixed port.
|
|
- name: Ensure port 3001 is free for this PR
|
|
run: |
|
|
set -euo pipefail
|
|
project="cpm-pr-${PR_NUMBER}"
|
|
# Any container publishing :3001 that is NOT this PR's stack blocks us.
|
|
holder=$(docker ps --filter "publish=${CPM_PORT}" --format '{{.Names}}' | grep -v "^cpm-pr-${PR_NUMBER}" || true)
|
|
if [ -n "$holder" ]; then
|
|
echo "::error::Port ${CPM_PORT} is in use by another PR environment: $holder"
|
|
echo "Tear it down first (close that PR or comment /teardown there)."
|
|
exit 1
|
|
fi
|
|
echo "Port ${CPM_PORT} is available for ${project}."
|
|
|
|
- name: Build PR image
|
|
run: |
|
|
set -euo pipefail
|
|
IMAGE="cpm-pr-${PR_NUMBER}:${GITHUB_SHA::8}"
|
|
docker build \
|
|
--build-arg VERSION="pr-${PR_NUMBER}" \
|
|
--build-arg COMMIT="${GITHUB_SHA::8}" \
|
|
-t "$IMAGE" .
|
|
echo "CPM_IMAGE=$IMAGE" >> "$GITHUB_ENV"
|
|
|
|
- name: Bring up the environment
|
|
run: |
|
|
set -euo pipefail
|
|
# Recreate cleanly (handles the synchronize re-run case).
|
|
docker compose -p "cpm-pr-${PR_NUMBER}" -f "$COMPOSE_FILE" up -d --force-recreate
|
|
|
|
- name: Run E2E tests
|
|
env:
|
|
CPM_URL: http://localhost:3001
|
|
CADDY_URL: http://localhost:8080
|
|
ADMIN_EMAIL: ${{ env.CPM_ADMIN_EMAIL }}
|
|
ADMIN_PASSWORD: ${{ env.CPM_ADMIN_PASSWORD }}
|
|
TEST_DOMAIN: app.e2e.local
|
|
run: bash test/e2e/run.sh
|
|
|
|
- name: Dump logs on failure
|
|
if: failure()
|
|
run: docker compose -p "cpm-pr-${PR_NUMBER}" -f "$COMPOSE_FILE" logs --no-color || true
|
|
|
|
- name: Comment environment URL
|
|
if: success()
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const n = context.payload.pull_request.number;
|
|
const marker = "<!-- cpm-e2e-env -->";
|
|
const body = `${marker}\n✅ **PR E2E environment is up** (port \`3001\`).\n\n` +
|
|
`Point your Cloudflare Tunnel at \`localhost:3001\` to inspect it.\n\n` +
|
|
`Comment \`/teardown\` to stop it early; it is otherwise removed when the PR closes.`;
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner: context.repo.owner, repo: context.repo.repo, issue_number: n,
|
|
});
|
|
const existing = comments.find(c => c.body && c.body.includes(marker));
|
|
if (existing) {
|
|
await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body });
|
|
} else {
|
|
await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: n, body });
|
|
}
|
|
|
|
teardown-on-close:
|
|
name: Teardown (PR closed)
|
|
runs-on: self-hosted
|
|
if: >
|
|
github.event_name == 'pull_request' &&
|
|
github.event.action == 'closed'
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Tear down the environment
|
|
run: |
|
|
set -euo pipefail
|
|
docker compose -p "cpm-pr-${PR_NUMBER}" -f "$COMPOSE_FILE" down -v --remove-orphans || true
|
|
docker image rm "cpm-pr-${PR_NUMBER}:${GITHUB_SHA::8}" 2>/dev/null || true
|
|
echo "Environment for PR #${PR_NUMBER} torn down."
|
|
|
|
teardown-on-comment:
|
|
name: Teardown (/teardown comment)
|
|
runs-on: self-hosted
|
|
# Only on PR comments containing "/teardown", authored by the repo owner.
|
|
if: >
|
|
github.event_name == 'issue_comment' &&
|
|
github.event.issue.pull_request != null &&
|
|
contains(github.event.comment.body, '/teardown') &&
|
|
github.event.comment.user.login == github.repository_owner
|
|
env:
|
|
PR_NUMBER: ${{ github.event.issue.number }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Tear down the environment
|
|
run: |
|
|
set -euo pipefail
|
|
docker compose -p "cpm-pr-${PR_NUMBER}" -f "$COMPOSE_FILE" down -v --remove-orphans || true
|
|
echo "Environment for PR #${PR_NUMBER} torn down."
|
|
- name: Acknowledge
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner, repo: context.repo.repo,
|
|
issue_number: ${{ env.PR_NUMBER }},
|
|
body: "🧹 PR E2E environment torn down.",
|
|
});
|