Skip to content

Latest commit

Β 

History

History
1328 lines (1125 loc) Β· 50 KB

File metadata and controls

1328 lines (1125 loc) Β· 50 KB

GitHub Workflows Documentation Guide

This guide provides a comprehensive overview of all reusable GitHub workflows in this repository. Each workflow is designed to be called from your repository's workflows to standardize CI/CD processes across projects.

Table of Contents

  1. Allow Deploys
  2. Block Deploys
  3. Code Coverage (Kotlin)
  4. Component Build
  5. Component Deploy
  6. Component Initialize
  7. Component Test (Kotlin)
  8. Deploy Kotlin
  9. Deploy Kotlin V2 (Service Repo)
  10. Publish Tech Docs
  11. Pull Request Kotlin
  12. Pull Request React (Bun)
  13. Pull Request React (pnpm)
  14. Rollback
  15. SonarCloud Analysis
  16. Track Pending Release
  17. PR Digest

Allow Deploys

File: allow-deploys.yml
Purpose: Enables a specified workflow to allow deployments to proceed.

What it does:

  1. Enables the specified workflow using GitHub CLI
  2. Useful for re-enabling deployment workflows after they've been blocked

Inputs:

Input Required Default Description
workflow Yes - Workflow (filename or name) to allow, e.g. "deploy-production.yml"

Secrets:

Secret Required Description
ADMIN_PAT Yes GitHub PAT with workflow permissions

Example Usage:

jobs:
  allow-deploys:
    uses: monta-app/github-workflows/.github/workflows/allow-deploys.yml@main
    with:
      workflow: "deploy-production.yml"
    secrets:
      ADMIN_PAT: ${{ secrets.PAT }}

Block Deploys

File: block-deploys.yml
Purpose: Disables a specified workflow and cancels any in-progress jobs to prevent deployments.

What it does:

  1. Disables the specified workflow using GitHub CLI
  2. Waits for any in-progress jobs to start
  3. Cancels all in-progress jobs for the specified workflow
  4. Useful for preventing deployments during incidents or maintenance

Inputs:

Input Required Default Description
workflow Yes - Workflow (filename or name) to block, e.g. "deploy-production.yml"

Secrets:

Secret Required Description
ADMIN_PAT Yes GitHub PAT with workflow permissions

Example Usage:

jobs:
  block-deploys:
    uses: monta-app/github-workflows/.github/workflows/block-deploys.yml@main
    with:
      workflow: "deploy-production.yml"
    secrets:
      ADMIN_PAT: ${{ secrets.PAT }}

Code Coverage (Kotlin)

File: code-coverage-kotlin.yml
Purpose: Runs tests with code coverage reporting for Kotlin projects and pushes metrics to Prometheus.

What it does:

  1. Validates service name format (must be kebab-case)
  2. Connects to Tailscale VPN
  3. Sets up Java environment
  4. Runs tests with Kover coverage reporting
  5. Counts lines of code with cloc
  6. Pushes coverage metrics to Prometheus

Inputs:

Input Required Default Description
service-name Yes - Project name in kebab-case format (e.g., "my-service")
runner-size No "normal" Runner size: "normal" or "large"
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
java-version No "21" Java version to use
gradle-module No - Gradle module name for multi-module projects
kover-report-path No "build/reports/kover/report.xml" Path to Kover XML report
catalog-info-path No "catalog-info.yaml" Path to Backstage catalog file
cloc-source-path No "." Path to analyze for lines of code
cloc-exclude-dirs No "build,target,dist,node_modules,.gradle,.idea,out" Directories to exclude from LOC count
test-timeout-minutes No 30 Test timeout in minutes

Secrets:

Secret Required Description
TAILSCALE_AUTHKEY Yes Tailscale authentication key
GHL_USERNAME Yes GitHub username for Gradle dependencies
GHL_PASSWORD Yes GitHub token for Gradle dependencies

Example Usage:

jobs:
  code-coverage:
    uses: monta-app/github-workflows/.github/workflows/code-coverage-kotlin.yml@main
    with:
      service-name: "my-kotlin-service"
      java-version: "21"
    secrets:
      TAILSCALE_AUTHKEY: ${{ secrets.TAILSCALE_AUTHKEY }}
      GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
      GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}

Component Build

File: component-build.yml
Purpose: Builds an ARM64 Docker image and pushes it to Amazon ECR.

What it does:

  1. Updates Slack with build progress
  2. Builds a single-platform ARM64 Docker image
  3. Pushes the image to ECR tagged with the commit SHA and latest
  4. Notifies Slack of build completion

Inputs:

Input Required Default Description
stage Yes - Deployment stage: "dev", "staging", or "production"
service-name Yes - Display name (e.g., "OCPP Service")
service-emoji Yes - Emoji for Slack notifications
service-identifier Yes - Service identifier (e.g., "ocpp", "vehicle")
runner-size No "normal" Runner size: "normal" or "large"
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
region No "eu-west-1" AWS region
docker-file-name No "Dockerfile" Dockerfile name
additional-build-args No - Additional Docker build arguments
ecr-repository-name No - Custom ECR repository name
slack-message-id No - Existing Slack message ID to update

Secrets:

Secret Required Description
AWS_ACCOUNT_ID Yes AWS account ID
SLACK_APP_TOKEN Yes Slack app token
GHL_USERNAME No GitHub username (required for Kotlin)
GHL_PASSWORD No GitHub token (required for Kotlin)
SENTRY_AUTH_TOKEN No Sentry authentication token
AWS_CDN_ACCESS_KEY_ID No CDN access key ID
AWS_CDN_SECRET_ACCESS_KEY No CDN secret access key

