Skip to content

Release

Release #8

Workflow file for this run

# =============================================================================
# Release - Full CI/CD Pipeline
# =============================================================================
# Complete release pipeline: Build → Scan → Security Gate → Update Helm
#
# Triggers:
# - Push to main: Builds image + auto-deploys to TESTNET (via ArgoCD)
# - Manual: Builds image + auto-deploys to TESTNET (toggle with `deploy: false`)
#
# A single SHA-tagged image is produced and deployed to testnet. Promoting the
# same image to MAINNET is a separate manual action via deploy.yml (which
# requires the DEPLOY-TO-PROD confirmation).
#
# Pipeline:
# ┌─────────┐ ┌─────────┐ ┌───────────────┐ ┌──────────────┐
# │ Build │ ──► │ Scan │ ──► │ Security Gate │ ──► │ Update Helm │
# └─────────┘ └─────────┘ └───────────────┘ └──────────────┘
#
# Security Gate (after scan):
# - No CRITICAL vulns → passes automatically → deploy proceeds
# - CRITICAL vulns → gate fails → deploy is blocked
# - CRITICAL vulns + admin override → re-trigger manually with
# skip_security_gate: true
# The override actor and timestamp are permanently recorded in
# GitHub Actions workflow run history.
#
# ⚠️ MAINNET deploys are MANUAL ONLY via deploy.yml
# =============================================================================
name: Release
on:
workflow_dispatch:
inputs:
deploy:
description: "Deploy to TESTNET after build (uncheck to build only)"
required: false
default: true
type: boolean
skip_security_gate:
description: "Override security gate when CRITICAL vulnerabilities are found (admin only)"
required: false
default: false
type: boolean
push:
branches: [main]
paths:
- "pages/**"
- "ui/**"
- "lib/**"
- "toolkit/**"
- "configs/**"
- "nextjs/**"
- "public/**"
- "package.json"
- "package-lock.json"
- "yarn.lock"
- "Dockerfile"
- "next.config.*"
permissions:
contents: read
security-events: write
jobs:
# ---------------------------------------------------------------------------
# Resolve should_deploy
# ---------------------------------------------------------------------------
# Push to main → always deploy. Manual dispatch → honor the `deploy` input
# (default: true). Mainnet promotions never run here — use deploy.yml.
# ---------------------------------------------------------------------------
setup:
name: 🔧 Setup
runs-on: ubuntu-latest-m
outputs:
should_deploy: ${{ steps.env.outputs.should_deploy }}
steps:
- name: Determine should_deploy
id: env
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "should_deploy=${{ inputs.deploy }}" >> $GITHUB_OUTPUT
else
echo "should_deploy=true" >> $GITHUB_OUTPUT
fi
# ---------------------------------------------------------------------------
# Build Docker Image (centralized workflow)
# ---------------------------------------------------------------------------
build:
name: 🐳 Build
needs: [setup]
uses: ./.github/workflows/_build.yml
with:
image_name: rayls-explorer-frontend
environment: dev
push: true
secrets: inherit
# ---------------------------------------------------------------------------
# Security Scan (local reusable workflow)
# ---------------------------------------------------------------------------
scan:
name: 🔒 Scan
needs: [setup, build]
uses: ./.github/workflows/_security-scan.yml
with:
image_ref: registry.digitalocean.com/rayls-public-chain-registry/rayls-explorer-frontend:${{ needs.build.outputs.image_tag }}
secrets: inherit
# ---------------------------------------------------------------------------
# Security Gate (inlined — continue-on-error not supported on reusable workflow calls)
# - No CRITICAL vulns → ✅ green → deploy proceeds
# - CRITICAL vulns, no override → ❌ red → deploy blocked
# - CRITICAL vulns + override → ⚠️ orange → deploy proceeds (admin audit trail preserved)
# ---------------------------------------------------------------------------
gate:
name: 🛡️ Security Gate
runs-on: ubuntu-latest-m
needs: [scan]
continue-on-error: ${{ inputs.skip_security_gate || false }}
steps:
- name: Verify admin permission
if: inputs.skip_security_gate == true
env:
GH_TOKEN: ${{ github.token }}
run: |
PERMISSION=$(gh api repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission --jq '.role_name' 2>/dev/null || echo "none")
if [[ "$PERMISSION" != "admin" ]]; then
echo "❌ Only repository admins can override the security gate."
echo "Actor '${{ github.actor }}' has permission level: $PERMISSION"
exit 1
fi
echo "✅ Admin permission verified for ${{ github.actor }} (role: $PERMISSION)"
- name: Evaluate
run: |
if [[ "${{ needs.scan.outputs.has_critical }}" == "true" ]]; then
if [[ "${{ inputs.skip_security_gate }}" == "true" ]]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " ⚠️ SECURITY OVERRIDE — DEPLOY UNBLOCKED ⚠️"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " CRITICAL vulnerabilities are present in the image."
echo " An admin has explicitly overridden the security gate."
echo ""
echo " Override actor : ${{ github.actor }}"
echo " Audit trail : GitHub Actions → workflow run history"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 1
else
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " ❌ SECURITY GATE — DEPLOY BLOCKED ❌"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " CRITICAL vulnerabilities were found in the image."
echo " To unblock: re-trigger this workflow manually with"
echo " 'skip_security_gate: true'."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 1
fi
else
echo "✅ No CRITICAL vulnerabilities found. Proceeding with deploy."
fi
# ---------------------------------------------------------------------------
# Update Helm Chart (GitOps) (centralized workflow)
# ArgoCD monitors the Helm chart repository and automatically deploys changes.
# ---------------------------------------------------------------------------
update-helm:
name: 📦 Update Helm Chart
needs: [setup, build, scan, gate]
if: >-
always() &&
needs.build.result == 'success' &&
(needs.gate.result == 'success' || (needs.gate.result == 'failure' && (inputs.skip_security_gate || false) == true)) &&
needs.setup.outputs.should_deploy == 'true'
uses: ./.github/workflows/_update-helm.yml
with:
app_name: rayls-explorer-frontend
environment: dev
image_tag: ${{ needs.build.outputs.image_tag }}
values_file: blockscout-testnet/values.yaml
image_tag_yaml_path: .frontend.image.tag
secrets: inherit
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
summary:
name: 📋 Summary
runs-on: ubuntu-latest-m
needs: [setup, build, scan, gate, update-helm]
if: always()
steps:
- name: Pipeline Results
run: |
# Determine gate status
if [[ "${{ needs.gate.result }}" == "success" ]]; then
GATE_STATUS="✅ Passed (no critical vulns)"
elif [[ "${{ needs.gate.result }}" == "failure" && "${{ inputs.skip_security_gate }}" == "true" ]]; then
GATE_STATUS="⚠️ Override by ${{ github.actor }}"
elif [[ "${{ needs.gate.result }}" == "failure" ]]; then
GATE_STATUS="❌ Blocked — re-trigger with skip_security_gate: true to override"
else
GATE_STATUS="⏭️ Skipped"
fi
{
echo "## 📋 Release Pipeline"
echo ""
echo "| Stage | Status |"
echo "|-------|--------|"
echo "| Setup | ${{ needs.setup.result == 'success' && '✅' || '❌' }} |"
echo "| Build | ${{ needs.build.result == 'success' && '✅' || needs.build.result == 'skipped' && '⏭️' || '❌' }} |"
echo "| Scan | ${{ needs.scan.result == 'success' && '✅' || needs.scan.result == 'skipped' && '⏭️' || '❌' }} |"
echo "| Security Gate | $GATE_STATUS |"
echo "| Update Helm | ${{ needs.update-helm.result == 'success' && '✅' || needs.update-helm.result == 'skipped' && '⏭️' || needs.update-helm.result == 'cancelled' && '🚫' || '❌' }} |"
echo ""
echo "**Target:** \`testnet (blockscout-testnet/values.yaml)\`"
echo "**Image Tag:** \`${{ needs.build.outputs.image_tag }}\`"
echo ""
if [[ "${{ needs.update-helm.result }}" == "success" ]]; then
echo "🔄 **ArgoCD** will automatically detect and deploy the changes."
fi
} >> $GITHUB_STEP_SUMMARY