Co-Authored-By: Codex <noreply@openai.com> Signed-off-by: James Maes <james@kof22.com>
768 lines
39 KiB
Markdown
768 lines
39 KiB
Markdown
# Munitor
|
|
|
|
Latin: *munitor* (fortifier, protector) -- KofTwentyTwo's unified CircleCI orb for building, testing, and deploying all repositories. Pipeline changes happen in one place instead of 80+ repos.
|
|
|
|
| | |
|
|
| --- | --- |
|
|
| **Tier** | Internal until classified -- listed under *Awaiting classification* in the KofTwentyTwo standards [MAINTAINERS.md](https://github.com/KofTwentyTwo/standards/blob/main/MAINTAINERS.md); Product-tier requirements are being adopted |
|
|
| **Standards** | Targets [KofTwentyTwo standards](https://github.com/KofTwentyTwo/standards) 1.0.0 |
|
|
| **Orb** | [`kof22/munitor`](https://circleci.com/developer/orbs/orb/kof22/munitor), latest stable `0.3.11` |
|
|
| **Security** | [SECURITY.md](SECURITY.md) |
|
|
| **Contributing** | [CONTRIBUTING.md](CONTRIBUTING.md) |
|
|
|
|
## How It Works
|
|
|
|
Munitor uses CircleCI's [dynamic configuration](https://circleci.com/docs/dynamic-config/) to generate your full CI/CD pipeline at runtime. Each repo has a minimal `.circleci/config.yml` that triggers Munitor, and a `.munitor.yml` that declares what your project needs. Munitor reads your config, selects the right template, and generates a complete CircleCI workflow.
|
|
|
|
```
|
|
repo/.munitor.yml --> Munitor setup job --> Generated pipeline --> Build/Test/Deploy
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### 1. Add `.circleci/config.yml`
|
|
|
|
This file is identical across all repos:
|
|
|
|
```yaml
|
|
version: 2.1
|
|
setup: true
|
|
orbs:
|
|
munitor: kof22/munitor@0.3
|
|
workflows:
|
|
setup:
|
|
jobs:
|
|
- munitor/generate_pipeline
|
|
```
|
|
|
|
### 2. Add `.munitor.yml`
|
|
|
|
Pick your pipeline type and create the config in your repo root. See [Pipeline Types](#pipeline-types) below.
|
|
|
|
### 3. Ensure branch naming follows GitFlow
|
|
|
|
Munitor validates branch names and will fail on non-conforming names.
|
|
|
|
| Pattern | Purpose |
|
|
|---------|---------|
|
|
| `main` | Production releases |
|
|
| `develop` | Integration |
|
|
| `staging` | Staging environment branch |
|
|
| `feature/<name>` | Feature work |
|
|
| `release/<version>` | Release prep |
|
|
| `hotfix/<name>` | Production fixes |
|
|
|
|
## Optional docs-only Java checks
|
|
|
|
The new `java-webapp` docs-only path is opt-in and must be adopted **after** the
|
|
aggregate required check is published and verified. Do not enable it against an
|
|
older orb or while individual build jobs remain required:
|
|
|
|
```yaml
|
|
ci:
|
|
docs_only: true
|
|
```
|
|
|
|
Only changes confined to root `README.md`, `CHANGELOG.md`, `CONTRIBUTING.md`, or
|
|
Markdown files under `docs/` (including nested directories) qualify. Feature and
|
|
hotfix branches use their complete diff from the merge base with `main`; `main`
|
|
uses its first-parent diff. Tags, release and other branches, shallow history,
|
|
failed comparisons, empty diffs, and any uncertain classification keep the full
|
|
pipeline. The docs path retains secret scanning and emits the aggregate gate;
|
|
it does not build or deploy an image.
|
|
|
|
Migration order: publish the gate-capable orb, adopt it with docs-only disabled,
|
|
and verify the real GitHub context `ci/circleci: ci-ready` on a consumer run. Then
|
|
replace that repository's required-check list with the verified aggregate gate,
|
|
preserving all other branch rules. A single adoption PR may update both orb pins
|
|
and enable `ci.docs_only`: those config changes force full CI. Verify the gate
|
|
and migrate required checks before merging it or opening a docs-only PR. Munitor's
|
|
own native no-op gate reports `ci/circleci: lint-pack/ci-ready`. Both features
|
|
ship in 0.3.11. Verify each consumer's actual successful context before changing
|
|
its rules; see [the adoption record](docs/PLAN-agile-ci.md).
|
|
|
|
Use native [Website-Backend Insights](https://app.circleci.com/insights/github/KofTwentyTwo/Website-Backend/workflows),
|
|
[PR timings](https://app.circleci.com/insights/github/KofTwentyTwo/Website-Backend/workflows/pr-checks),
|
|
and [Test Insights](https://app.circleci.com/insights/github/KofTwentyTwo/Website-Backend/workflows/pr-checks/tests)
|
|
to measure results. Compare matching branches/windows and track the `docs-checks`
|
|
median separately from full builds. The historical local report
|
|
`C:/Users/james/.cache/ci-insights-report.md` ends October 17 and does not establish
|
|
the speed of this change. No additional reporting job is needed.
|
|
|
|
Native auto-cancel is enabled for Website-Backend, whose default `main` is exempt.
|
|
It remains disabled for Munitor: its default is `develop`, so `main` release work
|
|
would not receive that exemption.
|
|
|
|
## Prerequisites
|
|
|
|
1. **CircleCI project**: Add your repo at https://app.circleci.com
|
|
2. **Enable dynamic config**: Project Settings > Advanced > "Enable dynamic config using setup workflows"
|
|
3. **Allow uncertified orbs**: Organization Settings > Security > "Allow uncertified orbs"
|
|
4. **CircleCI contexts** (Organization Settings > Contexts):
|
|
|
|
| Context | Secrets | Used By |
|
|
|---------|---------|---------|
|
|
| `ghcr` | `GHCR_TOKEN`, `GHCR_USERNAME` | Docker push to GitHub Container Registry |
|
|
| `github` | `GITHUB_TOKEN` | Git operations, CD repo updates, SBOM, GitHub Releases |
|
|
| `sonarcloud` | `SONAR_TOKEN` | SonarCloud analysis |
|
|
| `nvd` | `NVD_API_KEY` | OWASP dependency check (Java only, optional) |
|
|
|
|
## Adopting in another app
|
|
|
|
Munitor is a public CircleCI orb: any CircleCI organization can use it without forking. This checklist covers what a new consumer needs, for example a Java/Maven web service. A QQQ application should also read the [QQQ adoption checklist](https://github.com/QRun-IO/qqq/blob/develop/docs/migration/adoption-checklist.adoc) (section 7 covers release tooling).
|
|
|
|
**Version.** Use `kof22/munitor@0.3.11` (or `@0.3` to float on the 0.3 line) in `.circleci/config.yml`, and the same value as `orb_version` in `.munitor.yml`. Keep the setup orb and the generated pipeline on the same line.
|
|
|
|
### 1. Repository files
|
|
|
|
- `.circleci/config.yml`: the setup config from [Quick Start](#quick-start), pinned to `kof22/munitor@0.3.11`.
|
|
- `.munitor.yml`: pick a [pipeline type](#pipeline-types). A Java service is `java-webapp`. KofTwentyTwo's Website-Backend is a working example; a generalized version:
|
|
|
|
```yaml
|
|
pipeline: java-webapp
|
|
orb_version: "0.3.11"
|
|
image_name: my-service
|
|
java_version: "21"
|
|
docker:
|
|
registry: ghcr.io/my-org # the org default is ghcr.io/KofTwentyTwo -- always set this
|
|
cd:
|
|
repo: my-org/my-gitops-repo # omit to skip GitOps write-back
|
|
format: kustomize # default is helm (image.tag in values.yaml)
|
|
path: apps/my-service/kustomization.yaml # optional explicit file in a consolidated repo
|
|
production_only: true # only main updates the GitOps repo
|
|
supplemental_images: # optional extra images built from the same commit and version
|
|
- name: migrations
|
|
dockerfile: docker/Dockerfile.migrations
|
|
cd:
|
|
image_name: ghcr.io/my-org/my-service-migrations
|
|
health:
|
|
path: /health
|
|
port: "8000"
|
|
db: "true" # start a PostgreSQL sidecar for the health gate
|
|
migrations: migrations # run the <image>-migrations image against it first
|
|
smoke_paths: [/health]
|
|
sonar:
|
|
project_key: my-org_my-service # omit to skip SonarCloud
|
|
contexts:
|
|
registry: ghcr
|
|
github: github
|
|
sonar: sonarcloud # required only with sonar.project_key
|
|
nvd: nvd # optional
|
|
org: # organization defaults (see Known limitations)
|
|
ci_email: ci@example.com
|
|
ci_name: My CI
|
|
```
|
|
|
|
- A health endpoint that returns HTTP 200 within 30 seconds of container start (see [Health Check Requirement](#health-check-requirement)).
|
|
- A Dockerfile only for `node-webapp` (`docker.use_repo_dockerfile: true`) or for `supplemental_images`; `java-webapp` and `node-api` generate theirs.
|
|
|
|
### 2. CircleCI project settings
|
|
|
|
See [Prerequisites](#prerequisites): enable dynamic config (setup workflows) and allow uncertified orbs in the consuming organization.
|
|
|
|
### 3. Contexts and secrets
|
|
|
|
CircleCI organization contexts; the context names are set through `contexts.*`.
|
|
|
|
| Context | Variables | Needed for |
|
|
| --- | --- | --- |
|
|
| registry (e.g. `ghcr`) | `GHCR_USERNAME`, `GHCR_TOKEN` (`write:packages`) | Pushing images and inspecting existing tags; the immutable-tag guard fails closed without registry access |
|
|
| github | `GITHUB_TOKEN` with contents write on the app repository and on the GitOps repository | Reserving `vX.Y.Z` tags on `main` (required by `production`), GitHub Releases, GitOps write-back |
|
|
| sonar (optional) | `SONAR_TOKEN`; SonarCloud organization from `SONAR_ORG` (default `KofTwentyTwo`) | SonarCloud analysis |
|
|
| nvd (optional) | `NVD_API_KEY` | OWASP dependency check; `owasp: false` disables the check |
|
|
|
|
### 4. Branch model
|
|
|
|
Branch names are enforced: `main`, `develop`, `staging`, `feature/*`, `release/*`, `hotfix/*`. A merge to `main` is a production release: it reserves the next `vX.Y.Z` tag, pushes the image, updates the GitOps repository and creates the GitHub Release. Protect `main` accordingly.
|
|
|
|
### 5. Deployment infrastructure (outside Munitor)
|
|
|
|
Munitor stops at the GitOps commit. The consumer provides:
|
|
|
|
- a GitOps repository whose `kustomization.yaml` (an `images[]` entry named `<registry>/<image_name>`) or Helm `values.yaml` (`image.tag`) Munitor edits;
|
|
- Argo CD (or another controller) syncing that repository, with pull access to the registry;
|
|
- the runtime database and its backups. KofTwentyTwo's website uses a CloudNativePG cluster with an object-store backup and a `ScheduledBackup`, declared in `KofTwentyTwo/cluster-gitops` (`tenants/kof22/website`). Munitor only starts a throwaway PostgreSQL sidecar for the CI health gate and never touches a real database;
|
|
- schema migrations. Either the application migrates at start-up, or the deployment runs the `migrations` supplemental image (the KofTwentyTwo website runs a pre-migration backup image and then the migrations image as init containers). `health.migrations` only exercises that image in CI.
|
|
|
|
### 6. Known limitations
|
|
|
|
- Organization defaults point at KofTwentyTwo: registry `ghcr.io/KofTwentyTwo`, npm scope `@koftwentytwo`, CI email `munitor-ci@koftwentytwo.com`, SonarCloud organization `KofTwentyTwo`. Set `docker.registry`, the `org.*` keys and `SONAR_ORG` explicitly. The `org` block is read, but the unknown-key check prints a harmless `Unknown key 'org'` warning.
|
|
- `production` builds on `main` skip the quality gates, which run on `release/*` and `hotfix/*` pull requests. Do not merge unreviewed branches straight to `main`.
|
|
- CircleCI only; there is no GitHub Actions equivalent. Docker jobs use machine executors.
|
|
|
|
### 7. Upgrade notes
|
|
|
|
`0.3.3` reserves release tags atomically on `main` (needs the github context on `build-and-test`, which generated `production` workflows include), refuses to overwrite a version image tag built from another commit, and checks tag ownership before the GitOps bump. A consumer moving from a Munitor-derived orb with a tag-driven production trigger should compare that model with Munitor's merge-to-`main` model before switching.
|
|
|
|
## Pipeline Types
|
|
|
|
### `node-api`
|
|
|
|
For Node.js/Next.js applications with Docker deployment.
|
|
|
|
**Minimal config:**
|
|
|
|
```yaml
|
|
pipeline: node-api
|
|
orb_version: "0.3"
|
|
image_name: my-app
|
|
docker:
|
|
registry: ghcr.io/KofTwentyTwo
|
|
contexts:
|
|
registry: ghcr
|
|
github: github
|
|
```
|
|
|
|
**Full config with all options:**
|
|
|
|
```yaml
|
|
pipeline: node-api
|
|
orb_version: "0.3"
|
|
image_name: my-app
|
|
node_version: "22" # default: 20
|
|
|
|
sonar:
|
|
project_key: KofTwentyTwo_my-app # omit to skip SonarCloud
|
|
|
|
docker:
|
|
registry: ghcr.io/KofTwentyTwo
|
|
|
|
cd:
|
|
repo: KofTwentyTwo/my-app-cd # omit to skip GitOps CD updates
|
|
env:
|
|
release: staging # default: staging (CD target for release/* branches)
|
|
|
|
coverage:
|
|
min_instruction: 80 # default: 70
|
|
|
|
e2e: true # default: false (Playwright)
|
|
sbom: true # default: false
|
|
sast: true # default: false (Semgrep)
|
|
|
|
health:
|
|
path: /api/health # default: /api/health
|
|
port: 3000 # default: 3000 (node-api), 8080 (java-webapp)
|
|
|
|
npm:
|
|
private_registry: true # authenticate to private npm before install
|
|
|
|
services: # start containers before tests
|
|
- image: postgres:17
|
|
port: 5432
|
|
healthcheck: "pg_isready -U test -d mydb"
|
|
env:
|
|
POSTGRES_USER: test
|
|
POSTGRES_PASSWORD: test
|
|
POSTGRES_DB: mydb
|
|
|
|
test:
|
|
setup: scripts/ci-setup.sh # script to run before tests
|
|
commands: # replaces default Jest runner
|
|
- npx playwright install --with-deps chromium
|
|
- npx playwright test
|
|
coverage:
|
|
tool: nyc # default: jest
|
|
command: npm run coverage # custom coverage command
|
|
min_instruction: 80 # overrides top-level coverage.min_instruction
|
|
|
|
contexts:
|
|
registry: ghcr
|
|
github: github
|
|
sonar: sonarcloud
|
|
```
|
|
|
|
**Dockerfile:** Auto-generated multi-stage build. Default framework is `nextjs`, targeting Next.js [standalone output](https://nextjs.org/docs/app/api-reference/config/next-config-js/output) (requires `output: 'standalone'` in `next.config.js`). Set `node: { framework: express }` for Express apps (production deps only, uses `node .` entrypoint). If you need full control over the Dockerfile, use `node-webapp` instead.
|
|
|
|
### `node-webapp`
|
|
|
|
For Node.js applications that provide their own Dockerfile (unlike `node-api` which auto-generates one). Use this when your app needs a custom Docker build process.
|
|
|
|
**Minimal config:**
|
|
|
|
```yaml
|
|
pipeline: node-webapp
|
|
orb_version: "0.3"
|
|
image_name: my-webapp
|
|
docker:
|
|
registry: ghcr.io/KofTwentyTwo
|
|
use_repo_dockerfile: true
|
|
contexts:
|
|
registry: ghcr
|
|
github: github
|
|
```
|
|
|
|
**Full config with all options:**
|
|
|
|
```yaml
|
|
pipeline: node-webapp
|
|
orb_version: "0.3"
|
|
image_name: my-webapp
|
|
node_version: "22" # default: 20
|
|
|
|
sonar:
|
|
project_key: KofTwentyTwo_my-webapp # omit to skip SonarCloud
|
|
|
|
docker:
|
|
registry: ghcr.io/KofTwentyTwo
|
|
use_repo_dockerfile: true # preserve the repository Dockerfile
|
|
|
|
cd:
|
|
repo: KofTwentyTwo/cluster-gitops # omit to skip GitOps CD updates
|
|
format: kustomize
|
|
production_only: true # only main writes this production target
|
|
path: tenants/my-app/website/kustomization.yaml
|
|
env:
|
|
release: staging # default: staging (CD target for release/* branches)
|
|
|
|
coverage:
|
|
min_instruction: 80 # default: 70
|
|
|
|
e2e: true # default: false (Playwright)
|
|
sbom: true # default: false
|
|
sast: true # default: false (Semgrep)
|
|
|
|
health:
|
|
path: /api/health # default: /api/health
|
|
port: 3000 # default: 3000
|
|
|
|
npm:
|
|
private_registry: true # authenticate to private npm before install
|
|
|
|
services: # start containers before tests
|
|
- image: postgres:17
|
|
port: 5432
|
|
healthcheck: "pg_isready -U test -d mydb"
|
|
env:
|
|
POSTGRES_USER: test
|
|
POSTGRES_PASSWORD: test
|
|
POSTGRES_DB: mydb
|
|
|
|
test:
|
|
setup: scripts/ci-setup.sh # script to run before tests
|
|
commands: # replaces default Jest runner
|
|
- npx playwright install --with-deps chromium
|
|
- npx playwright test
|
|
coverage:
|
|
tool: nyc # default: jest
|
|
command: npm run coverage # custom coverage command
|
|
min_instruction: 80 # overrides top-level coverage.min_instruction
|
|
|
|
contexts:
|
|
registry: ghcr
|
|
github: github
|
|
sonar: sonarcloud
|
|
```
|
|
|
|
**Dockerfile:** Set `docker.use_repo_dockerfile: true` and provide `Dockerfile` in the repo root when the application owns its runtime contract. Munitor preserves and builds it, runs a health check, scans with Trivy, and pushes to the registry. Without the option, Munitor retains the legacy generated Dockerfile behavior. Use `node-api` when an always-generated framework Dockerfile is preferred.
|
|
|
|
### `java-webapp`
|
|
|
|
For Java/Maven applications with Docker deployment.
|
|
|
|
**Minimal config:**
|
|
|
|
```yaml
|
|
pipeline: java-webapp
|
|
orb_version: "0.3"
|
|
image_name: my-service
|
|
docker:
|
|
registry: ghcr.io/KofTwentyTwo
|
|
contexts:
|
|
registry: ghcr
|
|
github: github
|
|
nvd: nvd # optional, for OWASP dependency check
|
|
```
|
|
|
|
**Full config with all options:**
|
|
|
|
```yaml
|
|
pipeline: java-webapp
|
|
orb_version: "0.3"
|
|
image_name: my-service
|
|
java_version: "21" # default: 21
|
|
|
|
sonar:
|
|
project_key: KofTwentyTwo_my-service # omit to skip SonarCloud
|
|
|
|
docker:
|
|
registry: ghcr.io/KofTwentyTwo
|
|
|
|
cd:
|
|
repo: KofTwentyTwo/my-service-cd # omit to skip GitOps CD updates
|
|
env:
|
|
release: staging # default: staging (CD target for release/* branches)
|
|
|
|
coverage:
|
|
min_instruction: 80 # default: 70
|
|
|
|
e2e: true # default: false
|
|
sbom: true # default: false
|
|
sast: # or just `sast: true` to fail on findings
|
|
fail_on_findings: false # warn-only mode during migration
|
|
|
|
health:
|
|
path: /api/health # default: /api/health
|
|
port: 8080 # default: 8080
|
|
|
|
contexts:
|
|
registry: ghcr
|
|
github: github
|
|
sonar: sonarcloud
|
|
nvd: nvd # optional, for OWASP dependency check
|
|
```
|
|
|
|
**What runs:** Maven build/test, Checkstyle, SpotBugs, PMD, JaCoCo coverage, OWASP + Trivy security, Docker build/push (Dockerfile auto-generated), gitleaks. OWASP dependency check requires the `nvd` context; if omitted, the security scan runs without NVD database enrichment.
|
|
|
|
**Dockerfile:** Auto-generated production-ready image using Eclipse Temurin JRE Alpine. Includes non-root user (uid 1001), JVM container tuning flags, Docker HEALTHCHECK, and configurable port via `health.port`. The pre-built JAR from CI is copied directly (no redundant Maven build stage). PR and release image builds pull the current base and bypass Docker layer caching for generated Java/Gradle runtime images, ensuring the Alpine package upgrade runs on every build. Node builds retain their layer cache. Trivy gates remain required.
|
|
|
|
### `terraform`
|
|
|
|
For Terraform/Terragrunt infrastructure repos.
|
|
|
|
**Minimal config:**
|
|
|
|
```yaml
|
|
pipeline: terraform
|
|
orb_version: "0.3"
|
|
terraform:
|
|
path: terraform/
|
|
```
|
|
|
|
**Full config:**
|
|
|
|
```yaml
|
|
pipeline: terraform
|
|
orb_version: "0.3"
|
|
terraform:
|
|
path: terraform/ # default: terraform/
|
|
live_path: terraform/live # default: terraform/live
|
|
environments: # default: [production]
|
|
- production
|
|
- staging
|
|
checkov_skip: "CKV_AWS_144,CKV_AWS_145"
|
|
|
|
sast: true # default: false (Semgrep)
|
|
```
|
|
|
|
**What runs:** `tofu fmt` check, `terragrunt init/validate` per environment, Checkov, tfsec, Trivy, gitleaks.
|
|
|
|
### `validate-cd-repo`
|
|
|
|
For CD/GitOps repositories containing Kubernetes manifests, Kustomize overlays, sealed secrets, and ArgoCD application definitions. No application code, no Docker builds, no CD updates.
|
|
|
|
**Minimal config:**
|
|
|
|
```yaml
|
|
pipeline: validate-cd-repo
|
|
orb_version: "0.3"
|
|
```
|
|
|
|
**Full config:**
|
|
|
|
```yaml
|
|
pipeline: validate-cd-repo
|
|
orb_version: "0.3"
|
|
|
|
kustomize:
|
|
version: "5.5.0" # default: 5.5.0
|
|
base_path: base/ # default: base/
|
|
overlays: # default: auto-detect from overlays/*/kustomization.yaml
|
|
- dev
|
|
- staging
|
|
- production
|
|
load_restrictor: true # default: true (--load-restrictor LoadRestrictionsNone)
|
|
scan_overlay: production # default: production
|
|
|
|
kube_linter_config: .kube-linter.yaml # default: none (auto-detect if file exists)
|
|
```
|
|
|
|
**What runs:** YAML lint, kustomize build validation (base + all overlays), kubesec security scan, kube-linter best practices check, gitleaks secrets scan.
|
|
|
|
### `argocd-apps`
|
|
|
|
For ArgoCD app-of-apps repositories that use environment directories (`envs/`) instead of the standard `base/` + `overlays/` kustomize layout. Functionally identical to `validate-cd-repo` but with a different default directory structure.
|
|
|
|
**Minimal config:**
|
|
|
|
```yaml
|
|
pipeline: argocd-apps
|
|
orb_version: "0.3"
|
|
```
|
|
|
|
**Full config:**
|
|
|
|
```yaml
|
|
pipeline: argocd-apps
|
|
orb_version: "0.3"
|
|
|
|
kustomize:
|
|
version: "5.5.0" # default: 5.5.0
|
|
base_path: "" # empty string skips base build
|
|
overlay_dir: envs # default: overlays
|
|
overlays: # default: auto-detect from {overlay_dir}/*/kustomization.yaml
|
|
- dev
|
|
- staging
|
|
- production
|
|
load_restrictor: true # default: true (--load-restrictor LoadRestrictionsNone)
|
|
scan_overlay: production # default: production
|
|
|
|
kube_linter_config: .kube-linter.yaml # default: none
|
|
yamllint_paths: "envs/ apps/" # default: bootstrap/ projects/ credentials/ envs/ apps/ infra/
|
|
```
|
|
|
|
**What runs:** YAML lint, kustomize build validation (overlays only when `base_path` is empty), kubesec security scan, kube-linter best practices check, gitleaks secrets scan.
|
|
|
|
### `sdk-distribution`
|
|
|
|
For SDK packaging and GitHub Releases. Triggered by semver tags (`v1.2.3`).
|
|
|
|
```yaml
|
|
pipeline: sdk-distribution
|
|
orb_version: "0.3"
|
|
contexts:
|
|
github: github
|
|
```
|
|
|
|
**What runs:** Tag validation, SDK assembly/scrub/validate/package, SHA-256 checksums, GitHub Release with artifacts.
|
|
|
|
## Workflow Matrix
|
|
|
|
Each workflow runs on specific branches and includes jobs based on your config:
|
|
|
|
| Workflow | Branches | Jobs |
|
|
|----------|----------|------|
|
|
| `pr-checks` | `feature/*`, `hotfix/*` | Build, test, lint, coverage, security scans, container image scan (Docker modules) |
|
|
| `develop` | `develop` | All pr-checks + Docker build/push + CD update (dev env) |
|
|
| `staging` | `staging` | All pr-checks + Docker build/push + CD update (staging env) |
|
|
| `release-candidate` | `release/*` | All pr-checks + Docker, CD (staging), SonarCloud, SBOM, GitHub Release (pre-release) |
|
|
| `production` | `main` | Build + Docker + CD (prod) + GitHub Release (no quality gates) |
|
|
|
|
For `validate-cd-repo` and `argocd-apps`, all three workflows (`pr-checks`, `develop`, `release`) run the same 5-job set: yaml-lint, kustomize-validate, kubesec-scan, kube-linter, secrets-scan.
|
|
|
|
For `java-webapp`, `gradle-webapp`, `node-api`, and `node-webapp`, PRs run
|
|
`image-scan` after `build-and-test`: build the application and any supplemental
|
|
images locally, then fail on HIGH/CRITICAL CVEs with the same Trivy policy and
|
|
repository-root `.trivyignore` as release builds. JSON reports are saved as
|
|
artifacts. This job does not log in, publish images, reserve tags, or update CD.
|
|
Release candidates already scan through `docker-build-push` and do not run a
|
|
second image scan job.
|
|
|
|
### Job Dependency Graph (node-api pr-checks)
|
|
|
|
```
|
|
checkout
|
|
--> build-and-test
|
|
--> image-scan (local build + Trivy, including supplemental images)
|
|
--> code-quality
|
|
--> coverage
|
|
--> security-scan
|
|
--> e2e-tests (if enabled)
|
|
--> secrets-scan (parallel)
|
|
--> sast-scan (parallel)
|
|
```
|
|
|
|
For `release-candidate` workflows, `docker-build-push` runs after all quality checks pass, followed by `sbom`, `update-cd-repo` (targeting staging), and `github-release` (marked as pre-release). For `production` workflows, `docker-build-push` runs directly after `build-and-test` (no quality gates -- those were enforced on the release branch), followed by `update-cd-repo` (targeting prod) and `github-release` (full release). The `github-release` job creates the GitHub Release for the version tag reserved during `build-and-test` (see Release version integrity).
|
|
|
|
### Release version integrity
|
|
|
|
Production versions on `main` are unique per commit, even when merges land minutes apart:
|
|
|
|
- **Atomic reservation** -- `build-and-test` on `main` reserves `vX.Y.Z` by creating the git tag through the GitHub API before the version is baked into artifacts. Tag creation is create-if-absent, so concurrent pipelines serialize on it: if another commit already owns the tag, the build takes the next patch version. A rerun of the same commit reuses its tag. Existing tags are never moved. Requires the GitHub context on `build-and-test` (present in the generated `production` workflows).
|
|
- **Immutable image tags** -- images carry an `org.opencontainers.image.revision` label. `docker-build-push` refuses to push a version tag (main or supplemental) that already exists for a different commit, and fails closed if the registry cannot be inspected. Environment tags such as `develop` stay mutable.
|
|
- **Ownership-checked GitOps bump** -- `update-cd-repo` verifies that `vX.Y.Z` points at the pipeline's commit before writing the version to the CD repository.
|
|
- **Signed GitOps commits** -- `update-cd-repo` creates the CD repository commit through the GitHub GraphQL API (`createCommitOnBranch`) instead of `git push`. GitHub signs API-created commits, so they show as Verified and pass a CD repository ruleset that requires signed commits, with no bypass actor. The commit is authored by the identity behind `GITHUB_TOKEN`; the source repository and commit are recorded in the commit body. Concurrent bumps retry against the new branch head (`expectedHeadOid`). The `ci_git_email`/`ci_git_name` parameters and `org.ci_email`/`org.ci_name` no longer affect these commits.
|
|
- **Release targets the built commit** -- `github-release` targets `CIRCLE_SHA1`, not the moving `main` head, and refuses if the tag points elsewhere.
|
|
|
|
`cd_path` is rendered into generated pipelines only when `cd.path` is set, so manifests that pin an older `orb_version` keep validating.
|
|
|
|
## Release Workflow
|
|
|
|
Munitor follows a three-environment GitFlow model: **dev**, **staging**, **prod**. Release candidates on staging serve as the QA/UAT gate -- there is no separate UAT environment.
|
|
|
|
### Environments
|
|
|
|
| Environment | Branch | Version Format | Purpose |
|
|
|-------------|--------|----------------|---------|
|
|
| **Dev** | `develop` | `X.Y.Z-SNAPSHOT.SHA` | Integration testing, continuous builds |
|
|
| **Staging** | `release/*` | `X.Y.Z-RC.N` | QA validation, UAT, release candidate testing |
|
|
| **Prod** | `main` | `X.Y.Z` | Production release |
|
|
|
|
### How to release
|
|
|
|
1. **Develop** -- Merge feature branches into `develop`. Each push builds a SNAPSHOT image and deploys to the dev environment.
|
|
2. **Create release branch** -- When ready to release, create `release/X.Y.Z` from `develop`. This triggers the `release-candidate` workflow:
|
|
- Full quality pipeline (build, test, lint, coverage, security, SAST, SonarCloud)
|
|
- Docker image tagged `X.Y.Z-RC.1` pushed to registry
|
|
- CD repo updated targeting **staging** environment
|
|
- GitHub pre-release created (`vX.Y.Z-RC.1`)
|
|
3. **QA on staging** -- Test the RC on staging. If fixes are needed, commit directly to the release branch. Each push increments the RC number (`RC.2`, `RC.3`, etc.) and re-deploys to staging.
|
|
4. **Promote to production** -- Merge the release branch into `main`. This triggers the `production` workflow:
|
|
- Build + Docker image tagged `X.Y.Z` (no quality gates -- already passed on the release branch)
|
|
- CD repo updated targeting **prod** environment
|
|
- Full GitHub Release created (`vX.Y.Z`)
|
|
5. **Back-merge** -- Merge `main` back into `develop` to carry forward the version bump.
|
|
|
|
### Hotfixes
|
|
|
|
For emergency production fixes: create `hotfix/<name>` from `main`, fix, then merge directly to `main`. The production workflow runs the same fast-path (build + Docker + CD + release). Back-merge to `develop` after.
|
|
|
|
### Versioning
|
|
|
|
Munitor manages versioning automatically via GitVersion. You do not need a `GitVersion.yml` in your repo -- Munitor writes a standard SDLC-compliant config at CI time. Version bumps follow commit conventions:
|
|
|
|
- **Patch** (default): every merge to main
|
|
- **Minor**: feature branch merge to develop
|
|
- **Major**: commit message containing `+semver: major`
|
|
|
|
Tags follow the format `vX.Y.Z` (git tag) and `X.Y.Z` (Docker tag). Tags are immutable and only created on main (full releases) or release branches (pre-releases).
|
|
|
|
## `.munitor.yml` Reference
|
|
|
|
| Field | Type | Default | Pipelines | Description |
|
|
|-------|------|---------|-----------|-------------|
|
|
| `pipeline` | string | *required* | all | Pipeline type: `node-api`, `node-webapp`, `java-webapp`, `terraform`, `sdk-distribution`, `validate-cd-repo`, `argocd-apps` |
|
|
| `orb_version` | string | *required* | all | Orb version for the generated pipeline (e.g., `"0.3.3"`, `"0.3"`, `dev:snapshot`) |
|
|
| `image_name` | string | *required** | node-api, node-webapp, java-webapp | Docker image name (without registry prefix) |
|
|
| `node_version` | string | `20` | node-api, node-webapp | Node.js major version |
|
|
| `java_version` | string | `21` | java-webapp | Java major version |
|
|
| `sonar.project_key` | string | -- | node-api, node-webapp, java-webapp | SonarCloud project key. Omit to skip |
|
|
| `docker.registry` | string | *required** | node-api, node-webapp, java-webapp | Container registry URL |
|
|
| `docker.use_repo_dockerfile` | bool | `false` | node-webapp | Preserve and build the repository-root Dockerfile instead of generating one |
|
|
| `cd.repo` | string | -- | node-api, node-webapp, java-webapp | GitOps CD repo (`org/repo`). Omit to skip |
|
|
| `cd.path` | string | -- | node-api, node-webapp, java-webapp | Explicit repository-relative `kustomization.yaml` path for consolidated GitOps repositories |
|
|
| `cd.production_only` | bool | `false` | node-api, node-webapp, java-webapp | Disable CD write-back from develop, staging, and release workflows |
|
|
| `cd.env.release` | string | `staging` | node-api, node-webapp, java-webapp | CD target environment for release branches |
|
|
| `cd.env.develop` / `cd.env.staging` / `cd.env.prod` | string | `develop` / `staging` / `prod` | node-api, node-webapp, java-webapp | CD target environment names for the develop, staging and main workflows |
|
|
| `cd.format` | string | `helm` | node-api, node-webapp, java-webapp | `helm` (sets `image.tag` in `values.yaml`, per environment under `environments/<env>/` when present) or `kustomize` (sets `images[].newTag`) |
|
|
| `server_module` | string | -- | gradle-webapp | Gradle module whose `build/libs` JAR goes into the generated image (multi-module builds) |
|
|
| `owasp` | bool | `true` | java-webapp | Run the OWASP dependency check |
|
|
| `package_manager` | string | `npm` | node-api, node-webapp | `npm` or `pnpm` |
|
|
| `supplemental_images` | list | `[]` | node-api, node-webapp, java-webapp | Extra images (`name`, `dockerfile`, `cd.image_name`) built and tagged with the same version |
|
|
| `health.db` | bool | `false` | node-api, node-webapp, java-webapp | Start a PostgreSQL sidecar for the health check |
|
|
| `health.migrations` | string | -- | node-api, node-webapp, java-webapp | Supplemental image suffix run against the sidecar before the health check |
|
|
| `health.smoke_paths` | list | `[]` | node-api, node-webapp, java-webapp | Extra paths that must return HTTP 200 during the health check |
|
|
| `test.coverage.summary_path` | string | `coverage/coverage-summary.json` | node-api, node-webapp | Coverage summary file read by the coverage gate |
|
|
| `org.docker_registry` / `org.npm_scope` / `org.ci_email` / `org.ci_name` / `org.orb_slug` | string | KofTwentyTwo values | all | Organization defaults; see [Adopting in another app](#adopting-in-another-app) |
|
|
| `coverage.min_instruction` | int | `70` | node-api, node-webapp, java-webapp | Minimum coverage percentage |
|
|
| `e2e` | bool | `false` | node-api, node-webapp, java-webapp | Enable Playwright E2E tests |
|
|
| `sbom` | bool | `false` | node-api, node-webapp, java-webapp | Enable SBOM generation |
|
|
| `sast` | bool/object | `false` | all except sdk | Enable Semgrep SAST scanning. Use `true`/`false` or object form |
|
|
| `sast.fail_on_findings` | bool | `true` | all except sdk | Fail build on SAST findings. Set `false` for warn-only mode |
|
|
| `node.framework` | string | `nextjs` | node-api | Node framework: `nextjs` or `express` (not used by node-webapp) |
|
|
| `health.path` | string | `/api/health` | node-api, node-webapp, java-webapp | Health check endpoint path |
|
|
| `health.port` | int | `3000`/`8080` | node-api, node-webapp, java-webapp | Health check port (3000 for node-*, 8080 for java-*) |
|
|
| `npm.private_registry` | bool | `false` | node-api, node-webapp | Authenticate to private npm registry |
|
|
| `services` | list | `[]` | node-api, node-webapp | Service containers to start before tests |
|
|
| `test.setup` | string | -- | node-api, node-webapp | Script to run before tests |
|
|
| `test.commands` | list | `[]` | node-api, node-webapp | Custom test commands (replaces default Jest) |
|
|
| `test.coverage.tool` | string | `jest` | node-api, node-webapp | Coverage tool (`jest` or `nyc`) |
|
|
| `test.coverage.command` | string | -- | node-api, node-webapp | Custom coverage generation command |
|
|
| `terraform.path` | string | `terraform/` | terraform | Root Terraform directory |
|
|
| `terraform.live_path` | string | `terraform/live` | terraform | Terragrunt live directory |
|
|
| `terraform.environments` | list | `[production]` | terraform | Environments to validate |
|
|
| `terraform.checkov_skip` | string | -- | terraform | Comma-separated Checkov rules to skip |
|
|
| `kustomize.version` | string | `5.5.0` | validate-cd-repo, argocd-apps | Kustomize version to install |
|
|
| `kustomize.base_path` | string | `base/` | validate-cd-repo, argocd-apps | Path to kustomize base directory. Empty string skips base build |
|
|
| `kustomize.overlay_dir` | string | `overlays` | validate-cd-repo, argocd-apps | Directory containing overlay subdirectories (e.g. `overlays`, `envs`) |
|
|
| `kustomize.overlays` | list | auto-detect | validate-cd-repo, argocd-apps | Overlay names to validate |
|
|
| `kustomize.load_restrictor` | bool | `true` | validate-cd-repo, argocd-apps | Use --load-restrictor LoadRestrictionsNone |
|
|
| `kustomize.scan_overlay` | string | `production` | validate-cd-repo, argocd-apps | Overlay to scan with kubesec/kube-linter |
|
|
| `kube_linter_config` | string | -- | validate-cd-repo, argocd-apps | Path to kube-linter config file |
|
|
| `yamllint_paths` | string | *pipeline-dependent* | validate-cd-repo, argocd-apps | Space-separated directories for YAML linting |
|
|
| `contexts.registry` | string | -- | node-api, node-webapp, java-webapp | CircleCI context for Docker push (e.g., `ghcr`) |
|
|
| `contexts.github` | string | -- | all | CircleCI context for Git/GitHub operations. Enables GitHub Releases when set |
|
|
| `contexts.sonar` | string | -- | node-api, node-webapp, java-webapp | CircleCI context for SonarCloud. Required if `sonar.project_key` is set |
|
|
| `contexts.nvd` | string | -- | java-webapp | CircleCI context for OWASP NVD database. Optional; security scan runs without it |
|
|
|
|
*\* Required for pipelines that build Docker images.*
|
|
|
|
## Health Check Requirement
|
|
|
|
Every container built by Munitor must expose a health endpoint. After the Docker image is built, Munitor starts the container and validates the endpoint returns HTTP 200. This is a mandatory gate; builds fail if the health check does not pass within 30 seconds.
|
|
|
|
Default endpoint: `GET /api/health` on port 3000 (node-api, node-webapp) or 8080 (java-webapp). Override via `health.path` and `health.port` in `.munitor.yml`.
|
|
|
|
## Troubleshooting
|
|
|
|
**"Missing required fields: orb_version"** -- Add `orb_version` to your `.munitor.yml`. Use `dev:snapshot` for unreleased feature testing or `0.3` for the current stable line.
|
|
|
|
**"munitor_header: command not found"** -- You're using an older orb version. Update to `dev:snapshot` or wait for the next stable release.
|
|
|
|
**Empty output + exit code 1 on `npm ci`** -- Usually a PATH issue. Munitor exports node/npm to `BASH_ENV`; if this breaks, all subsequent steps fail silently. Check that `install_node` succeeded in the build logs.
|
|
|
|
**"Unknown key 'xxx' in .munitor.yml"** -- Munitor warns on unrecognized top-level keys to catch typos. Check spelling against the reference table above.
|
|
|
|
**Interactive prompt hangs (debconf/dpkg)** -- If test commands install system packages via apt, add `DEBIAN_FRONTEND=noninteractive` as a prefix. Munitor sets this automatically in the test runner, but explicit is safer for custom commands.
|
|
|
|
**"No template found for pipeline type"** -- The `pipeline` field must be one of: `node-api`, `node-webapp`, `java-webapp`, `terraform`, `sdk-distribution`, `validate-cd-repo`, `argocd-apps`.
|
|
|
|
**Setup succeeds but no continuation workflow appears** -- The generated config may contain invalid YAML that CircleCI accepts but can't execute. Common cause: an empty context list item from an unset optional context (e.g., `contexts.nvd`). Check the generated config by running `make publish-snapshot` and retriggering.
|
|
|
|
**Branch name rejected** -- Munitor enforces GitFlow naming. Rename your branch to match an allowed pattern.
|
|
|
|
**Tests pass locally but fail in CI** -- Check Node/Java version alignment. Add an `.nvmrc` file to keep local and CI versions in sync. Munitor will warn if `.nvmrc` and `.munitor.yml` versions differ.
|
|
|
|
## Development (Munitor Contributors)
|
|
|
|
```bash
|
|
make lint # yamllint + shellcheck
|
|
make validate # pack + circleci validate
|
|
make test-scripts # structural + unit tests
|
|
make test-templates # template rendering tests
|
|
make all # run everything
|
|
```
|
|
|
|
Pushes to `develop` auto-publish `kof22/munitor@dev:snapshot`. Production releases are tagged on `main` (`v1.2.3`).
|
|
|
|
### Architecture
|
|
|
|
```
|
|
src/
|
|
@orb.yml # Orb metadata
|
|
commands/*.yml # 56 reusable commands (each wraps a script)
|
|
jobs/*.yml # 22 jobs composing commands into workflows
|
|
executors/*.yml # Machine executor definitions
|
|
scripts/
|
|
*.sh # 55+ bash scripts (the actual logic)
|
|
munitor_helpers.sh # Shared library (header, tool checks, download retry)
|
|
templates/*.yml.tpl # Pipeline templates with conditional blocks
|
|
templates/partials/ # Reusable deploy workflow fragments
|
|
```
|
|
|
|
Pipeline generation flow:
|
|
1. `generate_pipeline` job runs `generate_config.sh`
|
|
2. Reads `.munitor.yml`, extracts variables via `extract_munitor_vars.sh`
|
|
3. Selects template based on `pipeline` type
|
|
4. Expands `##INCLUDE_DEPLOY##` markers with partial templates
|
|
5. Runs `envsubst` for variable substitution
|
|
6. Processes `##IF_*##` / `##ENDIF_*##` conditional blocks
|
|
7. Validates output YAML and passes to CircleCI continuation
|
|
|
|
## Temporary npm audit exceptions
|
|
|
|
The default npm audit gate remains unchanged. For a reviewed development-only HIGH
|
|
advisory with no patched package release, a repository may commit
|
|
`.npm-audit-exceptions.json` with schemaVersion 1 and an exceptions array. Each entry
|
|
requires a register ID, exact GHSA advisory ID, package, version, development scope
|
|
and UTC expiry date. See Website-Frontend's EX-0008 for the first consumer.
|
|
|
|
The full npm audit runs alongside a production audit. All production HIGH/CRITICAL
|
|
and all CRITICAL findings fail. Only the named advisory and its propagated HIGH
|
|
findings can pass, and every affected lockfile path must have dev true and the exact
|
|
excepted version. Audit errors, unknown chains and expired exceptions fail. A new
|
|
upstream package release also fails and requires review. Trivy remains unchanged.
|
|
This mechanism requires npm audit report version 2 and a package-path lockfile;
|
|
unsupported reports fail. BATS and Node are required for the audit regression tests.
|
|
|
|
## Security
|
|
|
|
Report vulnerabilities privately; see [SECURITY.md](SECURITY.md).
|
|
|
|
## Contributing
|
|
|
|
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
|
## License
|
|
|
|
[GPL-3.0](LICENSE)
|