Outputs:

  • image-tag: The SHA tag of the built image

Example Usage:

jobs:
  build:
    uses: monta-app/github-workflows/.github/workflows/component-build.yml@main
    with:
      stage: "staging"
      service-name: "Vehicle Service"
      service-emoji: "πŸš—"
      service-identifier: "vehicle"
    secrets:
      AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}

Component Deploy

File: component-deploy.yml
Purpose: Deploys a service by updating Kubernetes manifests in the kube-manifests repository.

What it does:

  1. Updates Slack with deployment progress
  2. Checks out the kube-manifests repository
  3. Updates image tag and metadata in values.yaml
  4. Updates deployment history in config.yaml
  5. Commits and pushes changes to trigger ArgoCD deployment
  6. Notifies Slack of deployment status

Inputs:

Input Required Default Description
stage Yes - Deployment stage: "dev", "staging", or "production"
service-name Yes - Display name (e.g., "OCPP Service")
service-emoji Yes - Emoji for Slack notifications
service-identifier Yes - Service identifier (e.g., "ocpp", "vehicle")
image-tag Yes - Docker image tag to deploy
slack-message-id No - Existing Slack message ID to update

Secrets:

Secret Required Description
MANIFEST_REPO_PAT Yes GitHub PAT for kube-manifests repo
SLACK_APP_TOKEN Yes Slack app token

Outputs:

  • slack-message-id: Slack message ID for updates

Example Usage:

jobs:
  deploy:
    uses: monta-app/github-workflows/.github/workflows/component-deploy.yml@main
    with:
      stage: "production"
      service-name: "Payment Service"
      service-emoji: "πŸ’³"
      service-identifier: "payment"
      image-tag: "abc123def456"
    secrets:
      MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}

Component Initialize

File: component-initialize.yml
Purpose: Initializes the CI/CD pipeline by creating a Slack notification message.

What it does:

  1. Creates an initial Slack message for tracking pipeline progress
  2. Returns the message ID for subsequent workflow updates

Inputs:

Input Required Default Description
service-name Yes - Display name (e.g., "OCPP Service")
service-emoji Yes - Emoji for Slack notifications

Secrets:

Secret Required Description
SLACK_APP_TOKEN Yes Slack app token

Outputs:

  • slack-message-id: Slack message ID for updates
  • slack-channel-id: Slack channel ID

Example Usage:

jobs:
  init:
    uses: monta-app/github-workflows/.github/workflows/component-initialize.yml@main
    with:
      service-name: "User Service"
      service-emoji: "πŸ‘€"
    secrets:
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}

Component Test (Kotlin)

File: component-test-kotlin.yml
Purpose: Runs tests for Kotlin projects with Gradle.

What it does:

  1. Sets up Java environment
  2. Runs Gradle tests
  3. Uploads test results as artifacts
  4. Updates Slack with test status

Inputs:

Input Required Default Description
runner-size No - Runner size for the job
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
service-name No - Display name for Slack
service-emoji No - Emoji for Slack
gradle-module No - Gradle module name
java-version No "21" Java version
slack-message-id No - Slack message ID to update

Secrets:

Secret Required Description
GHL_USERNAME Yes GitHub username
GHL_PASSWORD Yes GitHub token
SLACK_APP_TOKEN Yes Slack app token

Example Usage:

jobs:
  test:
    uses: monta-app/github-workflows/.github/workflows/component-test-kotlin.yml@main
    with:
      java-version: "21"
      service-name: "API Service"
      service-emoji: "πŸ”Œ"
    secrets:
      GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
      GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}

Component Test (Python)

File: component-test-python.yml
Purpose: Runs tests for Python projects using pytest with uv.

What it does:

  1. Sets up Python environment
  2. Optionally starts Docker Compose services if docker-compose-path is provided
  3. Waits for Docker containers to be healthy
  4. Creates .env file from TEST_ENV_FILE secret if provided
  5. Installs uv for fast dependency management
  6. Runs uv sync to install dependencies from pyproject.toml
  7. Executes pytest with HTML and JUnit reports
  8. Uploads test results as artifacts
  9. Updates Slack with test status

Inputs:

Input Required Default Description
runner-size No - Runner size for the job
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
service-name No - Display name for Slack
service-emoji No - Emoji for Slack
python-version No "3.13" Python version
test-directory No "tests" Directory containing test files
pytest-args No "" Additional pytest arguments
docker-compose-path No - File path of the docker compose file
slack-message-id No - Slack message ID to update

Secrets:

Secret Required Description
SLACK_APP_TOKEN Yes Slack app token
TEST_ENV_FILE No Environment variables for tests in .env format

Requirements:

  • Project must use pyproject.toml for dependency management
  • Test dependencies should be included in dev dependencies

Example Usage:

jobs:
  test:
    uses: monta-app/github-workflows/.github/workflows/component-test-python.yml@main
    with:
      python-version: "3.13"
      test-directory: "tests"
      service-name: "ML Service"
      service-emoji: "πŸ€–"
    secrets:
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
      TEST_ENV_FILE: ${{ secrets.TEST_ENV_FILE }}

Deploy Kotlin

File: deploy-kotlin.yml
Purpose: Complete CI/CD pipeline for Kotlin services including testing, building Docker images, and deploying to Kubernetes.

