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.
345 lines
11 KiB
YAML
345 lines
11 KiB
YAML
name: Build Pipeline
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
push:
|
|
branches:
|
|
- main
|
|
paths-ignore:
|
|
- 'docs/**'
|
|
- 'README.md'
|
|
- 'CHANGELOG.md'
|
|
- '.forgejo/ISSUE_TEMPLATE/**'
|
|
- '**/*.md'
|
|
|
|
concurrency:
|
|
group: build-${{ gitea.workflow }}-${{ gitea.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
packages: write
|
|
|
|
env:
|
|
GO_VERSION: '1.25.0'
|
|
ZIG_VERSION: '0.15.2'
|
|
RSYNC_VERSION: '3.3.0'
|
|
REGISTRY: ghcr.io
|
|
IMAGE_NAME: fetchml-worker
|
|
|
|
jobs:
|
|
build-binaries:
|
|
name: Build Binaries
|
|
runs-on: self-hosted
|
|
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
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- 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 build dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y podman build-essential autoconf automake libtool pkg-config musl-tools cmake zlib1g-dev
|
|
|
|
- name: Build pinned rsync from official source
|
|
run: |
|
|
make -C cli build-rsync RSYNC_VERSION=${{ env.RSYNC_VERSION }}
|
|
|
|
- name: Build SQLite for CLI
|
|
run: |
|
|
make -C cli build-sqlite
|
|
|
|
- name: Build CLI binary
|
|
run: |
|
|
cd cli && make tiny
|
|
|
|
- name: Build Native Libraries
|
|
if: matrix.build_config.build_native == 'true'
|
|
run: |
|
|
echo "Building native C++ libraries..."
|
|
make native-build 2>&1 || {
|
|
echo "Native build failed!"
|
|
exit 1
|
|
}
|
|
echo "Native libraries built successfully"
|
|
|
|
- name: Build Go binaries (${{ matrix.build_config.name }})
|
|
run: |
|
|
echo "Building Go binaries with CGO_ENABLED=${{ matrix.build_config.cgo_enabled }}, tags=${{ matrix.build_config.tags }}"
|
|
CGO_ENABLED=${{ matrix.build_config.cgo_enabled }} make build
|
|
# Tag the binaries with the build config name
|
|
mkdir -p "bin/${{ matrix.build_config.name }}"
|
|
cp bin/* "bin/${{ matrix.build_config.name }}/" 2>/dev/null || true
|
|
|
|
- name: Test binaries
|
|
run: |
|
|
./bin/worker --help || true
|
|
./cli/zig-out/bin/ml --help || true
|
|
ls -lh ./cli/zig-out/bin/ml
|
|
|
|
- name: Upload build artifacts
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: fetch_ml_binaries_${{ matrix.build_config.name }}
|
|
path: |
|
|
bin/
|
|
cli/zig-out/
|
|
retention-days: 30
|
|
|
|
build-docker:
|
|
name: Build Docker Images
|
|
runs-on: self-hosted
|
|
needs: build-binaries
|
|
timeout-minutes: 45
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Download build artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: fetch_ml_binaries_native
|
|
path: bin/
|
|
|
|
- name: Set up Docker
|
|
run: |
|
|
# Check Docker is available
|
|
docker --version || {
|
|
echo "Docker not available, using Podman"
|
|
sudo apt-get install -y podman
|
|
}
|
|
|
|
- name: Build Docker image
|
|
run: |
|
|
# Build the Docker image
|
|
docker build -f build/docker/simple.Dockerfile -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} .
|
|
|
|
- name: Generate image digest
|
|
run: |
|
|
docker inspect --format='{{index .RepoDigests 0}}' ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} > image-digest.txt
|
|
cat image-digest.txt
|
|
|
|
- name: Tag images
|
|
run: |
|
|
# Tag with commit SHA
|
|
docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
|
|
|
# If this is a version tag, tag with version
|
|
if [[ "${{ gitea.ref }}" == refs/tags/v* ]]; then
|
|
VERSION=$(echo "${{ gitea.ref }}" | sed 's/refs\/tags\///')
|
|
docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}
|
|
fi
|
|
|
|
- name: Container image scan (trivy)
|
|
run: |
|
|
# Scan the built image for vulnerabilities
|
|
trivy image --exit-code 1 --severity CRITICAL ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} || {
|
|
echo "CRITICAL vulnerabilities found in container image"
|
|
exit 1
|
|
}
|
|
|
|
- name: Save image digest artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: image-digest
|
|
path: image-digest.txt
|
|
retention-days: 30
|
|
|
|
# Note: In Forgejo, you may need to configure a local registry or use external push
|
|
# This section is a placeholder for registry push
|
|
- name: Push to registry (optional)
|
|
run: |
|
|
echo "Image built: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }}"
|
|
echo "Note: Registry push requires proper authentication setup in Forgejo"
|
|
# docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }}
|
|
# docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
|
|
|
sign-hipaa-config:
|
|
name: Sign HIPAA Config
|
|
runs-on: self-hosted
|
|
needs: build-binaries
|
|
timeout-minutes: 10
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install cosign (if available)
|
|
run: |
|
|
# Try to install cosign for signing
|
|
if command -v cosign &> /dev/null; then
|
|
echo "cosign already installed"
|
|
else
|
|
echo "Installing cosign..."
|
|
curl -sSfL https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 | sudo tee /usr/local/bin/cosign > /dev/null
|
|
sudo chmod +x /usr/local/bin/cosign || {
|
|
echo "cosign installation failed - signing will be skipped"
|
|
}
|
|
fi
|
|
cosign version || echo "cosign not available"
|
|
|
|
- name: Sign HIPAA config (placeholder)
|
|
run: |
|
|
echo "HIPAA config signing placeholder"
|
|
echo "To enable signing, configure COSIGN_KEY secret"
|
|
|
|
# Check if signing key is available
|
|
if [ -n "${{ secrets.COSIGN_KEY }}" ]; then
|
|
echo "Signing HIPAA config..."
|
|
# cosign sign-blob \
|
|
# --key ${{ secrets.COSIGN_KEY }} \
|
|
# deployments/configs/worker/docker-hipaa.yaml \
|
|
# > deployments/configs/worker/docker-hipaa.yaml.sig
|
|
echo "Signing would happen here with real cosign key"
|
|
else
|
|
echo "COSIGN_KEY not set - skipping HIPAA config signing"
|
|
# Create a placeholder signature file for now
|
|
echo "UNSIGNED_PLACEHOLDER" > deployments/configs/worker/docker-hipaa.yaml.sig
|
|
fi
|
|
|
|
- name: Upload HIPAA config signature
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: hipaa-config-signature
|
|
path: deployments/configs/worker/docker-hipaa.yaml.sig
|
|
retention-days: 30
|
|
|
|
provenance:
|
|
name: Generate SLSA Provenance
|
|
runs-on: self-hosted
|
|
needs: [build-binaries, build-docker]
|
|
timeout-minutes: 15
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Download build artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts/
|
|
|
|
- name: Generate provenance
|
|
run: |
|
|
echo "Generating SLSA provenance..."
|
|
|
|
# Create a basic SLSA provenance file
|
|
cat > provenance.json << 'EOF'
|
|
{
|
|
"_type": "https://in-toto.io/Statement/v0.1",
|
|
"predicateType": "https://slsa.dev/provenance/v0.2",
|
|
"subject": [
|
|
{
|
|
"name": "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}",
|
|
"digest": {
|
|
"sha256": "$(cat artifacts/image-digest/image-digest.txt | cut -d':' -f2 || echo 'unknown')"
|
|
}
|
|
}
|
|
],
|
|
"predicate": {
|
|
"builder": {
|
|
"id": "https://forgejo.example.com/jfraeysd/fetch_ml/.forgejo/workflows/build.yml"
|
|
},
|
|
"buildType": "https://forgejo.example.com/buildType/docker",
|
|
"invocation": {
|
|
"configSource": {
|
|
"uri": "https://forgejo.example.com/jfraeysd/fetch_ml",
|
|
"digest": {
|
|
"sha1": "${{ gitea.sha }}"
|
|
},
|
|
"entryPoint": ".forgejo/workflows/build.yml"
|
|
},
|
|
"parameters": {},
|
|
"environment": {
|
|
"gitea_actor": "${{ gitea.actor }}",
|
|
"gitea_ref": "${{ gitea.ref }}"
|
|
}
|
|
},
|
|
"metadata": {
|
|
"buildInvocationId": "${{ gitea.run_id }}",
|
|
"buildStartedOn": "$(date -Iseconds)",
|
|
"completeness": {
|
|
"parameters": false,
|
|
"environment": false,
|
|
"materials": false
|
|
}
|
|
},
|
|
"materials": [
|
|
{
|
|
"uri": "https://forgejo.example.com/jfraeysd/fetch_ml",
|
|
"digest": {
|
|
"sha1": "${{ gitea.sha }}"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
EOF
|
|
|
|
cat provenance.json
|
|
|
|
- name: Upload provenance
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: slsa-provenance
|
|
path: provenance.json
|
|
retention-days: 30
|