Add CI/CD pipelines for Forgejo/GitHub Actions: - build.yml - Main build pipeline with matrix builds - deploy-staging.yml - Automated staging deployment - deploy-prod.yml - Production deployment with rollback support - security-modes-test.yml - Security mode validation tests Add deployment artifacts: - docker-compose.staging.yml for staging environment - ROLLBACK.md with rollback procedures and playbooks Supports multi-environment deployment workflow with proper gates between staging and production.
449 lines
15 KiB
YAML
449 lines
15 KiB
YAML
name: CI Pipeline
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
push:
|
|
paths-ignore:
|
|
- 'docs/**'
|
|
- 'README.md'
|
|
- 'CHANGELOG.md'
|
|
- '.forgejo/ISSUE_TEMPLATE/**'
|
|
- '**/*.md'
|
|
pull_request:
|
|
paths-ignore:
|
|
- 'docs/**'
|
|
- 'README.md'
|
|
- 'CHANGELOG.md'
|
|
- '.forgejo/ISSUE_TEMPLATE/**'
|
|
- '**/*.md'
|
|
|
|
concurrency:
|
|
group: ${{ gitea.workflow }}-${{ gitea.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
security-events: write
|
|
actions: read
|
|
packages: write
|
|
|
|
env:
|
|
GO_VERSION: '1.25.0'
|
|
ZIG_VERSION: '0.15.2'
|
|
RSYNC_VERSION: '3.3.0'
|
|
|
|
jobs:
|
|
test:
|
|
name: Test
|
|
runs-on: self-hosted
|
|
timeout-minutes: 30
|
|
|
|
services:
|
|
redis:
|
|
image: redis:7
|
|
ports:
|
|
- 6379:6379
|
|
options: >-
|
|
--health-cmd "redis-cli ping"
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Go
|
|
run: |
|
|
REQUIRED_GO="1.25.0"
|
|
if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then
|
|
echo "Go ${REQUIRED_GO} already installed - skipping download"
|
|
else
|
|
echo "Installing Go ${REQUIRED_GO}..."
|
|
curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
|
|
export PATH="/usr/local/go/bin:$PATH"
|
|
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
|
echo "Go ${REQUIRED_GO} installed"
|
|
fi
|
|
go version
|
|
|
|
- name: Set up Zig
|
|
run: |
|
|
ZIG_VERSION="${{ env.ZIG_VERSION }}"
|
|
if command -v zig &> /dev/null && zig version | grep -q "${ZIG_VERSION}"; then
|
|
echo "Zig ${ZIG_VERSION} already installed - skipping download"
|
|
else
|
|
echo "Installing Zig ${ZIG_VERSION}..."
|
|
ZIG_DIR="/usr/local/zig-${ZIG_VERSION}"
|
|
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
curl -fsSL --retry 3 "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz
|
|
sudo mkdir -p "${ZIG_DIR}"
|
|
sudo tar -C "${ZIG_DIR}" --strip-components=1 -xJf /tmp/zig.tar.xz
|
|
sudo ln -sf "${ZIG_DIR}/zig" /usr/local/bin/zig
|
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
curl -fsSL --retry 3 "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-macos-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz
|
|
sudo mkdir -p "${ZIG_DIR}"
|
|
sudo tar -C "${ZIG_DIR}" --strip-components=1 -xJf /tmp/zig.tar.xz
|
|
sudo ln -sf "${ZIG_DIR}/zig" /usr/local/bin/zig
|
|
fi
|
|
rm -f /tmp/zig.tar.xz
|
|
echo "Zig ${ZIG_VERSION} installed"
|
|
fi
|
|
zig version
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
go mod download
|
|
sudo apt-get update
|
|
sudo apt-get install -y podman redis-tools build-essential autoconf automake libtool pkg-config musl-tools
|
|
|
|
- name: Build pinned rsync from official source (for CLI tests)
|
|
run: |
|
|
make -C cli build-rsync RSYNC_VERSION=${{ env.RSYNC_VERSION }}
|
|
|
|
- name: Verify dependencies
|
|
run: go mod verify
|
|
|
|
- name: Run tests
|
|
run: make test
|
|
|
|
- name: Test internal/queue package
|
|
run: go test -v -race -coverprofile=queue-coverage.out ./internal/queue/...
|
|
|
|
- name: Run comprehensive tests
|
|
run: make test-full
|
|
|
|
- name: Run linters
|
|
run: make lint
|
|
|
|
- name: Security lint checks
|
|
run: |
|
|
echo "=== Security Lint Checks ==="
|
|
echo "Checking for unsafe os.WriteFile usage..."
|
|
if grep -rn "os\.WriteFile" internal/ --include="*.go" | grep -v "_test.go" | grep -v "// fsync-exempt"; then
|
|
echo "ERROR: Found os.WriteFile calls. Use fileutil.WriteFileSafe() instead."
|
|
echo "Mark exemptions with '// fsync-exempt' comment"
|
|
exit 1
|
|
fi
|
|
echo "✓ No unsafe os.WriteFile calls found"
|
|
|
|
echo "Checking for O_NOFOLLOW in sensitive paths..."
|
|
if grep -rn "os\.OpenFile.*O_CREATE" internal/queue/ internal/crypto/ internal/experiment/ --include="*.go" | grep -v "OpenFileNoFollow" | grep -v "_test.go"; then
|
|
echo "WARNING: File open in sensitive dir may need O_NOFOLLOW"
|
|
fi
|
|
echo "✓ O_NOFOLLOW check complete"
|
|
|
|
- name: Generate coverage report
|
|
run: make test-coverage
|
|
|
|
dev-smoke:
|
|
name: Dev Compose Smoke Test
|
|
runs-on: self-hosted
|
|
needs: test
|
|
timeout-minutes: 20
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Run dev smoke test
|
|
run: make dev-smoke
|
|
|
|
security-scan:
|
|
name: Security Scan
|
|
runs-on: self-hosted
|
|
needs: test
|
|
timeout-minutes: 20
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Go
|
|
run: |
|
|
REQUIRED_GO="1.25.0"
|
|
if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then
|
|
echo "Go ${REQUIRED_GO} already installed"
|
|
else
|
|
echo "Installing Go ${REQUIRED_GO}..."
|
|
curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
|
|
export PATH="/usr/local/go/bin:$PATH"
|
|
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
|
echo "Go ${REQUIRED_GO} installed"
|
|
fi
|
|
go version
|
|
|
|
- name: Install security scanners
|
|
run: |
|
|
# Install gosec
|
|
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sudo sh -s -- -b /usr/local/bin latest
|
|
# Install nancy
|
|
curl -sfL https://raw.githubusercontent.com/sonatype-nexus-community/nancy/master/install.sh | sudo sh -s -- -b /usr/local/bin latest
|
|
# Install trivy
|
|
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin latest
|
|
|
|
- name: Go source security scan (gosec)
|
|
run: |
|
|
echo "Running gosec security scanner..."
|
|
gosec -fmt sarif -out gosec-results.sarif ./... || {
|
|
echo "gosec found issues - check gosec-results.sarif"
|
|
exit 1
|
|
}
|
|
continue-on-error: false
|
|
|
|
- name: Dependency audit (nancy)
|
|
run: |
|
|
echo "Running nancy dependency audit..."
|
|
go list -json -deps ./... | nancy sleuth --output sarif > nancy-results.sarif || {
|
|
echo "nancy found vulnerable dependencies"
|
|
cat nancy-results.sarif
|
|
exit 1
|
|
}
|
|
continue-on-error: false
|
|
|
|
- name: Upload security scan results
|
|
uses: actions/upload-artifact@v4
|
|
if: always()
|
|
with:
|
|
name: security-scan-results
|
|
path: |
|
|
gosec-results.sarif
|
|
nancy-results.sarif
|
|
retention-days: 30
|
|
|
|
test-scripts:
|
|
name: Test Scripts
|
|
runs-on: self-hosted
|
|
needs: test
|
|
timeout-minutes: 15
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y podman redis-tools bats
|
|
|
|
- name: Test scripts
|
|
run: |
|
|
chmod +x scripts/*.sh || true
|
|
chmod +x scripts/maintenance/*.sh || true
|
|
|
|
./scripts/verify_release.sh --help
|
|
./scripts/manage-artifacts.sh help
|
|
./scripts/track_performance.sh --help
|
|
./scripts/smoke-test.sh --help
|
|
|
|
test-native:
|
|
name: Test Native Libraries
|
|
runs-on: self-hosted
|
|
needs: native-build-matrix
|
|
timeout-minutes: 30
|
|
|
|
services:
|
|
redis:
|
|
image: redis:7-alpine
|
|
ports:
|
|
- 6379:6379
|
|
options: >-
|
|
--health-cmd "redis-cli ping"
|
|
--health-interval 5s
|
|
--health-timeout 3s
|
|
--health-retries 3
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Install cmake and build tools
|
|
run: |
|
|
echo "Installing cmake and build dependencies..."
|
|
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
if command -v apt-get &> /dev/null; then
|
|
sudo apt-get update
|
|
sudo apt-get install -y cmake zlib1g-dev build-essential
|
|
elif command -v yum &> /dev/null; then
|
|
sudo yum install -y cmake zlib-devel gcc-c++
|
|
fi
|
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
brew install cmake zlib
|
|
fi
|
|
which cmake
|
|
|
|
- name: Setup Go
|
|
run: |
|
|
REQUIRED_GO="1.25.0"
|
|
if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then
|
|
echo "Go ${REQUIRED_GO} already installed - skipping download"
|
|
else
|
|
echo "Installing Go ${REQUIRED_GO}..."
|
|
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
|
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
curl -sL "https://go.dev/dl/go${REQUIRED_GO}.darwin-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
|
|
fi
|
|
export PATH="/usr/local/go/bin:$PATH"
|
|
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
|
echo "Go ${REQUIRED_GO} installed"
|
|
fi
|
|
go version
|
|
|
|
- name: Build Native Libraries
|
|
run: |
|
|
echo "Building native C++ libraries..."
|
|
make native-build 2>&1 || {
|
|
echo ""
|
|
echo "Native build failed!"
|
|
echo ""
|
|
echo "Common causes:"
|
|
echo " 1. Missing cmake: Install with 'apt-get install cmake'"
|
|
echo " 2. Missing C++ compiler: Install with 'apt-get install build-essential'"
|
|
echo " 3. Missing zlib: Install with 'apt-get install zlib1g-dev'"
|
|
echo " 4. CMakeLists.txt not found: Ensure native/CMakeLists.txt exists"
|
|
echo ""
|
|
exit 1
|
|
}
|
|
echo "Native libraries built successfully"
|
|
|
|
- name: Test with Native Libraries
|
|
run: |
|
|
echo "Running tests WITH native libraries enabled..."
|
|
CGO_ENABLED=1 go test -tags native_libs -v ./tests/... || true
|
|
|
|
- name: Native Smoke Test
|
|
run: |
|
|
echo "Running native libraries smoke test..."
|
|
CGO_ENABLED=1 go test -tags native_libs ./tests/benchmarks/... -run TestNative || true
|
|
|
|
- name: Test Fallback (Go only)
|
|
run: |
|
|
echo "Running tests WITHOUT native libraries (Go fallback)..."
|
|
go test -v ./tests/... || true
|
|
|
|
- name: Run Benchmarks
|
|
run: |
|
|
echo "Running performance benchmarks..."
|
|
echo "=== Go Implementation ==="
|
|
go test -bench=. ./tests/benchmarks/ -benchmem || true
|
|
echo ""
|
|
echo "=== Native Implementation ==="
|
|
CGO_ENABLED=1 go test -tags native_libs -bench=. ./tests/benchmarks/ -benchmem || true
|
|
|
|
native-build-matrix:
|
|
name: Native Library Build Matrix
|
|
runs-on: self-hosted
|
|
needs: test
|
|
timeout-minutes: 30
|
|
strategy:
|
|
matrix:
|
|
build_config:
|
|
- name: "native"
|
|
tags: "native_libs"
|
|
cgo_enabled: "1"
|
|
build_native: "true"
|
|
- name: "cgo-only"
|
|
tags: ""
|
|
cgo_enabled: "1"
|
|
build_native: "false"
|
|
- name: "no-cgo"
|
|
tags: ""
|
|
cgo_enabled: "0"
|
|
build_native: "false"
|
|
fail-fast: false
|
|
|
|
services:
|
|
redis:
|
|
image: redis:7-alpine
|
|
ports:
|
|
- 6379:6379
|
|
options: >-
|
|
--health-cmd "redis-cli ping"
|
|
--health-interval 5s
|
|
--health-timeout 3s
|
|
--health-retries 3
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Install cmake and build tools
|
|
if: matrix.build_config.build_native == 'true'
|
|
run: |
|
|
echo "Installing cmake and build dependencies..."
|
|
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
if command -v apt-get &> /dev/null; then
|
|
sudo apt-get update
|
|
sudo apt-get install -y cmake zlib1g-dev build-essential
|
|
elif command -v yum &> /dev/null; then
|
|
sudo yum install -y cmake zlib-devel gcc-c++
|
|
fi
|
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
brew install cmake zlib
|
|
fi
|
|
which cmake
|
|
|
|
- name: Setup Go
|
|
run: |
|
|
REQUIRED_GO="1.25.0"
|
|
if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then
|
|
echo "Go ${REQUIRED_GO} already installed"
|
|
else
|
|
echo "Installing Go ${REQUIRED_GO}..."
|
|
curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
|
|
export PATH="/usr/local/go/bin:$PATH"
|
|
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
|
echo "Go ${REQUIRED_GO} installed"
|
|
fi
|
|
go version
|
|
|
|
- name: Build Native Libraries
|
|
if: matrix.build_config.build_native == 'true'
|
|
run: |
|
|
echo "Building native C++ libraries..."
|
|
make native-build 2>&1 || {
|
|
echo ""
|
|
echo "Native build failed!"
|
|
echo ""
|
|
echo "Common causes:"
|
|
echo " 1. Missing cmake: Install with 'apt-get install cmake'"
|
|
echo " 2. Missing C++ compiler: Install with 'apt-get install build-essential'"
|
|
echo " 3. Missing zlib: Install with 'apt-get install zlib1g-dev'"
|
|
echo " 4. CMakeLists.txt not found: Ensure native/CMakeLists.txt exists"
|
|
echo ""
|
|
exit 1
|
|
}
|
|
echo "Native libraries built successfully"
|
|
|
|
- name: Run tests - ${{ matrix.build_config.name }}
|
|
run: |
|
|
echo "=== Testing ${{ matrix.build_config.name }} build (CGO_ENABLED=${{ matrix.build_config.cgo_enabled }}, tags=${{ matrix.build_config.tags }}) ==="
|
|
CGO_ENABLED=${{ matrix.build_config.cgo_enabled }} go test -tags "${{ matrix.build_config.tags }}" -v ./tests/unit/... || true
|
|
|
|
- name: Run GPU matrix tests - ${{ matrix.build_config.name }}
|
|
run: |
|
|
echo "=== GPU Golden Test Matrix - ${{ matrix.build_config.name }} ==="
|
|
CGO_ENABLED=${{ matrix.build_config.cgo_enabled }} go test -tags "${{ matrix.build_config.tags }}" -v ./tests/unit/gpu/ -run TestGoldenGPUStatus || true
|
|
CGO_ENABLED=${{ matrix.build_config.cgo_enabled }} go test -tags "${{ matrix.build_config.tags }}" -v ./tests/unit/gpu/ -run TestBuildTagMatrix || true
|
|
|
|
build-trigger:
|
|
name: Trigger Build Workflow
|
|
runs-on: self-hosted
|
|
needs: [test, security-scan, native-build-matrix, dev-smoke, test-scripts]
|
|
if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
|
|
timeout-minutes: 5
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Trigger build workflow
|
|
run: |
|
|
echo "All CI checks passed. Build workflow will be triggered."
|
|
echo "SHA: ${{ gitea.sha }}"
|
|
echo "Ref: ${{ gitea.ref }}"
|
|
echo "Repository: ${{ gitea.repository }}"
|