What it does:

  1. Create Release Tag (optional): Creates a release tag when triggered via workflow_dispatch and enable-release-tag is true
  2. Initialize: Sets up Slack notifications for the deployment process
  3. Test: Runs unit tests and validates code quality
  4. Build: Builds an ARM64 Docker image and pushes to ECR
  5. Deploy: Updates Kubernetes manifests for deployment
  6. Create Changelog (optional): Generates and publishes changelog to Slack and GitHub releases

Job Execution Flow:

The workflow uses conditional execution to ensure proper handling of optional features:

  • Create Release Tag: Only runs when enable-release-tag: true AND triggered via workflow_dispatch
  • Initialize: Always runs if release tag job succeeded or was skipped
  • Test & Build: Run in parallel after Initialize succeeds, using if: always() to handle skipped dependencies
  • Deploy: Only runs when ALL required jobs (Initialize, Test, Build) succeed
  • Create Changelog: Only runs when enable-changelog: true AND Deploy succeeds

This conditional logic ensures the workflow continues properly even when optional features are disabled.

Inputs:

Input Required Default Description
runner-size No "normal" Runner size: "normal" or "large"
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
stage Yes - Deployment stage: "dev", "staging", or "production"
service-name Yes - Human-readable service name (e.g., "Charging Service")
service-emoji Yes - Emoji to identify the service in Slack notifications
service-identifier Yes - Service identifier for ECR and Kubernetes (e.g., "charging")
gradle-module No - Gradle module name for multi-module projects
java-version No "21" Java version to use
gradle-args No "--no-daemon --parallel" Additional Gradle arguments
region No "eu-west-1" AWS region for deployment
docker-file-name No "Dockerfile" Name of the Dockerfile to build
additional-build-args No - Additional Docker build arguments
ecr-repository-name No - Override ECR repository name
enable-release-tag No false Enable automatic release tag creation on workflow_dispatch
release-tag-prefix No '' Optional prefix for release tag names. If provided, creates tags like 'prefix-YYYY-MM-DD-HH-MM'
enable-changelog No false Enable changelog generation after deployment
changelog-tag-pattern No - Regex pattern for matching tag patterns (group 1 should match version) - example: processor-(.*)
changelog-path-exclude-pattern No - Regex pattern for excluding file paths from changelog - example: ^gateway/

Secrets:

Secret Required Description
GHL_USERNAME Yes GitHub username for Gradle dependencies
GHL_PASSWORD Yes GitHub token for Gradle dependencies
AWS_ACCOUNT_ID Yes AWS Account ID for ECR and deployment
SLACK_APP_TOKEN Yes Slack token for notifications
MANIFEST_REPO_PAT Yes GitHub PAT for updating kube-manifests
SENTRY_AUTH_TOKEN No Sentry authentication token
AWS_CDN_ACCESS_KEY_ID No CDN access key for S3 access
AWS_CDN_SECRET_ACCESS_KEY No CDN secret key for S3 access

Example Usage:

Basic Deployment:

name: Deploy to Production
on:
  push:
    branches: [main]

jobs:
  deploy:
    uses: monta-app/github-workflows/.github/workflows/deploy-kotlin.yml@main
    with:
      stage: "production"
      service-name: "Charging Service"
      service-emoji: "⚑"
      service-identifier: "charging"
    secrets:
      GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
      GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
      AWS_ACCOUNT_ID: ${{ secrets.PRODUCTION_AWS_ACCOUNT_ID }}
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
      MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}

With Release Management:

name: Deploy Production with Release
on:
  push:
    tags: ['*']
  workflow_dispatch:

jobs:
  deploy:
    uses: monta-app/github-workflows/.github/workflows/deploy-kotlin.yml@main
    with:
      stage: "production"
      runner-size: "large"
      service-name: "Charging Service"
      service-emoji: "⚑"
      service-identifier: "charging"
      gradle-module: "charging-service"
      enable-release-tag: true    # Creates tag on manual trigger
      enable-changelog: true      # Generates changelog after deploy
      # Optional: Prefix for release tags
      release-tag-prefix: "charging"  # Creates tags like 'charging-2024-01-15-14-30'
      # Optional: Filter changelog by tag pattern (e.g., for monorepo)
      changelog-tag-pattern: "charging-(.*)"
      # Optional: Exclude paths from changelog (e.g., other services)
      changelog-path-exclude-pattern: "^(payment|vehicle)/"
    secrets:
      GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
      GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
      AWS_ACCOUNT_ID: ${{ secrets.PRODUCTION_AWS_ACCOUNT_ID }}
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
      MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
      SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

Deploy Python

File: deploy-python.yml
Purpose: Complete CI/CD pipeline for Python services (test β†’ build β†’ deploy).

What it does:

  1. Initializes Slack notification
  2. Runs Python tests using pytest and uv
  3. Builds an ARM64 Docker image
  4. Deploys to Kubernetes via manifest updates

Inputs:

Input Required Default Description
runner-size No "normal" Runner size ("normal" or "large")
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
stage Yes - Deployment stage (dev/staging/production)
service-name Yes - Service display name
service-emoji Yes - Service emoji
service-identifier Yes - Service identifier for K8s
region No "eu-west-1" AWS region
docker-file-name No "Dockerfile" Dockerfile name
additional-build-args No - Extra Docker build args
ecr-repository-name No - ECR repository override
docker-compose-path No - File path of the docker compose file

Secrets:

