571 lines
23 KiB
YAML
571 lines
23 KiB
YAML
name: CI
|
|
|
|
on:
|
|
pull_request:
|
|
push:
|
|
branches:
|
|
- main
|
|
- "feat/**"
|
|
|
|
env:
|
|
CARGO_BUILD_JOBS: "2"
|
|
CARGO_INCREMENTAL: "0"
|
|
RUST_TEST_THREADS: "2"
|
|
TESTCONTAINERS_RYUK_DISABLED: "true"
|
|
|
|
jobs:
|
|
rust:
|
|
name: Rust Checks
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Install Rust toolchain
|
|
run: |
|
|
set -eu
|
|
toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n1)"
|
|
if [ -z "$toolchain" ]; then
|
|
echo "Unable to read Rust toolchain channel from rust-toolchain.toml" >&2
|
|
exit 1
|
|
fi
|
|
rustup toolchain install "$toolchain" --profile minimal --component clippy --component rustfmt
|
|
rustup default "$toolchain"
|
|
host="$(rustc -vV | sed -n 's/^host: //p')"
|
|
toolchain_dir="${RUSTUP_HOME:-$HOME/.rustup}/toolchains/${toolchain}-${host}"
|
|
toolchain_bin="$toolchain_dir/bin"
|
|
if [ ! -x "$toolchain_bin/rustc" ] || [ ! -x "$toolchain_bin/cargo" ]; then
|
|
echo "Rust $toolchain was not installed at $toolchain_dir." >&2
|
|
exit 1
|
|
fi
|
|
printf '%s\n' "$toolchain_bin" >> "$GITHUB_PATH"
|
|
"$toolchain_bin/rustc" --version
|
|
"$toolchain_bin/cargo" --version
|
|
"$toolchain_bin/rustfmt" --version
|
|
"$toolchain_bin/cargo-clippy" --version
|
|
|
|
- name: Verify runner toolchain
|
|
run: |
|
|
python3 --version
|
|
rustc --version
|
|
cargo --version
|
|
rustfmt --version
|
|
cargo clippy --version
|
|
docker --version
|
|
docker info
|
|
|
|
- name: Install dependency policy tool
|
|
run: cargo install cargo-deny --version 0.20.2 --locked
|
|
|
|
- name: Run tooling unit tests
|
|
run: python3 -m unittest discover -s tests/unit
|
|
|
|
- name: Check typed runtime configuration contract
|
|
run: |
|
|
cargo run -p crank-config --bin crank-config-contract -- --check
|
|
python3 scripts/check-runtime-config.py --root .
|
|
python3 scripts/check-config-boundaries.py --root .
|
|
|
|
- name: Check canonical migration contract
|
|
run: cargo run -p admin-api --bin crank-migrate -- plan --check
|
|
|
|
- name: Check typed metrics contract
|
|
run: |
|
|
cargo run -p crank-metrics --bin crank-metrics-contract -- --check
|
|
python3 scripts/check-metrics-boundaries.py --root .
|
|
|
|
- name: Check Capability Inventory
|
|
run: |
|
|
required_args=""
|
|
for number in $(seq 1 54); do
|
|
required_args="$required_args --required-fr FR-$number"
|
|
done
|
|
python3 scripts/validate-capability-inventory.py \
|
|
--root . \
|
|
--inventory docs/capability-inventory.json \
|
|
--schema docs/schemas/capability-inventory.schema.json \
|
|
$required_args
|
|
|
|
- name: Check Capability Baseline
|
|
run: |
|
|
python3 scripts/validate-capability-baseline.py \
|
|
--root . \
|
|
--manifest docs/capability-baseline/manifest.json \
|
|
--schema docs/schemas/capability-baseline.schema.json
|
|
|
|
- name: Check Community scope
|
|
run: scripts/check-community-scope.sh
|
|
|
|
- name: Check formatting
|
|
run: cargo fmt --all --check
|
|
|
|
- name: Check Rust code health
|
|
run: scripts/check-rust-code-health.sh
|
|
|
|
- name: Check dependency licenses and advisories
|
|
run: cargo deny --locked check advisories bans licenses sources
|
|
|
|
- name: Check Rust boundaries
|
|
run: scripts/check-rust-boundaries.sh
|
|
|
|
- name: Run clippy
|
|
run: cargo clippy --workspace --all-targets --all-features --jobs "$CARGO_BUILD_JOBS" -- -D warnings
|
|
|
|
- name: Run tests
|
|
run: cargo test --workspace --all-targets --jobs "$CARGO_BUILD_JOBS" -- --test-threads=1
|
|
|
|
ui:
|
|
name: UI Checks
|
|
runs-on: ubuntu-latest
|
|
needs: rust
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Verify runner toolchain
|
|
run: |
|
|
node --version
|
|
npm --version
|
|
docker --version
|
|
|
|
- name: Install UI dependencies
|
|
working-directory: apps/ui
|
|
run: npm ci
|
|
|
|
- name: Audit UI dependencies
|
|
working-directory: apps/ui
|
|
run: npm audit --audit-level=high
|
|
|
|
- name: Build UI bundle
|
|
working-directory: apps/ui
|
|
run: npm run build
|
|
|
|
frontend-e2e:
|
|
name: Frontend E2E
|
|
runs-on: ubuntu-latest
|
|
needs: ui
|
|
services:
|
|
crank-e2e-postgres:
|
|
image: postgres:16-alpine
|
|
env:
|
|
POSTGRES_DB: crank
|
|
POSTGRES_USER: crank
|
|
POSTGRES_PASSWORD: crank
|
|
options: >-
|
|
--health-cmd "pg_isready -U crank -d crank"
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
env:
|
|
CRANK_E2E_USE_EXTERNAL_POSTGRES: "1"
|
|
CRANK_E2E_POSTGRES_HOST: crank-e2e-postgres
|
|
CRANK_E2E_POSTGRES_PORT: "5432"
|
|
CRANK_E2E_POSTGRES_DB: crank
|
|
CRANK_E2E_POSTGRES_USER: crank
|
|
CRANK_E2E_POSTGRES_PASSWORD: crank
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Install Rust toolchain
|
|
run: |
|
|
set -eu
|
|
toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n1)"
|
|
if [ -z "$toolchain" ]; then
|
|
echo "Unable to read Rust toolchain channel from rust-toolchain.toml" >&2
|
|
exit 1
|
|
fi
|
|
rustup toolchain install "$toolchain" --profile minimal --component clippy --component rustfmt
|
|
rustup default "$toolchain"
|
|
host="$(rustc -vV | sed -n 's/^host: //p')"
|
|
toolchain_dir="${RUSTUP_HOME:-$HOME/.rustup}/toolchains/${toolchain}-${host}"
|
|
toolchain_bin="$toolchain_dir/bin"
|
|
if [ ! -x "$toolchain_bin/rustc" ] || [ ! -x "$toolchain_bin/cargo" ]; then
|
|
echo "Rust $toolchain was not installed at $toolchain_dir." >&2
|
|
exit 1
|
|
fi
|
|
printf '%s\n' "$toolchain_bin" >> "$GITHUB_PATH"
|
|
"$toolchain_bin/rustc" --version
|
|
"$toolchain_bin/cargo" --version
|
|
"$toolchain_bin/rustfmt" --version
|
|
"$toolchain_bin/cargo-clippy" --version
|
|
|
|
- name: Verify runner toolchain
|
|
run: |
|
|
rustc --version
|
|
cargo --version
|
|
node --version
|
|
npm --version
|
|
docker --version
|
|
|
|
- name: Install UI dependencies
|
|
working-directory: apps/ui
|
|
run: npm ci
|
|
|
|
- name: Install Playwright browser
|
|
working-directory: apps/ui
|
|
run: npx playwright install --with-deps chromium
|
|
|
|
- name: Prebuild e2e services
|
|
run: cargo build -p admin-api -p mcp-server --jobs "$CARGO_BUILD_JOBS"
|
|
|
|
- name: Run Playwright e2e
|
|
working-directory: apps/ui
|
|
run: |
|
|
mkdir -p ../../.tmp
|
|
rm -f ../../.tmp/openapi-playwright.json ../../.tmp/openapi-ui-evidence.json
|
|
PLAYWRIGHT_JSON_OUTPUT=../../.tmp/openapi-playwright.json npx playwright test
|
|
|
|
- name: Collect sanitized OpenAPI UI evidence
|
|
working-directory: apps/ui
|
|
run: |
|
|
trap 'rm -f ../../.tmp/openapi-playwright.json' EXIT
|
|
python3 ../../scripts/collect-capability-baseline.py playwright \
|
|
--report ../../.tmp/openapi-playwright.json \
|
|
--output ../../.tmp/openapi-ui-evidence.json \
|
|
--source-revision "$(git -C ../.. rev-parse HEAD)" \
|
|
--environment-class ci \
|
|
--flow-id openapi-upload-ui \
|
|
--required-test 'operations page imports OpenAPI methods as drafts' \
|
|
--required-test 'OpenAPI upload rejects invalid files locally and restores focus after Escape' \
|
|
--required-test 'OpenAPI upload recovers from pagehide and a preview server error' \
|
|
--required-test 'OpenAPI upload invalidates active draft creation after language or workspace changes' \
|
|
--required-test 'OpenAPI upload only renders the latest selected file and clears reset or close races' \
|
|
--required-test 'OpenAPI upload ignores a stale failure and renders only correlation identifiers'
|
|
python3 ../../scripts/validate-capability-run.py \
|
|
--schema ../../docs/schemas/capability-baseline.schema.json \
|
|
--candidate ../../.tmp/openapi-ui-evidence.json \
|
|
--require-accepted
|
|
|
|
- name: Show Playwright stack logs
|
|
if: failure()
|
|
run: |
|
|
find .tmp/ui-e2e/logs -maxdepth 1 -type f -print -exec sed -n '1,220p' {} \; || true
|
|
|
|
deployment:
|
|
name: Community Image Smoke
|
|
runs-on: ubuntu-latest
|
|
needs:
|
|
- rust
|
|
- ui
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Validate Community deployment manifests
|
|
run: |
|
|
docker compose -f docker-compose.yml --env-file .env.example config -q
|
|
docker compose -f deploy/community/docker-compose.yml --env-file deploy/community/.env.example config -q
|
|
docker compose -f deploy/community/docker-compose.images.yml --env-file deploy/community/.env.images.example --profile local-db config -q
|
|
|
|
- name: Build Community images
|
|
run: |
|
|
docker build -f apps/admin-api/Dockerfile -t crank/admin-api:ci .
|
|
docker build -f apps/mcp-server/Dockerfile -t crank/mcp-server:ci .
|
|
docker build -f apps/ui/Dockerfile -t crank/ui:ci .
|
|
|
|
- name: Start Community image stack
|
|
run: |
|
|
mkdir -p .tmp
|
|
cat > .tmp/community-smoke.env <<'EOF'
|
|
COMPOSE_PROJECT_NAME=crank-ci-smoke-${{ github.run_id }}-${{ github.run_attempt }}
|
|
POSTGRES_HOST=postgres
|
|
POSTGRES_PORT=5432
|
|
POSTGRES_PUBLISH_PORT=0
|
|
POSTGRES_DB=crank
|
|
POSTGRES_USER=crank
|
|
POSTGRES_PASSWORD=crank-ci-password
|
|
CRANK_ADMIN_API_IMAGE=crank/admin-api:ci
|
|
CRANK_MCP_SERVER_IMAGE=crank/mcp-server:ci
|
|
CRANK_UI_IMAGE=crank/ui:ci
|
|
CRANK_MASTER_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
|
CRANK_SESSION_SECRET=ci-session-secret
|
|
CRANK_PASSWORD_PEPPER=ci-password-pepper
|
|
CRANK_BOOTSTRAP_ADMIN_EMAIL=owner@crank.test
|
|
CRANK_BOOTSTRAP_ADMIN_PASSWORD=ci-admin-password
|
|
CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME=CI Owner
|
|
CRANK_BASE_URL=http://127.0.0.1
|
|
CRANK_PUBLISH_BIND=127.0.0.1
|
|
CRANK_ADMIN_PUBLISH_PORT=0
|
|
CRANK_MCP_PUBLISH_PORT=0
|
|
CRANK_UI_PUBLISH_PORT=0
|
|
CRANK_DEMO_SEED=true
|
|
EOF
|
|
docker compose -f deploy/community/docker-compose.images.yml \
|
|
--env-file .tmp/community-smoke.env --profile local-db up -d --wait
|
|
|
|
- name: Run authenticated Community image smoke
|
|
env:
|
|
CRANK_STAGING_ADMIN_EMAIL: owner@crank.test
|
|
CRANK_STAGING_ADMIN_PASSWORD: ci-admin-password
|
|
run: |
|
|
set -eu
|
|
project_name="$(sed -n 's/^COMPOSE_PROJECT_NAME=//p' .tmp/community-smoke.env)"
|
|
docker run --rm --network "${project_name}_default" \
|
|
-e CRANK_STAGING_ADMIN_EMAIL \
|
|
-e CRANK_STAGING_ADMIN_PASSWORD \
|
|
-i python:3.13-alpine \
|
|
python - http://ui:3000 < scripts/authenticated-product-smoke.py
|
|
|
|
- name: Show Community image logs
|
|
if: failure()
|
|
run: |
|
|
docker compose -f deploy/community/docker-compose.images.yml \
|
|
--env-file .tmp/community-smoke.env --profile local-db ps || true
|
|
docker compose -f deploy/community/docker-compose.images.yml \
|
|
--env-file .tmp/community-smoke.env --profile local-db logs --no-color || true
|
|
|
|
- name: Stop Community image stack
|
|
if: always()
|
|
run: |
|
|
docker compose -f deploy/community/docker-compose.images.yml \
|
|
--env-file .tmp/community-smoke.env --profile local-db down -v --remove-orphans || true
|
|
|
|
deploy:
|
|
name: Deploy
|
|
runs-on: ubuntu-latest
|
|
needs:
|
|
- rust
|
|
- ui
|
|
- frontend-e2e
|
|
- deployment
|
|
if: ${{ gitea.event_name == 'push' && gitea.ref == 'refs/heads/main' }}
|
|
env:
|
|
REGISTRY: git.itexp.me
|
|
IMAGE_TAG: ${{ gitea.sha }}
|
|
ADMIN_API_IMAGE: git.itexp.me/bsodfather/crank-community-admin-api
|
|
MCP_SERVER_IMAGE: git.itexp.me/bsodfather/crank-community-mcp-server
|
|
UI_IMAGE: git.itexp.me/bsodfather/crank-community-ui
|
|
OPENBAO_ENV_FILE: .openbao-env
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Verify runner toolchain
|
|
run: |
|
|
docker --version
|
|
command -v bao
|
|
bao version
|
|
|
|
- name: Load deployment secrets from OpenBao
|
|
env:
|
|
BAO_ADDR: ${{ secrets.BAO_ADDR }}
|
|
BAO_ROLE_ID: ${{ secrets.BAO_ROLE_ID }}
|
|
BAO_SECRET_ID: ${{ secrets.BAO_SECRET_ID }}
|
|
OPENBAO_APP: crank
|
|
run: scripts/load-openbao-env.sh
|
|
|
|
- name: Login to registry
|
|
run: |
|
|
. "$OPENBAO_ENV_FILE"
|
|
printf '%s' "$DEPLOY_REGISTRY_TOKEN" | \
|
|
docker login '${{ env.REGISTRY }}' -u "$DEPLOY_REGISTRY_USER" --password-stdin
|
|
|
|
- name: Build and push images
|
|
run: |
|
|
docker build -f apps/admin-api/Dockerfile \
|
|
-t '${{ env.ADMIN_API_IMAGE }}:${{ env.IMAGE_TAG }}' \
|
|
-t '${{ env.ADMIN_API_IMAGE }}:main' \
|
|
.
|
|
docker build -f apps/mcp-server/Dockerfile \
|
|
-t '${{ env.MCP_SERVER_IMAGE }}:${{ env.IMAGE_TAG }}' \
|
|
-t '${{ env.MCP_SERVER_IMAGE }}:main' \
|
|
.
|
|
docker build -f apps/ui/Dockerfile \
|
|
-t '${{ env.UI_IMAGE }}:${{ env.IMAGE_TAG }}' \
|
|
-t '${{ env.UI_IMAGE }}:main' \
|
|
.
|
|
scripts/scan-images.sh \
|
|
'${{ env.ADMIN_API_IMAGE }}:${{ env.IMAGE_TAG }}' \
|
|
'${{ env.MCP_SERVER_IMAGE }}:${{ env.IMAGE_TAG }}' \
|
|
'${{ env.UI_IMAGE }}:${{ env.IMAGE_TAG }}'
|
|
docker push '${{ env.ADMIN_API_IMAGE }}:${{ env.IMAGE_TAG }}'
|
|
docker push '${{ env.ADMIN_API_IMAGE }}:main'
|
|
docker push '${{ env.MCP_SERVER_IMAGE }}:${{ env.IMAGE_TAG }}'
|
|
docker push '${{ env.MCP_SERVER_IMAGE }}:main'
|
|
docker push '${{ env.UI_IMAGE }}:${{ env.IMAGE_TAG }}'
|
|
docker push '${{ env.UI_IMAGE }}:main'
|
|
|
|
- name: Configure SSH key
|
|
run: |
|
|
. "$OPENBAO_ENV_FILE"
|
|
mkdir -p ~/.ssh
|
|
chmod 700 ~/.ssh
|
|
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
|
|
chmod 600 ~/.ssh/id_ed25519
|
|
|
|
- name: Configure known hosts
|
|
run: |
|
|
. "$OPENBAO_ENV_FILE"
|
|
if [ -n "${DEPLOY_KNOWN_HOSTS:-}" ]; then
|
|
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts
|
|
else
|
|
ssh-keyscan -p "${DEPLOY_PORT:-22}" "$DEPLOY_HOST" > ~/.ssh/known_hosts
|
|
fi
|
|
chmod 644 ~/.ssh/known_hosts
|
|
|
|
- name: Sync deployment files to server
|
|
run: |
|
|
. "$OPENBAO_ENV_FILE"
|
|
ssh -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" \
|
|
"mkdir -p '$DEPLOY_PATH' && \
|
|
if [ -f '$DEPLOY_PATH/docker-compose.yml' ]; then \
|
|
cp '$DEPLOY_PATH/docker-compose.yml' '$DEPLOY_PATH/docker-compose.previous.yml'; \
|
|
fi"
|
|
rsync -az -e "ssh -p $DEPLOY_PORT" deploy/community/docker-compose.yml \
|
|
"$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/docker-compose.yml"
|
|
rsync -az -e "ssh -p $DEPLOY_PORT" scripts/deploy-community.sh \
|
|
"$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/deploy-community.sh"
|
|
|
|
- name: Write environment file
|
|
run: |
|
|
. "$OPENBAO_ENV_FILE"
|
|
tmp_env="$(mktemp)"
|
|
append_if_set() {
|
|
if [ -n "$2" ]; then
|
|
printf '%s=%s\n' "$1" "$2" >> "$tmp_env"
|
|
fi
|
|
}
|
|
: > "$tmp_env"
|
|
append_if_set POSTGRES_DB "$POSTGRES_DB"
|
|
append_if_set POSTGRES_USER "$POSTGRES_USER"
|
|
append_if_set POSTGRES_PASSWORD "$POSTGRES_PASSWORD"
|
|
append_if_set POSTGRES_HOST "$POSTGRES_HOST"
|
|
append_if_set POSTGRES_MAX_CONNECTIONS "${POSTGRES_MAX_CONNECTIONS:-}"
|
|
append_if_set POSTGRES_MIN_CONNECTIONS "${POSTGRES_MIN_CONNECTIONS:-}"
|
|
append_if_set POSTGRES_ACQUIRE_TIMEOUT_MS "${POSTGRES_ACQUIRE_TIMEOUT_MS:-}"
|
|
append_if_set POSTGRES_IDLE_TIMEOUT_MS "${POSTGRES_IDLE_TIMEOUT_MS:-}"
|
|
append_if_set POSTGRES_MAX_LIFETIME_MS "${POSTGRES_MAX_LIFETIME_MS:-}"
|
|
if [ -n "${POSTGRES_PORT:-}" ]; then
|
|
append_if_set POSTGRES_PORT "$POSTGRES_PORT"
|
|
elif [ -n "${PGBOUNCER_PORT:-}" ]; then
|
|
append_if_set POSTGRES_PORT "$PGBOUNCER_PORT"
|
|
fi
|
|
append_if_set CRANK_STORAGE_ROOT "$CRANK_STORAGE_ROOT"
|
|
append_if_set CRANK_PUBLISH_BIND "$CRANK_PUBLISH_BIND"
|
|
append_if_set CRANK_ADMIN_BIND "$CRANK_ADMIN_BIND"
|
|
append_if_set CRANK_MCP_BIND "$CRANK_MCP_BIND"
|
|
append_if_set CRANK_MCP_REFRESH_MS "$CRANK_MCP_REFRESH_MS"
|
|
append_if_set CRANK_ADMIN_RATE_LIMIT_RPS "${CRANK_ADMIN_RATE_LIMIT_RPS:-}"
|
|
append_if_set CRANK_ADMIN_RATE_LIMIT_BURST "${CRANK_ADMIN_RATE_LIMIT_BURST:-}"
|
|
append_if_set CRANK_MCP_RATE_LIMIT_RPS "${CRANK_MCP_RATE_LIMIT_RPS:-}"
|
|
append_if_set CRANK_MCP_RATE_LIMIT_BURST "${CRANK_MCP_RATE_LIMIT_BURST:-}"
|
|
append_if_set CRANK_RUNTIME_MAX_CONCURRENT_UNARY "${CRANK_RUNTIME_MAX_CONCURRENT_UNARY:-}"
|
|
append_if_set CRANK_RUNTIME_MAX_CONCURRENT_SESSIONS "${CRANK_RUNTIME_MAX_CONCURRENT_SESSIONS:-}"
|
|
append_if_set CRANK_OUTBOUND_ALLOWED_HOSTS "${CRANK_OUTBOUND_ALLOWED_HOSTS:-}"
|
|
append_if_set CRANK_OUTBOUND_DENIED_HOSTS "${CRANK_OUTBOUND_DENIED_HOSTS:-}"
|
|
append_if_set CRANK_OUTBOUND_MAX_RESPONSE_BYTES "${CRANK_OUTBOUND_MAX_RESPONSE_BYTES:-}"
|
|
append_if_set CRANK_ENVIRONMENT "${CRANK_ENVIRONMENT:-production}"
|
|
append_if_set CRANK_LOG_LEVEL "$CRANK_LOG_LEVEL"
|
|
append_if_set CRANK_SENTRY_DSN "${CRANK_SENTRY_DSN:-}"
|
|
append_if_set CRANK_METRICS_ENABLED "${CRANK_METRICS_ENABLED:-}"
|
|
append_if_set CRANK_ADMIN_METRICS_BIND "${CRANK_ADMIN_METRICS_BIND:-}"
|
|
append_if_set CRANK_MCP_METRICS_BIND "${CRANK_MCP_METRICS_BIND:-}"
|
|
append_if_set CRANK_METRICS_BEARER_TOKEN "${CRANK_METRICS_BEARER_TOKEN:-}"
|
|
append_if_set CRANK_INVOCATION_LOG_RETENTION_DAYS "${CRANK_INVOCATION_LOG_RETENTION_DAYS:-}"
|
|
append_if_set OTEL_EXPORTER_OTLP_ENDPOINT "${OTEL_EXPORTER_OTLP_ENDPOINT:-}"
|
|
append_if_set OTEL_EXPORTER_OTLP_TRACES_ENDPOINT "${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-}"
|
|
append_if_set OTEL_EXPORTER_OTLP_PROTOCOL "${OTEL_EXPORTER_OTLP_PROTOCOL:-}"
|
|
append_if_set OTEL_EXPORTER_OTLP_TRACES_PROTOCOL "${OTEL_EXPORTER_OTLP_TRACES_PROTOCOL:-}"
|
|
append_if_set OTEL_EXPORTER_OTLP_TIMEOUT "${OTEL_EXPORTER_OTLP_TIMEOUT:-}"
|
|
append_if_set OTEL_EXPORTER_OTLP_TRACES_TIMEOUT "${OTEL_EXPORTER_OTLP_TRACES_TIMEOUT:-}"
|
|
append_if_set OTEL_EXPORTER_OTLP_HEADERS "${OTEL_EXPORTER_OTLP_HEADERS:-}"
|
|
append_if_set OTEL_EXPORTER_OTLP_TRACES_HEADERS "${OTEL_EXPORTER_OTLP_TRACES_HEADERS:-}"
|
|
append_if_set OTEL_BSP_MAX_QUEUE_SIZE "${OTEL_BSP_MAX_QUEUE_SIZE:-}"
|
|
append_if_set OTEL_BSP_MAX_EXPORT_BATCH_SIZE "${OTEL_BSP_MAX_EXPORT_BATCH_SIZE:-}"
|
|
append_if_set OTEL_BSP_SCHEDULE_DELAY "${OTEL_BSP_SCHEDULE_DELAY:-}"
|
|
append_if_set OTEL_BSP_EXPORT_TIMEOUT "${OTEL_BSP_EXPORT_TIMEOUT:-}"
|
|
append_if_set CRANK_MASTER_KEY "$CRANK_MASTER_KEY"
|
|
append_if_set CRANK_BASE_URL "$CRANK_BASE_URL"
|
|
append_if_set CRANK_CACHE_BACKEND "$CRANK_CACHE_BACKEND"
|
|
append_if_set CRANK_CACHE_URL "$CRANK_CACHE_URL"
|
|
append_if_set CRANK_CACHE_DEFAULT_TTL_MS "$CRANK_CACHE_DEFAULT_TTL_MS"
|
|
append_if_set CRANK_SESSION_SECRET "$CRANK_SESSION_SECRET"
|
|
append_if_set CRANK_PASSWORD_PEPPER "$CRANK_PASSWORD_PEPPER"
|
|
append_if_set CRANK_SESSION_TTL_HOURS "$CRANK_SESSION_TTL_HOURS"
|
|
append_if_set CRANK_BOOTSTRAP_ADMIN_EMAIL "$CRANK_BOOTSTRAP_ADMIN_EMAIL"
|
|
append_if_set CRANK_BOOTSTRAP_ADMIN_PASSWORD "$CRANK_BOOTSTRAP_ADMIN_PASSWORD"
|
|
append_if_set CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME "$CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME"
|
|
append_if_set CRANK_DEMO_SEED "$CRANK_DEMO_SEED"
|
|
{
|
|
printf 'COMPOSE_PROJECT_NAME=community\n'
|
|
printf 'CRANK_ADMIN_API_IMAGE=%s:%s\n' '${{ env.ADMIN_API_IMAGE }}' '${{ env.IMAGE_TAG }}'
|
|
printf 'CRANK_MCP_SERVER_IMAGE=%s:%s\n' '${{ env.MCP_SERVER_IMAGE }}' '${{ env.IMAGE_TAG }}'
|
|
printf 'CRANK_UI_IMAGE=%s:%s\n' '${{ env.UI_IMAGE }}' '${{ env.IMAGE_TAG }}'
|
|
} >> "$tmp_env"
|
|
cat "$tmp_env" | ssh -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" \
|
|
"mkdir -p '$DEPLOY_PATH' && \
|
|
if [ -f '$DEPLOY_PATH/.env' ]; then \
|
|
cp '$DEPLOY_PATH/.env' '$DEPLOY_PATH/.env.previous'; \
|
|
fi && cat > '$DEPLOY_PATH/.env'"
|
|
rm -f "$tmp_env"
|
|
|
|
- name: Validate required environment variables
|
|
run: |
|
|
. "$OPENBAO_ENV_FILE"
|
|
ssh -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" "
|
|
set -e
|
|
cd '$DEPLOY_PATH'
|
|
required_vars='
|
|
POSTGRES_HOST
|
|
POSTGRES_PORT
|
|
POSTGRES_DB
|
|
POSTGRES_USER
|
|
POSTGRES_PASSWORD
|
|
CRANK_MASTER_KEY
|
|
CRANK_SESSION_SECRET
|
|
CRANK_PASSWORD_PEPPER
|
|
CRANK_BOOTSTRAP_ADMIN_EMAIL
|
|
CRANK_BOOTSTRAP_ADMIN_PASSWORD
|
|
CRANK_BASE_URL
|
|
'
|
|
for var in \$required_vars; do
|
|
value=\$(grep -E \"^\${var}=\" .env | tail -n1 | cut -d= -f2- || true)
|
|
if [ -z \"\$value\" ]; then
|
|
echo \"missing required env: \$var\" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
"
|
|
|
|
- name: Deploy with Docker Compose
|
|
run: |
|
|
. "$OPENBAO_ENV_FILE"
|
|
printf '%s' "$DEPLOY_REGISTRY_TOKEN" | ssh -p "$DEPLOY_PORT" \
|
|
"$DEPLOY_USER@$DEPLOY_HOST" \
|
|
"docker login '${{ env.REGISTRY }}' -u '$DEPLOY_REGISTRY_USER' --password-stdin"
|
|
ssh -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" \
|
|
"chmod 700 '$DEPLOY_PATH/deploy-community.sh' && \
|
|
'$DEPLOY_PATH/deploy-community.sh' '$DEPLOY_PATH'"
|
|
|
|
- name: Verify health endpoints
|
|
run: |
|
|
. "$OPENBAO_ENV_FILE"
|
|
ssh -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" "
|
|
set -e
|
|
cd '$DEPLOY_PATH'
|
|
for attempt in \$(seq 1 30); do
|
|
if curl --fail --silent http://127.0.0.1:3000/ >/dev/null \
|
|
&& curl --fail --silent http://127.0.0.1:3001/ready >/dev/null \
|
|
&& curl --fail --silent http://127.0.0.1:3002/ready >/dev/null; then
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo 'deployment health verification failed' >&2
|
|
docker compose ps >&2
|
|
exit 1
|
|
"
|
|
|
|
- name: Run authenticated product smoke
|
|
run: |
|
|
. "$OPENBAO_ENV_FILE"
|
|
CRANK_STAGING_ADMIN_EMAIL="$CRANK_BOOTSTRAP_ADMIN_EMAIL" \
|
|
CRANK_STAGING_ADMIN_PASSWORD="$CRANK_BOOTSTRAP_ADMIN_PASSWORD" \
|
|
scripts/authenticated-product-smoke.sh "$CRANK_BASE_URL"
|