stabilize tests
This commit is contained in:
49
devops/ci-local/.env.local.template
Normal file
49
devops/ci-local/.env.local.template
Normal file
@@ -0,0 +1,49 @@
|
||||
# =============================================================================
|
||||
# LOCAL CI ENVIRONMENT VARIABLES
|
||||
# =============================================================================
|
||||
# Copy this file to .env.local and adjust values for your machine:
|
||||
# cp .env.local.template .env.local
|
||||
#
|
||||
# .env.local is git-ignored. Do NOT commit secrets.
|
||||
# =============================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test toggles
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set to 1 to enable RabbitMQ integration tests (requires running RabbitMQ)
|
||||
STELLAOPS_TEST_RABBITMQ=0
|
||||
|
||||
# Set to 1 to enable Valkey/Redis integration tests (requires running Valkey)
|
||||
STELLAOPS_TEST_VALKEY=0
|
||||
|
||||
# Enable full integration test suite (needs supporting services)
|
||||
STELLAOPS_INTEGRATION_TESTS=false
|
||||
|
||||
# Enable live/external tests (network-dependent, not offline-safe)
|
||||
STELLAOPS_LIVE_TESTS=false
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# External service connections
|
||||
# ---------------------------------------------------------------------------
|
||||
# Testcontainers auto-configure PostgreSQL; only set for an external instance
|
||||
STELLAOPS_TEST_POSTGRES_CONNECTION=
|
||||
|
||||
# MongoDB connection string (leave empty to skip Mongo-dependent tests)
|
||||
STELLAOPS_TEST_MONGO_URI=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HSM / crypto
|
||||
# ---------------------------------------------------------------------------
|
||||
# Path to SoftHSM2 shared library (leave empty to skip HSM tests)
|
||||
# Linux: /usr/lib/softhsm/libsofthsm2.so
|
||||
# macOS: /usr/local/lib/softhsm/libsofthsm2.so
|
||||
# Windows: C:\SoftHSM2\lib\softhsm2-64.dll
|
||||
STELLAOPS_SOFTHSM_LIB=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# .NET runtime
|
||||
# ---------------------------------------------------------------------------
|
||||
DOTNET_NOLOGO=1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
TZ=UTC
|
||||
109
devops/ci-local/README.md
Normal file
109
devops/ci-local/README.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Local CI with act
|
||||
|
||||
Run Gitea CI workflows on your machine using [nektos/act](https://github.com/nektos/act) and the StellaOps CI Docker image.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
| Tool | Install |
|
||||
|------|---------|
|
||||
| Docker Desktop | [docs.docker.com/get-docker](https://docs.docker.com/get-docker/) |
|
||||
| act | **Windows:** `choco install act-cli` / **macOS:** `brew install act` / **Linux:** `curl -sSL https://raw.githubusercontent.com/nektos/act/master/install.sh \| sudo bash` |
|
||||
|
||||
Docker must be running before you invoke any command below.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# 1. Build the CI image (one-time, ~10 min first build)
|
||||
docker build -t stellaops-ci:local -f devops/docker/Dockerfile.ci .
|
||||
|
||||
# 2. Copy the env template (edit as needed)
|
||||
cp devops/ci-local/.env.local.template devops/ci-local/.env.local
|
||||
|
||||
# 3. List available jobs
|
||||
act -l
|
||||
|
||||
# 4. Dry-run a workflow
|
||||
act -W .gitea/workflows/test-matrix.yml -n
|
||||
```
|
||||
|
||||
### Windows (PowerShell)
|
||||
|
||||
```powershell
|
||||
.\devops\ci-local\run-act.ps1 -List
|
||||
.\devops\ci-local\run-act.ps1 -Workflow test-matrix -DryRun
|
||||
.\devops\ci-local\run-act.ps1 -Workflow build-test-deploy -Job build
|
||||
```
|
||||
|
||||
### Linux / macOS
|
||||
|
||||
```bash
|
||||
./devops/ci-local/run-act.sh --list
|
||||
./devops/ci-local/run-act.sh --workflow test-matrix --dry-run
|
||||
./devops/ci-local/run-act.sh --workflow build-test-deploy --job build
|
||||
```
|
||||
|
||||
### Full-featured runner (bash only)
|
||||
|
||||
The `local-ci.sh` script supports additional modes beyond raw act invocation:
|
||||
|
||||
```bash
|
||||
./devops/scripts/local-ci.sh smoke # Quick unit tests
|
||||
./devops/scripts/local-ci.sh pr # Full PR-gating suite
|
||||
./devops/scripts/local-ci.sh workflow --workflow test-matrix
|
||||
./devops/scripts/local-ci.sh module --module Scanner
|
||||
```
|
||||
|
||||
## Common workflows
|
||||
|
||||
| Workflow | What it tests | Example |
|
||||
|----------|--------------|---------|
|
||||
| `test-matrix.yml` | Unit + integration test matrix | `act -W .gitea/workflows/test-matrix.yml -n` |
|
||||
| `build-test-deploy.yml` | Full build/test/deploy pipeline | `act -W .gitea/workflows/build-test-deploy.yml -n` |
|
||||
| `scanner-analyzers.yml` | Scanner analyzer suite | `act -W .gitea/workflows/scanner-analyzers.yml -n` |
|
||||
| `parity-tests.yml` | Cross-platform parity checks | `act -W .gitea/workflows/parity-tests.yml -n` |
|
||||
| `integration-tests-gate.yml` | Integration test gate | `act -W .gitea/workflows/integration-tests-gate.yml -n` |
|
||||
| `schema-validation.yml` | JSON/OAS schema validation | `act -W .gitea/workflows/schema-validation.yml -n` |
|
||||
| `determinism-gate.yml` | Deterministic output checks | `act -W .gitea/workflows/determinism-gate.yml -n` |
|
||||
|
||||
## Environment variables
|
||||
|
||||
See `.env.local.template` for the full list. Key variables:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `STELLAOPS_TEST_RABBITMQ` | `0` | Set `1` to enable RabbitMQ tests |
|
||||
| `STELLAOPS_TEST_VALKEY` | `0` | Set `1` to enable Valkey/Redis tests |
|
||||
| `STELLAOPS_INTEGRATION_TESTS` | `false` | Enable integration test suite |
|
||||
| `STELLAOPS_LIVE_TESTS` | `false` | Enable network-dependent tests |
|
||||
| `STELLAOPS_SOFTHSM_LIB` | _(empty)_ | Path to SoftHSM2 library for HSM tests |
|
||||
| `STELLAOPS_TEST_POSTGRES_CONNECTION` | _(empty)_ | External Postgres (Testcontainers auto-configure if empty) |
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **Services block:** `act` does not natively support `services:` blocks in workflow YAML. Workflows that declare Postgres/Valkey/RabbitMQ services will need those services running externally (use `docker-compose.testing.yml`).
|
||||
- **Secrets:** GitHub/Gitea-style secrets are not available locally. Use `.env.local` for test-only values. Never put production secrets in `.env.local`.
|
||||
- **Artifact upload:** `act` provides a local artifact server (`--artifact-server-path ./out/act-artifacts`). Upload/download actions work but paths differ from real CI.
|
||||
- **Composite actions:** Some nested composite actions may not resolve correctly under act.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Docker socket on Windows / WSL
|
||||
If act cannot connect to Docker, ensure Docker Desktop has "Expose daemon on tcp://localhost:2375 without TLS" enabled, or use WSL 2 integration.
|
||||
|
||||
### OOM with parallel builds
|
||||
The CI image runs .NET builds that can consume significant memory. If builds fail with OOM, increase Docker Desktop memory allocation (Settings > Resources > Memory) to at least 8 GB.
|
||||
|
||||
### MSYS path mangling (Git Bash on Windows)
|
||||
Git Bash on Windows rewrites Unix-style paths. If act receives corrupted paths, prefix the command with `MSYS_NO_PATHCONV=1`:
|
||||
|
||||
```bash
|
||||
MSYS_NO_PATHCONV=1 act -W .gitea/workflows/test-matrix.yml -n
|
||||
```
|
||||
|
||||
### Container reuse issues
|
||||
If you see stale state between runs, disable container reuse:
|
||||
|
||||
```bash
|
||||
act --no-reuse -W .gitea/workflows/test-matrix.yml
|
||||
```
|
||||
14
devops/ci-local/events/pull-request.json
Normal file
14
devops/ci-local/events/pull-request.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"action": "opened",
|
||||
"number": 1,
|
||||
"pull_request": {
|
||||
"head": {
|
||||
"ref": "feature-branch",
|
||||
"sha": "abc1234567890abcdef1234567890abcdef123456"
|
||||
},
|
||||
"base": {
|
||||
"ref": "main",
|
||||
"sha": "def4567890abcdef1234567890abcdef12345678"
|
||||
}
|
||||
}
|
||||
}
|
||||
5
devops/ci-local/events/push.json
Normal file
5
devops/ci-local/events/push.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ref": "refs/heads/main",
|
||||
"before": "0000000000000000000000000000000000000000",
|
||||
"after": "abc1234567890abcdef1234567890abcdef123456"
|
||||
}
|
||||
162
devops/ci-local/run-act.ps1
Normal file
162
devops/ci-local/run-act.ps1
Normal file
@@ -0,0 +1,162 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Run Gitea CI workflows locally using act.
|
||||
|
||||
.DESCRIPTION
|
||||
PowerShell wrapper around nektos/act for local CI execution.
|
||||
Builds the CI Docker image if needed, ensures .env.local exists,
|
||||
and invokes act with the correct arguments.
|
||||
|
||||
.PARAMETER Workflow
|
||||
Workflow file name (with or without .yml) under .gitea/workflows/.
|
||||
|
||||
.PARAMETER Job
|
||||
Run only a specific job within the workflow.
|
||||
|
||||
.PARAMETER List
|
||||
List all available workflow jobs without running them.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Dry-run mode (-n). Shows what would execute without running.
|
||||
|
||||
.PARAMETER Event
|
||||
Event type to simulate: pull_request (default) or push.
|
||||
|
||||
.PARAMETER Rebuild
|
||||
Force rebuild of the CI Docker image.
|
||||
|
||||
.PARAMETER ActVerbose
|
||||
Enable verbose act output.
|
||||
|
||||
.EXAMPLE
|
||||
.\run-act.ps1 -List
|
||||
.\run-act.ps1 -Workflow test-matrix -DryRun
|
||||
.\run-act.ps1 -Workflow build-test-deploy -Job build
|
||||
.\run-act.ps1 -Event push -Workflow release-validation
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Workflow,
|
||||
[string]$Job,
|
||||
[switch]$List,
|
||||
[switch]$DryRun,
|
||||
[ValidateSet('pull_request', 'push')]
|
||||
[string]$Event = 'pull_request',
|
||||
[switch]$Rebuild,
|
||||
[switch]$ActVerbose
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Resolve paths
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$RepoRoot = (Resolve-Path "$ScriptDir\..\..").Path
|
||||
$CiLocalDir = $ScriptDir
|
||||
$CiImage = 'stellaops-ci:local'
|
||||
$Dockerfile = Join-Path $RepoRoot 'devops\docker\Dockerfile.ci'
|
||||
|
||||
# ---- Prerequisite checks ----
|
||||
|
||||
# Docker
|
||||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "Docker is not installed or not in PATH."
|
||||
exit 1
|
||||
}
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$null = docker info 2>$null
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Docker is not running. Start Docker Desktop and try again."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# act
|
||||
if (-not (Get-Command act -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "act is not installed. Install with: choco install act-cli"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ---- Build CI image if needed ----
|
||||
|
||||
docker image inspect $CiImage 2>&1 | Out-Null
|
||||
$imageExists = $LASTEXITCODE -eq 0
|
||||
if ($Rebuild -or -not $imageExists) {
|
||||
Write-Host "[ci-local] Building CI image $CiImage ..." -ForegroundColor Cyan
|
||||
docker build -t $CiImage -f $Dockerfile $RepoRoot
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "CI image build failed."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---- Ensure .env.local exists ----
|
||||
|
||||
$envLocal = Join-Path $CiLocalDir '.env.local'
|
||||
$envTemplate = Join-Path $CiLocalDir '.env.local.template'
|
||||
if (-not (Test-Path $envLocal)) {
|
||||
Write-Host "[ci-local] Creating .env.local from template ..." -ForegroundColor Yellow
|
||||
Copy-Item $envTemplate $envLocal
|
||||
}
|
||||
|
||||
# ---- Assemble act arguments ----
|
||||
|
||||
$actArgs = @()
|
||||
|
||||
# List mode
|
||||
if ($List) {
|
||||
$actArgs += '-l'
|
||||
Push-Location $RepoRoot
|
||||
try { act @actArgs }
|
||||
finally { Pop-Location }
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
# Workflow
|
||||
if ($Workflow) {
|
||||
$wfFile = $Workflow
|
||||
if (-not $wfFile.EndsWith('.yml')) { $wfFile = "$wfFile.yml" }
|
||||
$wfPath = Join-Path $RepoRoot ".gitea\workflows\$wfFile"
|
||||
if (-not (Test-Path $wfPath)) {
|
||||
Write-Error "Workflow not found: $wfPath"
|
||||
exit 1
|
||||
}
|
||||
$actArgs += '-W', $wfPath
|
||||
}
|
||||
|
||||
# Job filter
|
||||
if ($Job) {
|
||||
$actArgs += '-j', $Job
|
||||
}
|
||||
|
||||
# Event file
|
||||
$eventMap = @{
|
||||
'pull_request' = Join-Path $CiLocalDir 'events\pull-request.json'
|
||||
'push' = Join-Path $CiLocalDir 'events\push.json'
|
||||
}
|
||||
$eventFile = $eventMap[$Event]
|
||||
if (Test-Path $eventFile) {
|
||||
$actArgs += '--eventpath', $eventFile
|
||||
}
|
||||
|
||||
# Dry run
|
||||
if ($DryRun) {
|
||||
$actArgs += '-n'
|
||||
}
|
||||
|
||||
# Verbose
|
||||
if ($ActVerbose) {
|
||||
$actArgs += '--verbose'
|
||||
}
|
||||
|
||||
# ---- Run act ----
|
||||
|
||||
Write-Host "[ci-local] act $($actArgs -join ' ')" -ForegroundColor Cyan
|
||||
Push-Location $RepoRoot
|
||||
try {
|
||||
act @actArgs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
140
devops/ci-local/run-act.sh
Normal file
140
devops/ci-local/run-act.sh
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# LOCAL ACT RUNNER (Bash)
|
||||
# =============================================================================
|
||||
# Thin wrapper around nektos/act for local CI execution.
|
||||
# For full-featured local CI (smoke, pr, module modes) use:
|
||||
# ./devops/scripts/local-ci.sh
|
||||
#
|
||||
# Usage:
|
||||
# ./devops/ci-local/run-act.sh [options]
|
||||
#
|
||||
# Options:
|
||||
# -w, --workflow <name> Workflow file (with or without .yml)
|
||||
# -j, --job <name> Run only a specific job
|
||||
# -l, --list List available jobs
|
||||
# -n, --dry-run Dry-run mode
|
||||
# -e, --event <type> Event type: pull_request (default) or push
|
||||
# -r, --rebuild Force rebuild CI image
|
||||
# -v, --verbose Verbose output
|
||||
# -h, --help Show this help
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
CI_IMAGE="stellaops-ci:local"
|
||||
CI_DOCKERFILE="$REPO_ROOT/devops/docker/Dockerfile.ci"
|
||||
|
||||
# Defaults
|
||||
WORKFLOW=""
|
||||
JOB=""
|
||||
LIST=false
|
||||
DRY_RUN=false
|
||||
EVENT="pull_request"
|
||||
REBUILD=false
|
||||
VERBOSE=false
|
||||
|
||||
usage() {
|
||||
head -25 "$0" | grep '^#' | sed 's/^# \?//'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ---- Parse args ----
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-w|--workflow) WORKFLOW="$2"; shift 2 ;;
|
||||
-j|--job) JOB="$2"; shift 2 ;;
|
||||
-l|--list) LIST=true; shift ;;
|
||||
-n|--dry-run) DRY_RUN=true; shift ;;
|
||||
-e|--event) EVENT="$2"; shift 2 ;;
|
||||
-r|--rebuild) REBUILD=true; shift ;;
|
||||
-v|--verbose) VERBOSE=true; shift ;;
|
||||
-h|--help) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---- Prerequisite checks ----
|
||||
|
||||
if ! docker info &>/dev/null; then
|
||||
echo "ERROR: Docker is not running." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v act &>/dev/null; then
|
||||
echo "ERROR: act is not installed." >&2
|
||||
echo " macOS: brew install act" >&2
|
||||
echo " Linux: curl -sSL https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash" >&2
|
||||
echo " Windows: choco install act-cli" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- Build CI image if needed ----
|
||||
|
||||
image_exists() {
|
||||
docker image inspect "$CI_IMAGE" &>/dev/null
|
||||
}
|
||||
|
||||
if [[ "$REBUILD" == "true" ]] || ! image_exists; then
|
||||
echo "[ci-local] Building CI image $CI_IMAGE ..."
|
||||
docker build -t "$CI_IMAGE" -f "$CI_DOCKERFILE" "$REPO_ROOT"
|
||||
fi
|
||||
|
||||
# ---- Ensure .env.local ----
|
||||
|
||||
ENV_LOCAL="$SCRIPT_DIR/.env.local"
|
||||
if [[ ! -f "$ENV_LOCAL" ]]; then
|
||||
echo "[ci-local] Creating .env.local from template ..."
|
||||
cp "$SCRIPT_DIR/.env.local.template" "$ENV_LOCAL"
|
||||
fi
|
||||
|
||||
# ---- Assemble act arguments ----
|
||||
|
||||
act_args=()
|
||||
|
||||
if [[ "$LIST" == "true" ]]; then
|
||||
cd "$REPO_ROOT"
|
||||
exec act -l
|
||||
fi
|
||||
|
||||
if [[ -n "$WORKFLOW" ]]; then
|
||||
wf_file="$WORKFLOW"
|
||||
[[ "$wf_file" != *.yml ]] && wf_file="${wf_file}.yml"
|
||||
wf_path="$REPO_ROOT/.gitea/workflows/$wf_file"
|
||||
if [[ ! -f "$wf_path" ]]; then
|
||||
echo "ERROR: Workflow not found: $wf_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
act_args+=(-W "$wf_path")
|
||||
fi
|
||||
|
||||
if [[ -n "$JOB" ]]; then
|
||||
act_args+=(-j "$JOB")
|
||||
fi
|
||||
|
||||
# Event file
|
||||
case "$EVENT" in
|
||||
pull_request) event_file="$SCRIPT_DIR/events/pull-request.json" ;;
|
||||
push) event_file="$SCRIPT_DIR/events/push.json" ;;
|
||||
*) echo "ERROR: Unknown event type: $EVENT" >&2; exit 1 ;;
|
||||
esac
|
||||
if [[ -f "$event_file" ]]; then
|
||||
act_args+=(--eventpath "$event_file")
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
act_args+=(-n)
|
||||
fi
|
||||
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
act_args+=(--verbose)
|
||||
fi
|
||||
|
||||
# ---- Run ----
|
||||
|
||||
echo "[ci-local] act ${act_args[*]}"
|
||||
cd "$REPO_ROOT"
|
||||
exec act "${act_args[@]}"
|
||||
Reference in New Issue
Block a user