Secret Required Description
AWS_ACCOUNT_ID Yes AWS account for ECR
SLACK_APP_TOKEN Yes Slack app token
MANIFEST_REPO_PAT Yes GitHub PAT for manifest repo
SENTRY_AUTH_TOKEN No Sentry auth token
AWS_CDN_ACCESS_KEY_ID No CDN access key
AWS_CDN_SECRET_ACCESS_KEY No CDN secret key
TEST_ENV_FILE No Environment variables for tests in .env format

Requirements:

  • Python project with pyproject.toml
  • Tests in tests/ directory (configurable)
  • Dockerfile for containerization

Example Usage:

name: Deploy to Production
on:
  push:
    branches: [main]

jobs:
  deploy:
    uses: monta-app/github-workflows/.github/workflows/deploy-python.yml@main
    with:
      stage: "production"
      service-name: "ML Service"
      service-emoji: "πŸ€–"
      service-identifier: "ml-service"
      region: "eu-west-1"
    secrets:
      AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
      MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
      TEST_ENV_FILE: ${{ secrets.TEST_ENV_FILE }}

Deploy Kotlin V2 (Service Repo)

File: deploy-kotlin-v2.yml Purpose: Complete CI/CD pipeline for Kotlin services using service repository-based deployment pattern. This workflow follows the v2 pattern where helm values are stored in a separate service repository instead of the central kube-manifests repo.

What it does:

  1. Initialize: Sets up Slack notifications for the deployment process
  2. Test: Runs unit tests and validates code quality (optional, controlled by run-tests)
  3. Build: Builds an ARM64 Docker image and pushes to ECR
  4. Deploy: Updates helm values in the service repository and syncs with ArgoCD
  5. Update Service Profile (optional): Generates linkerd service profile from OpenAPI spec
  6. Create Release Tag (optional): Creates a release tag when enable-release-tag is true
  7. Create Changelog (optional): Generates and publishes changelog to Slack and GitHub releases

Differences from v1 (deploy-kotlin.yml):

  • Uses component-deploy-v2.yml which updates a separate service repository instead of kube-manifests
  • Adds helm-values-path input for flexible helm values directory structure
  • Adds repository-name input to specify the service repository name
  • Adds argocd-app-name input for custom ArgoCD application naming

Inputs:

Input Required Default Description
runner-size No "normal" Runner size: "normal" or "large"
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
stage Yes - Deployment stage: "dev", "staging", or "production"
service-name Yes - Human-readable service name (e.g., "Charging Service")
service-emoji Yes - Emoji to identify the service in Slack notifications
service-identifier Yes - Service identifier for ECR and Kubernetes (e.g., "charging")
gradle-module No - Gradle module name for multi-module projects
java-version No "21" Java version to use
gradle-args No "--no-daemon --parallel" Additional Gradle arguments
region No "eu-west-1" AWS region for deployment
docker-file-name No "Dockerfile" Name of the Dockerfile to build
additional-build-args No - Additional Docker build arguments
ecr-repository-name No - Override ECR repository name
run-tests No true Run tests before deployment
update-service-profile No true Generate linkerd service profile from OpenAPI spec
helm-values-path No helm/{service-identifier}/{stage}/app Path to helm values directory in service repo
repository-name No service-{service-identifier} Service repository name override
argocd-server No - ArgoCD server URL (e.g., argocd.monta.app)
argocd-app-name No {service-identifier} ArgoCD application name prefix (without stage)
argocd-sync-wait-seconds No 300 Timeout for ArgoCD sync monitoring
git-sha No - Git SHA to checkout and build from
enable-release-tag No false Enable automatic release tag creation
release-tag-prefix No '' Optional prefix for release tag names
enable-changelog No false Enable changelog generation
changelog-tag-pattern No - Regex pattern for tag matching (monorepo filtering)
changelog-path-exclude-pattern No - Regex pattern for excluding paths from changelog
comment-on-prs No true Comment on PRs with deployment info
comment-on-jira No true Comment on JIRA tickets with deployment info

Secrets:

Secret Required Description
GHL_USERNAME Yes GitHub username for Gradle dependencies
GHL_PASSWORD Yes GitHub token for Gradle dependencies
AWS_ACCOUNT_ID Yes AWS Account ID for ECR and deployment
SLACK_APP_TOKEN Yes Slack token for notifications
MANIFEST_REPO_PAT Yes GitHub PAT for updating service repository
SENTRY_AUTH_TOKEN No Sentry authentication token
AWS_CDN_ACCESS_KEY_ID No CDN access key for S3 access
AWS_CDN_SECRET_ACCESS_KEY No CDN secret key for S3 access
LOKALISE_TOKEN No Lokalise API token
LOKALISE_PROJECT_ID No Lokalise project ID
JIRA_EMAIL No Email for JIRA API authentication
JIRA_TOKEN No JIRA API token
CHANGELOG_GITHUB_TOKEN No GitHub token for changelog generation
ARGOCD_TOKEN No ArgoCD authentication token

Outputs:

  • slack-message-id: Slack message ID for notifications
  • stage: Stage that was deployed to
  • docker-image: Docker image repository path
  • previous-image-tag: Previous Docker image tag (for rollback)
  • image-tag: Deployed Docker image tag
  • deployment-start-time: ISO 8601 timestamp when deployment started
  • deployment-end-time: ISO 8601 timestamp when deployment completed
  • deployment-url: Direct URL to the application in ArgoCD UI

Example Usage:

Basic Deployment with Service Repo:

name: Deploy to Production
on:
  push:
    branches: [main]

