feat: port Rayls UI customizations from rayls-custom-logo branch (#3) #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================= | |
| # Release - Full CI/CD Pipeline | |
| # ============================================================================= | |
| # Complete release pipeline: Build → Scan → Security Gate → Update Helm | |
| # | |
| # Triggers: | |
| # - Push to main: Builds DEV image + auto-deploy to DEV (via ArgoCD) | |
| # - Manual: Choose environment (prod requires 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. | |
| # | |
| # ⚠️ PROD deploys are MANUAL ONLY via deploy.yml | |
| # ============================================================================= | |
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| description: "Target environment" | |
| required: true | |
| default: "dev" | |
| type: choice | |
| options: [dev, prod] | |
| deploy: | |
| description: "Deploy after build" | |
| 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: | |
| # --------------------------------------------------------------------------- | |
| # Determine Environment | |
| # --------------------------------------------------------------------------- | |
| setup: | |
| name: 🔧 Setup | |
| runs-on: ubuntu-latest | |
| outputs: | |
| environment: ${{ steps.env.outputs.environment }} | |
| should_deploy: ${{ steps.env.outputs.should_deploy }} | |
| steps: | |
| - name: Determine environment | |
| id: env | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "environment=${{ inputs.environment }}" >> $GITHUB_OUTPUT | |
| # Manual dispatch: deploy only if requested AND environment is dev | |
| # Prod deploys must go through deploy.yml with confirmation | |
| if [[ "${{ inputs.environment }}" == "prod" ]]; then | |
| echo "should_deploy=false" >> $GITHUB_OUTPUT | |
| echo "::warning::⚠️ PROD deploy disabled in release.yml. Use deploy.yml for production deployments." | |
| else | |
| echo "should_deploy=${{ inputs.deploy }}" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| # Push to main: build and auto-deploy to dev | |
| echo "environment=dev" >> $GITHUB_OUTPUT | |
| 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: ${{ needs.setup.outputs.environment }} | |
| 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 | |
| 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: ${{ needs.setup.outputs.environment }} | |
| image_tag: ${{ needs.build.outputs.image_tag }} | |
| helm_repo_path: blockscout-testnet | |
| secrets: inherit | |
| # --------------------------------------------------------------------------- | |
| # Summary | |
| # --------------------------------------------------------------------------- | |
| summary: | |
| name: 📋 Summary | |
| runs-on: ubuntu-latest | |
| 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 "**Environment:** \`${{ needs.setup.outputs.environment }}\`" | |
| 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 |