paperclip/.github/workflows/release-smoke.yml

201 lines
7.0 KiB
YAML

name: Release Smoke
on:
workflow_dispatch:
inputs:
paperclip_version:
description: Published Paperclip dist-tag to test
required: true
default: canary
type: choice
options:
- canary
- nightly
- beta
- latest
host_port:
description: Host port for the Docker smoke container
required: false
default: "3232"
type: string
artifact_name:
description: Artifact name for uploaded diagnostics
required: false
default: release-smoke
type: string
workflow_call:
inputs:
paperclip_version:
required: true
type: string
host_port:
required: false
default: "3232"
type: string
artifact_name:
required: false
default: release-smoke
type: string
jobs:
# The Docker smoke below can never exercise the background-service leg of
# onboarding: containers have no service manager, so v2026.824.0 shipped a
# service install that crash-looped on a missing shim while every
# golden-path check stayed green. Run the same published artifact directly
# on the runner VM's systemd and require the installed service to end up
# serving.
smoke_service:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 24
- name: Start a user systemd session
# The hosted runner has no login session for the runner user, so
# `systemctl --user` cannot reach a user manager until lingering
# starts one. Export the session address for the steps below.
run: |
sudo loginctl enable-linger "$(id -un)"
uid="$(id -u)"
for _ in $(seq 1 30); do
[[ -S "/run/user/$uid/bus" ]] && break
sleep 1
done
[[ -S "/run/user/$uid/bus" ]]
{
echo "XDG_RUNTIME_DIR=/run/user/$uid"
echo "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus"
} >> "$GITHUB_ENV"
- name: Onboard with the background service
env:
PAPERCLIPAI_VERSION: ${{ inputs.paperclip_version }}
DATA_DIR: ${{ runner.temp }}/service-smoke-data
SMOKE_CLEANUP: "false"
run: ./scripts/service-onboard-smoke.sh
- name: Capture service diagnostics
if: always()
run: |
{
systemctl --user --no-pager status paperclipai.service || true
journalctl --user -u paperclipai.service --no-pager || true
} > "$RUNNER_TEMP/paperclipai-service.log" 2>&1
- name: Upload service diagnostics
if: always()
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.artifact_name }}-service
path: ${{ runner.temp }}/paperclipai-service.log
retention-days: 14
smoke:
runs-on: ubuntu-latest
timeout-minutes: 45
# Fixed here rather than read back out of the harness, so the `always()`
# diagnostics steps below still know the container's name when the launch
# step is the thing that failed. Reading it back was why a failing smoke
# uploaded no Docker logs at all.
env:
SMOKE_CONTAINER_NAME: release-smoke-onboard
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 9.15.4
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 24
cache: pnpm
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Verify runner Chrome
# Release smoke also runs headless on GitHub's Ubuntu image, so use the
# runner's preinstalled Chrome instead of a Playwright browser download.
run: google-chrome --version
- name: Launch Docker smoke harness
run: |
HOST_PORT="${{ inputs.host_port }}" \
DATA_DIR="$RUNNER_TEMP/release-smoke-data" \
PAPERCLIPAI_VERSION="${{ inputs.paperclip_version }}" \
SMOKE_READY_TIMEOUT_SECONDS=420 \
SMOKE_DETACH=true \
SMOKE_METADATA_FILE="${{ runner.temp }}/release-smoke.env" \
SMOKE_LOG_FILE="${{ runner.temp }}/docker-onboard-smoke.log" \
./scripts/docker-onboard-smoke.sh
set -a
source "${{ runner.temp }}/release-smoke.env"
set +a
{
echo "SMOKE_BASE_URL=$SMOKE_BASE_URL"
echo "SMOKE_ADMIN_EMAIL=$SMOKE_ADMIN_EMAIL"
echo "SMOKE_ADMIN_PASSWORD=$SMOKE_ADMIN_PASSWORD"
echo "SMOKE_DATA_DIR=$SMOKE_DATA_DIR"
echo "SMOKE_IMAGE_NAME=$SMOKE_IMAGE_NAME"
echo "SMOKE_PAPERCLIPAI_VERSION=$SMOKE_PAPERCLIPAI_VERSION"
} >> "$GITHUB_ENV"
- name: Run release smoke Playwright suite
env:
PAPERCLIP_RELEASE_SMOKE_BASE_URL: ${{ env.SMOKE_BASE_URL }}
PAPERCLIP_RELEASE_SMOKE_EMAIL: ${{ env.SMOKE_ADMIN_EMAIL }}
PAPERCLIP_RELEASE_SMOKE_PASSWORD: ${{ env.SMOKE_ADMIN_PASSWORD }}
PAPERCLIP_PLAYWRIGHT_CHANNEL: "chrome"
run: pnpm run test:release-smoke
- name: Capture Docker logs
if: always()
run: |
log_file="${{ runner.temp }}/docker-onboard-smoke.log"
# A live container has the fuller story, so prefer it. When the
# harness already tore the container down it wrote this file on its
# way out, and that copy is kept rather than clobbered.
if docker inspect "$SMOKE_CONTAINER_NAME" >/dev/null 2>&1; then
docker logs "$SMOKE_CONTAINER_NAME" >"$log_file" 2>&1 || true
fi
# Never leave the upload with nothing to say. An absent log reads as
# a missing artifact; a file saying the container was gone reads as
# the diagnosis it is.
if [[ ! -s "$log_file" ]]; then
echo "No Docker logs captured: container '$SMOKE_CONTAINER_NAME' left no log dump and is no longer present." >"$log_file"
fi
echo "Captured $(wc -l <"$log_file") log lines to $log_file"
- name: Upload diagnostics
if: always()
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.artifact_name }}
path: |
${{ runner.temp }}/docker-onboard-smoke.log
${{ runner.temp }}/release-smoke.env
tests/release-smoke/playwright-report/
tests/release-smoke/test-results/
# The capture step above guarantees the log file, so an empty upload
# means the diagnostics wiring itself broke — which is worth failing
# over rather than burying in a warning nobody reads.
if-no-files-found: error
retention-days: 14
- name: Stop Docker smoke container
if: always()
run: docker rm -f "$SMOKE_CONTAINER_NAME" >/dev/null 2>&1 || true