jobs:
  deploy:
    uses: monta-app/github-workflows/.github/workflows/deploy-kotlin-v2.yml@main
    with:
      stage: "production"
      service-name: "Payment Service"
      service-emoji: "πŸ’³"
      service-identifier: "payment"
      repository-name: "service-payment"  # Service repo with helm values
    secrets:
      GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
      GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
      AWS_ACCOUNT_ID: ${{ secrets.PRODUCTION_AWS_ACCOUNT_ID }}
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
      MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}

With Custom Helm Path and ArgoCD Monitoring:

name: Deploy to Staging
on:
  workflow_dispatch:

jobs:
  deploy:
    uses: monta-app/github-workflows/.github/workflows/deploy-kotlin-v2.yml@main
    with:
      stage: "staging"
      service-name: "Auth Service"
      service-emoji: "πŸ”"
      service-identifier: "auth"
      repository-name: "service-auth"
      helm-values-path: "infrastructure/helm/staging/app"
      argocd-server: "argocd.monta.app"
      argocd-app-name: "auth"
      java-version: "21"
      gradle-module: "auth-api"
    secrets:
      GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
      GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
      AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
      MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
      ARGOCD_TOKEN: ${{ secrets.ARGOCD_TOKEN }}

Production with Release Management:

name: Deploy Production with Release
on:
  push:
    tags: ['*']
  workflow_dispatch:

jobs:
  deploy:
    uses: monta-app/github-workflows/.github/workflows/deploy-kotlin-v2.yml@main
    with:
      stage: "production"
      runner-size: "large"
      service-name: "Gateway Service"
      service-emoji: "πŸšͺ"
      service-identifier: "gateway"
      repository-name: "service-gateway"
      enable-release-tag: true
      release-tag-prefix: "gateway"
      enable-changelog: true
      changelog-tag-pattern: "gateway-(.*)"
      argocd-server: "argocd.monta.app"
      comment-on-prs: true
      comment-on-jira: true
    secrets:
      GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
      GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
      AWS_ACCOUNT_ID: ${{ secrets.PRODUCTION_AWS_ACCOUNT_ID }}
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
      MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
      SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
      CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }}
      JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }}
      ARGOCD_TOKEN: ${{ secrets.ARGOCD_TOKEN }}

Service Repository Structure:

The service repository should have the following structure:

service-{identifier}/
β”œβ”€β”€ helm/
β”‚   β”œβ”€β”€ {service-identifier}/
β”‚   β”‚   β”œβ”€β”€ dev/
β”‚   β”‚   β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”‚   β”‚   └── values.yaml
β”‚   β”‚   β”‚   └── cluster/
β”‚   β”‚   β”‚       └── config.yaml  # Optional: ArgoCD auto-sync config
β”‚   β”‚   β”œβ”€β”€ staging/
β”‚   β”‚   β”‚   └── app/
β”‚   β”‚   β”‚       └── values.yaml
β”‚   β”‚   └── production/
β”‚   β”‚       └── app/
β”‚   β”‚           └── values.yaml
└── ...

Or with custom path:

service-{identifier}/
β”œβ”€β”€ infrastructure/
β”‚   └── helm/
β”‚       β”œβ”€β”€ dev/
β”‚       β”‚   └── app/
β”‚       β”‚       └── values.yaml
β”‚       └── staging/
β”‚           └── app/
β”‚               └── values.yaml
└── ...

When to Use V2 vs V1:

  • Use V2 (deploy-kotlin-v2.yml) when:

    • Helm values should live in the service repository
    • You want tighter coupling between code and infrastructure
    • Each service manages its own deployment configuration
  • Use V1 (deploy-kotlin.yml) when:

    • Helm values are centralized in the kube-manifests repository
    • You prefer separation of concerns between code and infrastructure
    • Infrastructure team manages all deployment configurations

Publish Tech Docs

File: publish-tech-docs.yml
Purpose: Publishes technical documentation to Backstage via AWS S3.

What it does:

  1. Sets up Node.js and Python environments
  2. Installs techdocs-cli and mkdocs
  3. Generates documentation site
  4. Publishes to S3 bucket for Backstage

Inputs:

None - uses repository name as entity name

Secrets:

Secret Required Description
TECHDOCS_AWS_ACCESS_KEY_ID Yes AWS access key for S3
TECHDOCS_AWS_SECRET_ACCESS_KEY Yes AWS secret key for S3

Example Usage:

jobs:
  publish-docs:
    uses: monta-app/github-workflows/.github/workflows/publish-tech-docs.yml@main
    secrets:
      TECHDOCS_AWS_ACCESS_KEY_ID: ${{ secrets.TECHDOCS_AWS_ACCESS_KEY_ID }}
      TECHDOCS_AWS_SECRET_ACCESS_KEY: ${{ secrets.TECHDOCS_AWS_SECRET_ACCESS_KEY }}

Pull Request Kotlin

File: pull-request-kotlin.yml
Purpose: Validates Kotlin pull requests with linting, testing, and code coverage.

What it does:

  1. Validates PR title format
  2. Runs Kotlin linter (ktlint)
  3. Executes tests with Kover coverage
  4. Uploads results to SonarCloud
  5. Comments coverage report on PR
  6. Publishes test results

Inputs:

Input Required Default Description
runner-size No "normal" Runner size
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
java-version No "21" Java version
gradle-module No - Gradle module name
kover-report-path No "build/reports/kover/report.xml" Kover report path
test-timeout-minutes No 30 Test timeout
skip-sonar No false Skip SonarCloud analysis

