CollabOps

Full Production Pipeline

A complete CI/CD pipeline for a real production environment — from checkout to deployment and notification

A complete pipeline example ready for use in a real production environment. It includes all stages: checkout, lint, test, build, deploy, and notification.

Full Code

name: production-pipeline

# ─────────────────────────────────────────
# Trigger configuration
# ─────────────────────────────────────────
triggers:
  # Runs on main/develop push (only when src/ changes)
  push:
    branches: [main, develop]
    paths: [src/**, package.json, Dockerfile]

  # Runs on Change Requests targeting main
  change_request:
    branches: [main]

  # Scheduled daily execution at midnight
  schedule:
    branch: main
    cron: ["0 0 * * *"]

  # Manual execution available
  workflow_dispatch:

# ─────────────────────────────────────────
# Global environment variables
# ─────────────────────────────────────────
env:
  APP_NAME: my-app
  REGISTRY: "asia-northeast3-docker.pkg.dev"
  PROJECT_ID: "${{ vars.GCP_PROJECT_ID }}"

jobs:
  # ═══════════════════════════════════════
  # Phase 1: Source
  # ═══════════════════════════════════════

  checkout:
    phase: source
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
          fetch-depth: "0"         # Full history (for version extraction)

  # Version info + metadata collection (placed inside the build Job)
  # → Step Outputs can only be referenced within the same Job,
  #   so the metadata step is placed inside the build Job.

  # ═══════════════════════════════════════
  # Phase 2: Dependencies
  # ═══════════════════════════════════════

  install:
    phase: deps
    needs: [checkout]
    steps:
      - name: npm-ci
        image: node:18
        run: |
          cd /workspace/source
          npm ci                   # Clean install of dependencies

  # ═══════════════════════════════════════
  # Phase 3: Test (parallel execution)
  # ═══════════════════════════════════════

  lint:
    phase: test
    needs: [install]
    steps:
      - name: eslint
        image: node:18
        run: |
          cd /workspace/source
          npm run lint

      - name: typecheck
        image: node:18
        run: |
          cd /workspace/source
          npx tsc --noEmit

  test:
    phase: test
    needs: [install]               # Runs in parallel with lint
    steps:
      - name: unit-test
        id: test-result
        image: node:18
        run: |
          cd /workspace/source
          npm test -- --coverage 2>&1 | tee /tmp/test.txt

          # Extract coverage percentage
          COVERAGE=$(grep 'Statements' /tmp/test.txt | grep -oP '\d+\.\d+' | head -1 || echo "N/A")
          echo "coverage=${COVERAGE}%" >> $COLLABOPS_OUTPUT
        env:
          CI: "true"

  # ═══════════════════════════════════════
  # Phase 4: Build
  # ═══════════════════════════════════════

  # GCP authentication (required for build/deploy)
  auth:
    phase: build
    needs: [checkout]
    steps:
      - name: gcloud-auth
        uses: "collabops/gcloud-auth@v1"
        with:
          project-id: ${{ vars.GCP_PROJECT_ID }}
          credentials: ${{ secrets.GCP_SA_KEY }}

      - name: docker-auth
        uses: "collabops/gcloud-docker-auth@v1"
        with:
          registry: ${{ env.REGISTRY }}

  build:
    phase: build
    needs: [lint, test, auth]             # After tests pass + auth complete
    services:
      - docker                     # Enable Docker service
    steps:
      # Collect metadata (step output referenced within the same Job)
      - name: collect-meta
        id: meta
        image: node:18
        run: |
          cd /workspace/source

          # package.json version
          VERSION=$(node -p "require('./package.json').version")
          echo "version=${VERSION}" >> $COLLABOPS_OUTPUT

          # Short SHA (for image tag)
          SHORT_SHA=$(echo "${{ collabops.sha }}" | cut -c1-7)
          echo "short_sha=${SHORT_SHA}" >> $COLLABOPS_OUTPUT

          # Build timestamp
          echo "build_time=$(date -u +%Y%m%dT%H%M%S)" >> $COLLABOPS_OUTPUT

          echo "=== Build Metadata ==="
          echo "Version: ${VERSION}"
          echo "SHA: ${SHORT_SHA}"
          echo "Branch: ${{ collabops.ref_name }}"
          echo "Event: ${{ collabops.event_name }}"

      - name: build-push
        uses: "collabops/docker-build-push@v1"
        with:
          tags: |
            ${{ env.REGISTRY }}/${{ env.PROJECT_ID }}/docker/${{ env.APP_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.PROJECT_ID }}/docker/${{ env.APP_NAME }}:${{ steps.meta.outputs.version }}
            ${{ env.REGISTRY }}/${{ env.PROJECT_ID }}/docker/${{ env.APP_NAME }}:${{ steps.meta.outputs.short_sha }}
          build-args: |
            NODE_ENV=production
            APP_VERSION=${{ steps.meta.outputs.version }}
            BUILD_TIME=${{ steps.meta.outputs.build_time }}

  # ═══════════════════════════════════════
  # Phase 5: Deploy (main push only)
  # ═══════════════════════════════════════

  deploy:
    phase: deploy
    needs: [build]
    # Deploy to production only on main branch push
    # For CRs or other branches, only build is executed
    if: "collabops.ref == 'refs/heads/main' && collabops.event_name == 'push'"
    steps:
      # GKE cluster authentication
      - name: gke-setup
        uses: "collabops/gcloud-setup@v1"
        with:
          project-id: ${{ vars.GCP_PROJECT_ID }}
          cluster-name: ${{ vars.GKE_CLUSTER }}
          cluster-location: ${{ vars.GKE_REGION }}

      # Production deployment
      - name: rollout
        image: google/cloud-sdk:527.0.0-slim
        run: |
          SHORT_SHA=$(echo "${{ collabops.sha }}" | cut -c1-7)
          IMAGE="${{ env.REGISTRY }}/${{ env.PROJECT_ID }}/docker/${{ env.APP_NAME }}:${SHORT_SHA}"

          echo "Deploying ${IMAGE} to production..."

          # Update Deployment image
          kubectl set image deployment/${{ env.APP_NAME }} \
            app=${IMAGE} \
            -n production

          # Wait for rollout to complete (up to 5 minutes)
          kubectl rollout status deployment/${{ env.APP_NAME }} \
            -n production \
            --timeout=300s

          echo "Deploy complete!"

  # ═══════════════════════════════════════
  # Phase 6: Notification (always runs)
  # ═══════════════════════════════════════

  notify:
    needs: [deploy]
    if: "always()"                 # Always notify regardless of success/failure
    steps:
      - name: slack
        uses: "collabops/slack-notify@v1"
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          title: "Production Deploy"
          message: |
            App: ${{ env.APP_NAME }}
            Branch: ${{ collabops.ref_name }}
            Commit: ${{ collabops.sha }}
            Actor: ${{ collabops.actor }}
          color: good

Execution Flow

checkout
  ├── install (dependency installation)
  │     ├── lint (ESLint + TypeScript)         ← parallel
  │     └── test (unit tests + coverage)       ← parallel
  └── auth (GCP + Docker authentication)

              └── build (collect metadata → Docker build & push)
                    │     ※ runs after lint, test, auth all complete
                    └── deploy (GKE deployment, main push only)
                          └── notify (Slack notification, always)

Behavior by Trigger

TriggerExecution Scope
push (main)Full execution (test → build → deploy → notify)
push (develop)Up to test → build (deploy skipped)
change_requestUp to test → build (deploy skipped)
scheduleUp to test → build (deploy skipped)
workflow_dispatchUp to test → build (deploy skipped)

The if condition on the deploy Job checks collabops.event_name == 'push', so any non-push trigger (including schedule and workflow_dispatch) will only execute up to build and skip deployment.

Table of Contents