Skip to main content

Zyntem Fiscalization - CI/CD Pipeline

Company: Zyntem Product: Fiscalization by Zyntem Version: 1.0 Last Updated: 2025-10-30 Status: Active


Overview

This document describes the Continuous Integration and Continuous Deployment (CI/CD) pipeline for Zyntem's Fiscalization product. The pipeline automates testing, building, and deployment to ensure code quality and enable rapid, safe releases.

Pipeline Trigger: Every push to main and all pull requests


Pipeline Architecture

graph LR
A[Push to main / PR] --> B[Lint Job]
B --> C[Test Job]
C --> D[Build Job]
D --> D2[Contract Test Job]
D2 --> E{Branch?}
E -->|main| F[Deploy-Dev Job]
E -->|PR| G[No Deploy]
F --> H[Smoke Tests]
H --> I{Success?}
I -->|Yes| J[✓ Deployed]
I -->|No| K[Rollback & Alert]

Jobs Overview

JobRuntimeTriggersFail Conditions
Lint< 2 minAll pushesLinting errors, Spectral violations
Test< 5 minAfter lint passesTest failures, coverage < 80%
Build< 8 minAfter tests passBuild errors
Contract Test< 3 minAfter build passesAPI/schema mismatch
Deploy-Dev< 5 minPush to main onlyDeployment errors, smoke test failures

Total Pipeline Time: ~20 minutes (main branch) Total Pipeline Time: ~15 minutes (PRs, no deployment)


Job Details

1. Lint Job

Purpose: Enforce code style, catch basic errors, and validate the OpenAPI spec

Steps:

  1. Go Linting (golangci-lint)

    golangci-lint run --config .golangci.yml
    • Checks: gofmt, govet, ineffassign, unused, staticcheck
    • Config: .golangci.yml
  2. TypeScript Linting (ESLint + Prettier)

    cd apps/dashboard && npm run lint
    cd apps/docs && npm run lint
    • Checks: ESLint rules, Prettier formatting
    • Config: .eslintrc.js, .prettierrc
  3. OpenAPI Linting (Spectral)

    npx --yes @stoplight/spectral-cli@latest lint docs/openapi.yaml
    • Checks: OpenAPI spec against spectral:oas ruleset
    • Config: .spectral.yaml
  4. Terraform Linting

    terraform fmt -check -recursive infrastructure/
    • Checks: Terraform formatting

Failure Actions:

  • Pipeline stops
  • PR cannot be merged
  • Notification sent (if configured)

2. Test Job

Purpose: Run all unit tests and verify code coverage

Dependencies: Lint job must pass

Services:

  • PostgreSQL 15 (GitHub Actions service container)

Steps:

  1. Setup Services

    services:
    postgres:
    image: postgres:15
    env:
    POSTGRES_DB: fiscalization_test
    POSTGRES_USER: fiscalization
    POSTGRES_PASSWORD: test_password
    ports:
    - 5433:5432
  2. Go Tests

    go test -v -cover -coverprofile=coverage.out ./...
    go tool cover -func=coverage.out
    • Minimum coverage: 80%
  3. TypeScript Tests

    cd apps/dashboard && npm run test:coverage
    cd apps/docs && npm test
    • Minimum coverage: 75%
  4. Upload Coverage Reports

    - uses: actions/upload-artifact@v3
    with:
    name: coverage-reports
    path: |
    coverage.out
    apps/dashboard/coverage/

Failure Actions:

  • Pipeline stops
  • Coverage report available in artifacts
  • PR comment added with coverage summary

3. Build Job

Purpose: Build Docker images and verify they start successfully

Dependencies: Test job must pass

Steps:

  1. Build Go Services

    # Core API
    docker build -t ghcr.io/zyntem/fiscalization-api/core-api:sha-${GITHUB_SHA} apps/core-api/

    # Adapters (Epic 2+)
    docker build -t ghcr.io/zyntem/fiscalization-api/adapter-spain:sha-${GITHUB_SHA} apps/adapter-spain/
  2. Build TypeScript Apps

    # Dashboard
    cd apps/dashboard
    npm run build
    docker build -t ghcr.io/zyntem/fiscalization-api/dashboard:sha-${GITHUB_SHA} .

    # Docs
    cd apps/docs
    npm run build
    docker build -t ghcr.io/zyntem/fiscalization-api/docs:sha-${GITHUB_SHA} .
  3. Push to GitHub Container Registry

    docker push ghcr.io/zyntem/fiscalization-api/core-api:sha-${GITHUB_SHA}
    docker push ghcr.io/zyntem/fiscalization-api/dashboard:sha-${GITHUB_SHA}
    # ... (all images)

Failure Actions:

  • Pipeline stops
  • Build logs available
  • PR comment added with build errors

4. Deploy-Dev Job

Purpose: Deploy to development environment and run smoke tests

Dependencies: Build job must pass

Trigger: Push to main branch only (not PRs)

Authentication: Workload Identity Federation (keyless)

Steps:

  1. Authenticate to GCP

    - uses: google-github-actions/auth@v1
    with:
    workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
    service_account: github-actions@zyntem-dev.iam.gserviceaccount.com
  2. Deploy to Cloud Run

    # Core API
    gcloud run deploy core-api \
    --image ghcr.io/zyntem/fiscalization-api/core-api:sha-${GITHUB_SHA} \
    --region europe-west1 \
    --platform managed

    # Dashboard
    gcloud run deploy dashboard \
    --image ghcr.io/zyntem/fiscalization-api/dashboard:sha-${GITHUB_SHA} \
    --region europe-west1 \
    --platform managed
  3. Run Smoke Tests

    # Health check
    curl -f https://core-api-dev-zyntem.run.app/health || exit 1

    # Basic API test
    curl -f -H "Authorization: Bearer $TEST_API_KEY" \
    https://core-api-dev-zyntem.run.app/v1/locations || exit 1
  4. Send Notification (optional)

    - uses: slackapi/slack-github-action@v1
    if: failure()
    with:
    payload: |
    {
    "text": "Deployment to dev failed: ${{ github.event.head_commit.message }}"
    }