Secrets:

Secret Required Description
GHL_USERNAME Yes GitHub username
GHL_PASSWORD Yes GitHub token
SONAR_TOKEN Yes SonarCloud token

Example Usage:

name: PR Validation
on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  validate:
    uses: monta-app/github-workflows/.github/workflows/pull-request-kotlin.yml@main
    with:
      java-version: "21"
    secrets:
      GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
      GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
      SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

Pull Request React (Bun)

File: pull-request-react-bun.yml
Purpose: Validates React/TypeScript pull requests using Bun runtime with code coverage reporting.

What it does:

  1. Validates PR title format
  2. Starts optional Docker Compose services if docker-compose-path is provided
  3. Waits for Docker containers to be healthy
  4. Installs dependencies with Bun
  5. Runs linter
  6. Runs tests (if configured)
  7. Builds the project
  8. Reports code coverage using LCOV format

Inputs:

Input Required Default Description
runner-size No "normal" Runner size
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
bun-version No "latest" Bun version
working-directory No "." Frontend code directory
build-timeout-minutes No 15 Build timeout
lint-command No "bun run lint" Lint command
build-command No "bun run build" Build command
test-command No "bun run test" Test command (optional)
docker-compose-path No - Path to Docker Compose file for services

Example Usage:

name: PR Validation
on:
  pull_request:

jobs:
  validate:
    uses: monta-app/github-workflows/.github/workflows/pull-request-bun.yml@main
    with:
      working-directory: "./frontend"
      lint-command: "bun run lint:all"
      docker-compose-path: "docker-compose.test.yml"

Code Coverage:

The workflow automatically reports code coverage if your project generates LCOV coverage files in the coverage/ directory. The coverage report is posted as a comment on the pull request.


Pull Request React (pnpm)

File: pull-request-react-pnpm.yml
Purpose: Validates React/TypeScript pull requests using pnpm package manager.

What it does:

  1. Validates PR title format
  2. Sets up pnpm and Node.js
  3. Installs dependencies
  4. Runs linter
  5. Runs tests (if configured)
  6. Builds the project

Inputs:

Input Required Default Description
runner-size No "normal" Runner size
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
node-version No "lts/jod" Node.js version
pnpm-version No "10" pnpm version
working-directory No "." Frontend code directory
build-timeout-minutes No 15 Build timeout
lint-command No "pnpm run lint" Lint command
build-command No "pnpm run build" Build command
test-command No "pnpm run test" Test command (optional)

Example Usage:

name: PR Validation
on:
  pull_request:

jobs:
  validate:
    uses: monta-app/github-workflows/.github/workflows/pull-request-react.yml@main
    with:
      node-version: "20"
      pnpm-version: "9"

Rollback

File: rollback.yml
Purpose: Rolls back a service deployment to a previous commit by updating Kubernetes manifests.

What it does:

  1. Sends a Slack notification about the rollback (if not in dry-run mode)
  2. Identifies the commit to roll back to (defaults to the previous commit)
  3. Updates the Kubernetes manifests in the kube-manifests repository
  4. Updates the image tag and revision in values.yaml
  5. Updates deployment history in config.yaml
  6. Optionally blocks further deployments by calling the block-deploys workflow

Inputs:

Input Required Default Description
commit-sha No HEAD^ Commit to roll back to (defaults to previous commit)
service-name Yes - Proper name for your service (e.g., "OCPP Service")
service-identifier Yes - Identifier of the service (e.g., "ocpp", "vehicle")
slack-channel Yes - Slack channel for rollback notifications
environment Yes - Deployment environment: "dev", "staging", or "production"
dry-run No false Set to true to show rollback without pushing (disables Slack)
block-workflow No - Name of workflow to block after rollback (e.g., "deploy-production.yml")

Secrets:

Secret Required Description
SLACK_WEBHOOK Yes Slack webhook URL for notifications
ADMIN_PAT Yes GitHub PAT for updating workflows and pushing to kube-manifests repo

Example Usage:

jobs:
  rollback:
    uses: monta-app/github-workflows/.github/workflows/rollback.yml@main
    with:
      service-name: "Charging Service"
      service-identifier: "charging"
      slack-channel: "#deploys"
      environment: "production"
      block-workflow: "deploy-production.yml"
    secrets:
      SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
      ADMIN_PAT: ${{ secrets.PAT }}

SonarCloud Analysis

File: sonar-cloud.yml
Purpose: Runs dedicated SonarCloud analysis for code quality metrics.

What it does:

  1. Checks out code with full history
  2. Sets up Java environment
  3. Caches SonarCloud packages
  4. Runs Kover coverage report
  5. Uploads analysis to SonarCloud

Inputs:

Input Required Default Description
runner-size No "normal" Runner size
use-blacksmith-runners No true Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64
java-version No "21" Java version
gradle-module No - Gradle module name

Secrets:

Secret Required Description
GHL_USERNAME Yes GitHub username
GHL_PASSWORD Yes GitHub token
SONAR_TOKEN Yes SonarCloud token

Example Usage:

jobs:
  sonarcloud:
    uses: monta-app/github-workflows/.github/workflows/sonar-cloud.yml@main
    with:
      java-version: "21"
    secrets:
      GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
      GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
      SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

Track Pending Release

File: track-pending-release.yml Purpose: For repositories where production releases are triggered manually (via workflow dispatch or tags), this workflow maintains a persistent GitHub issue that shows at a glance what commits are pending deployment. The issue title uses color-coded emoji indicators to quickly show deployment status in your issue list.

