Files
2026-10-19 15:25:37 -05:00

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)