Failure Actions:

  • Automatic rollback triggered (Story 1.10)
  • Slack notification sent
  • Issue created (optional)

5. Contract Test Job

Purpose: Validate API responses against the OpenAPI specification

Dependencies: Build job must pass

Steps:

  1. Install Schemathesis

    pip install schemathesis
  2. Run Contract Tests

    schemathesis run http://localhost:8080/docs/openapi.yaml \
    --checks all \
    --max-response-time 10000 \
    --hypothesis-max-examples 50 \
    --junit-xml schemathesis-report.xml
    • Phases: examples, coverage, fuzzing
    • Tests paths: /health, /version, /v1/accounts
  3. Upload Report

    - uses: actions/upload-artifact@v3
    with:
    name: schemathesis-report
    path: schemathesis-report.xml

Failure Actions:

  • Pipeline stops
  • JUnit XML report available in artifacts
  • Indicates API response/schema mismatch

Workload Identity Federation Setup

Purpose: Keyless authentication to GCP (no service account keys)

Setup Steps

  1. Create Workload Identity Pool

    gcloud iam workload-identity-pools create github-actions-pool \
    --location="global" \
    --display-name="GitHub Actions Pool"

    gcloud iam workload-identity-pools providers create-oidc github-actions-provider \
    --location="global" \
    --workload-identity-pool="github-actions-pool" \
    --issuer-uri="https://token.actions.githubusercontent.com" \
    --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
    --attribute-condition="assertion.repository_owner=='yourusername'"
  2. Create Service Account

    gcloud iam service-accounts create github-actions \
    --display-name="GitHub Actions Deployment"

    # Grant roles
    gcloud projects add-iam-policy-binding zyntem-dev \
    --member="serviceAccount:github-actions@zyntem-dev.iam.gserviceaccount.com" \
    --role="roles/run.admin"

    gcloud projects add-iam-policy-binding zyntem-dev \
    --member="serviceAccount:github-actions@zyntem-dev.iam.gserviceaccount.com" \
    --role="roles/artifactregistry.writer"
  3. Allow GitHub Actions to Impersonate

    gcloud iam service-accounts add-iam-policy-binding \
    github-actions@zyntem-dev.iam.gserviceaccount.com \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-actions-pool/attribute.repository/yourusername/fiscalization"
  4. Add GitHub Secrets

    • GCP_PROJECT_ID: zyntem-dev
    • GCP_WORKLOAD_IDENTITY_PROVIDER: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider

Environment Secrets & Variables

GitHub Secrets

Secret NameDescriptionExample
GCP_PROJECT_IDGCP project IDzyntem-dev
GCP_WORKLOAD_IDENTITY_PROVIDERWorkload Identity Providerprojects/123.../providers/github-actions-provider
TEST_API_KEYAPI key for smoke testsfsk_test_...

GitHub Variables

Variable NameDescriptionExample
DEV_CLOUD_RUN_REGIONCloud Run regioneurope-west1

Pipeline Status Badge

Add to main README:

![CI Status](https://github.com/yourusername/fiscalization/actions/workflows/ci.yml/badge.svg)

Local Testing

Run the same checks locally before pushing:

1. Lint

# Go
golangci-lint run

# TypeScript
npm run lint

2. Test

# All tests
make test

# With coverage
make test-coverage

3. Build

# Build all services
make build

# Or individual services
docker build -t core-api apps/core-api/

Manual Deployment (Fallback)

If CI/CD fails, use manual deployment script:

# Deploy specific service
./scripts/deploy-dev.sh core-api

# Deploy all services
./scripts/deploy-dev.sh all

Script: scripts/deploy-dev.sh

#!/bin/bash
set -e

SERVICE=$1
REGION="europe-west1"
PROJECT="zyntem-dev"

if [ "$SERVICE" == "all" ]; then
SERVICES=("core-api" "dashboard")
else
SERVICES=("$SERVICE")
fi

for svc in "${SERVICES[@]}"; do
echo "Deploying $svc..."
gcloud run deploy $svc \
--source apps/$svc \
--region $REGION \
--project $PROJECT \
--platform managed
done

echo "✓ Deployment complete"

Troubleshooting

Pipeline Fails on Lint

Symptom: golangci-lint: command not found

Solution:

# Install locally
brew install golangci-lint

# Or use Docker
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:latest golangci-lint run

Tests Pass Locally But Fail in CI

Symptom: Tests fail due to environment differences

Solution:

# Match CI environment with Docker
docker-compose -f docker-compose.test.yml up

Deployment Authentication Fails

Symptom: ERROR: (gcloud.run.deploy) User does not have permission to access project

Solution:

# Verify Workload Identity setup
gcloud iam service-accounts get-iam-policy github-actions@zyntem-dev.iam.gserviceaccount.com

# Check GitHub secrets are set correctly
# Settings → Secrets and variables → Actions

Smoke Tests Fail

Symptom: Health check returns 503

Solution:

# Check Cloud Run logs
gcloud run services logs read core-api --region europe-west1 --limit 50

# Verify service is running
gcloud run services describe core-api --region europe-west1

Future Enhancements (Epic 3+)

  • SDK Publishing: Automate SDK releases to NPM, PyPI, etc. (Story 3.3a)
  • Performance Testing: Load testing in staging
  • Security Scanning: Trivy/Snyk integration
  • Deploy to Staging: Separate staging environment

Additional Resources