How it works:

  • Compares latest production release tag with main branch
  • Creates/updates a persistent GitHub issue with status emoji in title
  • Shows commit list with links and deployment instructions
  • Updates automatically on every push to main and after production deployments

Status Indicators:

  • 🟒 Green: Production is up-to-date (0 commits)
  • 🟑 Yellow: 1 commit pending
  • πŸ”΄ Red: Multiple commits pending

Inputs:

Input Required Default Description
main-branch No "main" Main branch name to track
production-workflow No "deploy-production.yml" Production deployment workflow filename

Secrets:

None required - uses GITHUB_TOKEN which is automatically provided by GitHub Actions.

Requirements:

  • Service must use enable-release-tag: true in production deployment workflow
  • Production deployment must create git tags (used to identify deployed version)

Example Usage:

Basic Setup (Caller Workflow):

name: Track Pending Release

on:
  push:
    branches:
      - main
    tags:
      - '*'
  workflow_dispatch:
  workflow_run:
    workflows: ["Deploy Production"]  # Must match your production workflow name
    types:
      - completed

jobs:
  track-release:
    uses: monta-app/github-workflows/.github/workflows/track-pending-release.yml@main
    with:
      main-branch: 'main'
      production-workflow: 'deploy-production.yml'

Important: The workflow_run trigger is required because enable-release-tag creates tags using GITHUB_TOKEN, which doesn't trigger other workflows. This ensures the tracker updates immediately after production deploys.

Custom Branch:

jobs:
  track-release:
    uses: monta-app/github-workflows/.github/workflows/track-pending-release.yml@main
    with:
      main-branch: 'master'
      production-workflow: 'deploy-prod.yml'

What the issue looks like:

  • Title: 🟒 Production Release Tracker (changes to 🟑 or πŸ”΄ based on pending commits)
  • Body: Shows latest deploy tag, current main commit, and list of pending commits with links
  • When up-to-date: Displays "Recently Released" confirmation message
  • When pending: Shows commit list with direct link to trigger deployment via workflow dispatch

Requirements:

  • Service must use enable-release-tag: true in production deployment workflow (creates the tags this workflow tracks)

Best Practices:

  1. Pin the issue for easy team access
  2. Include tag trigger (tags: ['*']) to update immediately after production deploys
  3. Pairs well with enable-changelog: true for automated release notes

Implementation Guide

Setting Up Secrets

Most workflows require organization or repository secrets. Add these in your repository settings:

  1. GitHub Tokens:

    • GHL_USERNAME: Your GitHub username
    • GHL_PASSWORD: A GitHub personal access token with read:packages scope
    • MANIFEST_REPO_PAT: PAT with write access to kube-manifests repo
  2. AWS Credentials:

    • AWS_ACCOUNT_ID: Your AWS account ID
    • TECHDOCS_AWS_ACCESS_KEY_ID: AWS access key for TechDocs S3
    • TECHDOCS_AWS_SECRET_ACCESS_KEY: AWS secret key for TechDocs S3
  3. Third-party Services:

    • SLACK_APP_TOKEN: Slack app-level token
    • SONAR_TOKEN: SonarCloud authentication token
    • TAILSCALE_AUTHKEY: Tailscale VPN authentication key
    • SENTRY_AUTH_TOKEN: Sentry authentication token (optional)

Repository Structure Requirements

  • Kotlin Projects:

    • Must have Gradle build configuration
    • Should include Kover plugin for coverage
    • SonarCloud configuration in build.gradle.kts
  • React Projects:

    • Package.json with lint, build, and test scripts
    • TypeScript configuration
    • Either Bun or pnpm as package manager
  • Kubernetes Deployments:

    • Manifests must be in kube-manifests repository
    • Structure: apps/<service-identifier>/<stage>/app/values.yaml
    • Config file: apps/<service-identifier>/<stage>/cluster/config.yaml

Best Practices

  1. Version Pinning: Always use a specific version tag (e.g., @main) when calling workflows
  2. Runner Sizing: Use "large" runners for resource-intensive builds
  3. Timeouts: Adjust timeouts based on your project's build/test duration
  4. Slack Integration: Use consistent service names and emojis across workflows
  5. Multi-Module Projects: Specify gradle-module for targeted builds
  6. Environment Stages: Use consistent stage names: "dev", "staging", "production"

Troubleshooting

Common Issues:

  1. Gradle Build Failures:

    • Ensure GHL_USERNAME and GHL_PASSWORD secrets are set
    • Check that GitHub packages are properly configured
  2. Docker Build Failures:

    • Verify Dockerfile exists at specified path
    • Check build arguments syntax
    • Ensure secrets are passed correctly
  3. Deployment Failures:

    • Verify manifest repository structure
    • Check PAT permissions for kube-manifests
    • Ensure service identifier matches manifest paths
  4. Coverage Report Issues:

    • Verify Kover plugin is configured
    • Check report path matches actual output
    • Ensure tests generate coverage data

For additional support, check the workflow logs in GitHub Actions or contact the platform team.


PR Digest

File: pr-digest.yml Purpose: Posts a daily Slack digest of open pull requests in the calling repository. Designed for high-volume repos (100+ open PRs): the message is AI-curated by default so the channel sees a focused triage view ("today's focus", critical PRs, area summary) instead of a flat list of every open PR. Falls back to deterministic oldest-first surfacing when AI is disabled or unavailable.

What it does:

  1. Fetches all open PRs in the calling repo via the GitHub CLI (no checkout needed).
  2. Filters out excluded authors (default: Dependabot, Renovate) and drafts (configurable).
  3. Buckets PRs as fresh, stale (β‰₯ stale-threshold-days), or critical (β‰₯ critical-threshold-days).
  4. Optional: sends a compact JSON of the data to the Anthropic API (Claude) and asks it to produce a 1–2 sentence headline plus up to N focus PRs with one-line "why this matters" notes.
  5. Builds a Slack Block Kit message with: header + counts, AI headline (if any), today's focus PRs, area summary, deep-link footer.
  6. Optional: posts a threaded follow-up with the full per-area PR breakdown.
  7. Posts via chat.postMessage using the existing SLACK_APP_TOKEN.

Inputs:

Input Required Default Description
slack-channel-id Yes - Slack channel ID (e.g. C0ALEGFRPU1). The bot behind SLACK_APP_TOKEN must be a member.
stale-threshold-days No 2 Days of inactivity before a PR counts as "stale" in the headline stats.
critical-threshold-days No 7 Days of inactivity for a PR to be surfaced individually as "critical" (about to be auto-closed).
max-prs-to-surface No 5 Max PRs to render by name in the "Today's focus" section.
include-drafts No false Include draft PRs in the digest.
exclude-authors No "dependabot[bot],renovate[bot]" Comma-separated GitHub logins to exclude.
use-ai-curation No true Use Claude to pick focus PRs and write the headline. Falls back gracefully if the call fails.
model No "claude-sonnet-4-6" Anthropic model identifier (model list). Use claude-haiku-4-5 for ~10Γ— cheaper, slightly less nuanced output.
post-thread-breakdown No true Reply in-thread with the full per-area PR breakdown.
title No repo name Header text for the Slack message.
empty-state No "silent" When no PRs are stale: "silent" (post nothing) or "celebrate" (post a πŸŽ‰ message).

Secrets:

Secret Required Description
SLACK_APP_TOKEN Yes Slack bot token with chat:write scope. Bot must be in the target channel.
ANTHROPIC_API_KEY If use-ai-curation: true Anthropic API key. If missing while AI curation is enabled, the AI step is skipped and the deterministic message ships.

Permissions:

The workflow declares its own pull-requests: read and contents: read. The caller does not need to pass any extra permissions.

Behaviour notes:

  • Volume-aware: deterministic mode hard-caps focus to max-prs-to-surface; the rest is summarised by area. The threaded breakdown carries the long tail so the main channel stays scannable even at 100+ open PRs.
  • Non-blocking AI: the Anthropic call has a 2-minute step timeout and continue-on-error: true. If it fails, the digest ships without the AI headline / focus, falling back to oldest-first.
  • Area detection: PRs are auto-grouped by the first label matching the canonical area set (hub, api-gateway, control-v2, portals, installers, control, ocpp-toolkit, storybook, dependencies, security). Anything else falls into other.
  • Cost (Sonnet, ~100 PRs): roughly ~$0.04 per run, ~$1/month for weekday-only scheduling.
  • Scheduling: GitHub Actions cron runs in UTC. For 9 AM Europe/Copenhagen year-round, use dual cron entries plus a small timezone guard in the caller (see the monorepo-typescript automation-pr-digest.yml for the canonical pattern).

Example Usage:

Basic β€” daily digest for a single channel:

name: PR digest

on:
  schedule:
    - cron: "7 7 * * 1-5"  # 09:07 CEST (summer)
    - cron: "7 8 * * 1-5"  # 09:07 CET  (winter)
  workflow_dispatch:

jobs:
  schedule-guard:
    runs-on: ubuntu-latest
    outputs:
      proceed: ${{ steps.check.outputs.proceed }}
    steps:
      - id: check
        run: |
          if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
            echo "proceed=true" >> "$GITHUB_OUTPUT"; exit 0
          fi
          LOCAL_HOUR=$(TZ=Europe/Copenhagen date +%H)
          if [[ "$LOCAL_HOUR" == "09" ]]; then
            echo "proceed=true" >> "$GITHUB_OUTPUT"
          else
            echo "proceed=false (local hour=$LOCAL_HOUR)"
            echo "proceed=false" >> "$GITHUB_OUTPUT"
          fi

  digest:
    needs: schedule-guard
    if: needs.schedule-guard.outputs.proceed == 'true'
    uses: monta-app/github-workflows/.github/workflows/pr-digest.yml@main
    with:
      slack-channel-id: C0ALEGFRPU1
      stale-threshold-days: 2
      critical-threshold-days: 7
      title: "my-repo β€” open PRs"
    secrets: inherit

Low-volume repo β€” flat list, no AI:

jobs:
  digest:
    uses: monta-app/github-workflows/.github/workflows/pr-digest.yml@main
    with:
      slack-channel-id: C0XXXXXXXXX
      use-ai-curation: false
      post-thread-breakdown: false
      empty-state: celebrate
    secrets:
      SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}

Manual testing:

This reusable workflow only declares workflow_call β€” it cannot be dispatched directly. Manual testing is done via the caller workflow in the consuming repository, which must declare workflow_dispatch itself (both example callers above do).

Once the caller is in place, trigger from the GitHub UI ("Actions" tab β†’ caller workflow β†’ "Run workflow") or via the CLI:

gh workflow run <caller-workflow-filename>.yml --repo monta-app/<your-repo>

The first manual run is the recommended way to verify channel membership, secrets, and AI model availability before relying on the schedule.