Compare commits

...
Author SHA1 Message Date
james.maesandCodex 21517b25e7 fix: refresh generated JVM runtime packages on every build
Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-19 07:50:52 -05:00
james.maes a3d680f130 Merge pull request #37 from KofTwentyTwo/main
chore: back-merge main into develop after 0.3.6
2026-10-13 17:24:24 -05:00
james.maes 57dffdd9a3 Merge pull request #36 from KofTwentyTwo/release/0.3.6
Release Munitor 0.3.6
2026-10-13 17:17:42 -05:00
james.maesandClaude Opus 5.5 9e2c45e79a docs(readme): adoption examples use 0.3.6
Signed-off-by: James Maes <james@kof22.com>

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:11:51 -05:00
james.maes fbeec65cb5 Merge pull request #35 from KofTwentyTwo/feature/supplemental-push-boolean
fix: push supplemental images on release builds again (0.3.5 regression)
2026-10-13 17:11:47 -05:00
james.maesandClaude Opus 5.5 69898922be fix: push supplemental images on release builds again
CircleCI renders the boolean push parameter as 1/0 in the step
environment, but build_push_supplemental.sh only pushed when
PUSH_IMAGES was the string "true". Since 0.3.5 every release build
built and scanned supplemental images and never pushed them, so
deployments referencing them hit ImagePullBackOff (kof22 0.1.75).

PUSH_IMAGES now accepts true/1/yes/on and false/0/no/off/empty; unset
still means push, and an unrecognised value fails before any build.
Tests cover release-mode pushes (1, true, TRUE) of every supplemental
image, PR-mode non-pushes (0, false, empty), and the invalid value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-13 17:05:34 -05:00
james.maes 59eb5294fe Merge pull request #34 from KofTwentyTwo/main
chore: back-merge main into develop after 0.3.5
2026-10-13 15:44:21 -05:00
james.maes 16079c5ca8 Merge pull request #33 from KofTwentyTwo/release/0.3.5
Release Munitor 0.3.5
2026-10-13 15:37:54 -05:00
james.maesandClaude Opus 5.5 578c7ea0eb docs(readme): adoption examples use 0.3.5
Signed-off-by: James Maes <james@kof22.com>

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:31:55 -05:00
james.maes 53c04937b0 Merge pull request #32 from KofTwentyTwo/feature/pr-image-scan
feat: scan container images in PR pipelines
2026-10-13 14:42:28 -05:00
james.maesandClaude Opus 5.5 b09ced056f feat: scan container images in PR pipelines
New munitor/docker_scan job (image-scan) in the pr-checks workflow of
java-webapp, gradle-webapp, node-api and node-webapp: builds the app
image and every supplemental image, runs the same Trivy 0.69.3
HIGH/CRITICAL --exit-code 1 scan as the release pipeline (same
.trivyignore), stores the JSON results, and never pushes.
build_push_supplemental gains push (default true, unchanged behavior).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:18:41 -05:00
james.maes fb232fb6fe Merge pull request #31 from KofTwentyTwo/main
chore: back-merge main into develop after 0.3.4
2026-10-12 06:24:42 -05:00
james.maes 687944e872 Merge pull request #30 from KofTwentyTwo/release/0.3.4
Release Munitor 0.3.4
2026-10-12 06:19:46 -05:00
james.maes e54d739554 Merge pull request #29 from KofTwentyTwo/feature/signed-gitops-commits
fix: sign GitOps bumps, run tests in CI, scope feature snapshots
2026-10-12 06:14:14 -05:00
james.maesandClaude Opus 5.5 71c262fb62 docs: cite EX-0011 for the 2-space shell layout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-12 06:09:01 -05:00
james.maes 6cbe4256d5 Merge pull request #28 from KofTwentyTwo/feature/adoption-checklist-link
docs(readme): link the QQQ adoption checklist
2026-10-12 06:04:10 -05:00
james.maesandClaude Opus 5.5 d7960161fb test: cover signed API commits and head-refresh retries in update_cd_repo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-12 06:02:52 -05:00
james.maesandClaude Opus 5.5 807120f2af ci: install bats-core from its release tag
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-12 05:56:11 -05:00
james.maesandClaude Opus 5.5 1c64d1e18e fix: sign GitOps bumps, run tests in CI, scope feature snapshots
update_cd_repo creates the CD commit through the GraphQL
createCommitOnBranch mutation so GitHub signs it, letting the GitOps
repository require signed commits without a bypass actor. Concurrent
bumps retry against the refreshed head.

CI pins circleci/orb-tools to 12.5.0 and adds a test job running the
script, template and BATS suites. Feature branches publish only the
immutable dev:<sha> tag; develop keeps dev:snapshot (#26). The
generator accepts the documented org key (#27). Adds .editorconfig.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-12 05:51:41 -05:00
james.maesandClaude Opus 5.5 24482fd030 docs(readme): link the QQQ adoption checklist; neutral example
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-12 05:47:45 -05:00
james.maes ad1d30681b Merge pull request #25 from KofTwentyTwo/feature/adoption-standards-docs
docs: add adoption guide, security policy and code owners
2026-10-12 05:30:48 -05:00
james.maesandClaude Opus 5.5 b4a9d120eb docs: add adoption guide, security policy and code owners
Add an "Adopting in another app" section to the README (version,
.munitor.yml example, CircleCI settings, contexts and secrets, branch
model, deployment infrastructure, known limitations, 0.3.3 upgrade
notes), a tier and standards statement, and reference rows for the
previously undocumented .munitor.yml keys. Add SECURITY.md,
.github/CODEOWNERS and a CONTRIBUTING workflow section (branches,
Conventional Commits, DCO sign-off, local checks), and fix the
nonexistent `make test` target in CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-12 05:29:05 -05:00
james.maes b3b90f5886 Merge pull request #24 from KofTwentyTwo/main
Merge main back into develop after 0.3.3
2026-10-11 14:50:53 -05:00
james.maes d9b4da9e9e Merge pull request #23 from KofTwentyTwo/release/0.3.3
Release 0.3.3: atomic release versions
2026-10-11 14:49:46 -05:00
james.maesandClaude Opus 5.5 4b99bbec09 chore: prepare Munitor 0.3.3 release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:48:30 -05:00
james.maes 00f349cb0c Merge pull request #22 from KofTwentyTwo/feature/atomic-release-versions
fix: atomic release versions, immutable image tags, conditional cd_path
2026-10-11 14:48:10 -05:00
27 changed files with 927 additions and 134 deletions

No files matched your search

+46 -1
View File
@@ -1,7 +1,8 @@
version: 2.1
orbs:
orb-tools: circleci/orb-tools@12.1
# Pinned to an exact version (K22-CI-11); bump deliberately.
orb-tools: circleci/orb-tools@12.5.0
filters: &filters
tags:
@@ -13,6 +14,31 @@ tag-only: &tag-only
tags:
only: /^v[0-9]+\.[0-9]+\.[0-9]+$/
jobs:
test:
docker:
- image: cimg/node:22.12
resource_class: medium
steps:
- checkout
- run:
name: Install test tools
command: |
sudo apt-get update -qq
sudo apt-get install -y -qq gettext-base shellcheck >/dev/null
YQ_VERSION=v4.44.6
sudo curl -fsSL -o /usr/local/bin/yq \
"https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64"
sudo chmod +x /usr/local/bin/yq
git clone -q --depth 1 --branch v1.11.1 https://github.com/bats-core/bats-core.git /tmp/bats-core
sudo /tmp/bats-core/install.sh /usr/local
- run:
name: Script and template tests
command: make test-scripts test-templates
- run:
name: BATS tests
command: bats tests
workflows:
lint-pack:
jobs:
@@ -23,6 +49,8 @@ workflows:
- orb-tools/review:
filters: *filters
exclude: RC006,RC007,RC010 # RC006/RC007: private repo URL unreachable; RC010: machine executors
- test:
filters: *filters
test-deploy:
jobs:
@@ -38,11 +66,15 @@ workflows:
- orb-publishing
filters: *tag-only
# develop owns the shared dev:snapshot tag. Feature branches publish only an
# immutable dev:<sha> tag so open branches cannot overwrite each other's
# snapshot (issue #26).
snapshot:
jobs:
- orb-tools/pack:
filters: *filters
- orb-tools/publish:
name: publish-develop-snapshot
orb_name: kof22/munitor
vcs_type: << pipeline.project.type >>
pub_type: dev
@@ -55,4 +87,17 @@ workflows:
branches:
only:
- develop
- orb-tools/publish:
name: publish-feature-sha
orb_name: kof22/munitor
vcs_type: << pipeline.project.type >>
pub_type: dev
dev_tags: "dev:${CIRCLE_SHA1}"
requires:
- orb-tools/pack
context:
- orb-publishing
filters:
branches:
only:
- /feature\/.*/
+26
View File
@@ -0,0 +1,26 @@
# KofTwentyTwo .editorconfig (standards K22-REPO-03, configs/editorconfig).
# Exception: the existing Bash scripts and CircleCI templates use 2-space
# indentation; K22-CODE-SH-02 specifies 3. K22 exception EX-0011.
# Do not mix widths in a file.
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[{Makefile,makefile,*.mk}]
indent_style = tab
# Shell: shfmt reads these keys (K22-CODE-SH-02), apart from the indent width above.
[*.{sh,bash,bats}]
binary_next_line = true
switch_case_indent = true
space_redirects = true
function_next_line = true
+3
View File
@@ -0,0 +1,3 @@
# Every path has an owner (K22-REPO-08). Add narrower rules below for areas that
# need a specific reviewer once the project has more than one maintainer.
* @KofTwentyTwo
+6 -4
View File
@@ -5,7 +5,7 @@ CircleCI orb that generates CI/CD pipeline configurations from a `.munitor.yml`
## Key Commands
```bash
make test # Run all tests (BATS)
make test-scripts # Script unit tests (bats tests for the .bats suites)
make pack # Pack orb YAML from src/
make all # lint + pack + test
make validate # Validate packed orb with CircleCI CLI
@@ -29,15 +29,17 @@ GitHub Issues (org: KofTwentyTwo). Commit format: `feat(#N): description` or `Cl
- Namespace: `kof22`
- Orb: `kof22/munitor`
- Latest stable: `kof22/munitor@0.3.1`
- Release candidate: `kof22/munitor@0.3.2` (stable tag publication pending)
- Latest stable: `kof22/munitor@0.3.3`
- Dev snapshot: `kof22/munitor@dev:snapshot`
`0.3.0` includes first-class pnpm support, repository-owned Dockerfiles for
`node-webapp`, and consolidated Kustomize GitOps write-back with optional
production-only behavior and bounded push-race retries. `0.3.1` moves the
generated `node-webapp` runtime to `distroless/nodejs<N>-debian13` (debian12
failed the Trivy gate on OpenSSL CVEs).
failed the Trivy gate on OpenSSL CVEs). `0.3.3` makes release versions on
`main` atomic (git-tag reservation via the GitHub refs API), refuses to
overwrite version image tags built from another commit, checks tag ownership
before the GitOps bump, and renders `cd_path` only when `cd.path` is set.
## Session Continuity
+10
View File
@@ -1,5 +1,14 @@
# Contributing to Munitor
## Workflow
- **Branches.** Branch `feature/<name>` or `hotfix/<name>` from `develop` and open a pull request back to `develop`. Releases go through `release/X.Y.Z` into `main`; pushing the `vX.Y.Z` tag publishes `kof22/munitor@X.Y.Z`, and every push to `develop` publishes `dev:snapshot`, and every push to `feature/*` publishes only the immutable `dev:<commit-sha>` tag. Back-merge `main` into `develop` after a release.
- **Commits.** Conventional Commits (`feat(#N): ...`, `fix: ...`, `docs: ...`), signed (`git commit -S`), with a Developer Certificate of Origin sign-off line (`git commit -s`, which adds `Signed-off-by: Name <email>`). Reference the GitHub issue (`Closes #N`) where there is one.
- **Checks.** Run `make all` (yamllint, shellcheck, pack plus `circleci orb validate`, script and template tests) and `bats tests` (the `.bats` suites) before pushing. CircleCI runs the orb-tools lint, pack and review jobs plus a `test` job (`make test-scripts test-templates` and `bats tests`). `circleci orb validate` still runs only locally.
- **Formatting.** Shell scripts and templates use 2-space indentation (see `.editorconfig`); this deviates from K22-CODE-SH-02 under standards exception EX-0011.
- **Security.** Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md), never in a public issue.
- **AI assistance.** Disclose AI-assisted changes in the pull request description.
## Module Inventory
Munitor organizes CI/CD pipelines into **modules** (called "pipeline types" in `.munitor.yml`). Each module is a self-contained set of templates, jobs, commands, and scripts that handle a specific application stack.
@@ -323,6 +332,7 @@ These components are shared across all modules -- you don't need to recreate the
| `secrets_scan` job | Gitleaks (all modules) |
| `sast_scan` job | Semgrep (all modules, opt-in) |
| `docker_build_push` job | Build + health check + Trivy scan + GHCR push |
| `docker_scan` job | PR image build + Trivy scan (including supplemental images), JSON artifacts, no publishing |
| `health_check` command | Container health check smoke test (all Docker modules) |
| `update_cd_repo` job | GitOps CD repo update |
| `github_release` job | Annotated git tags + GitHub Releases (auto-enabled with `contexts.github`) |
+131 -3
View File
@@ -2,6 +2,14 @@
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.6` |
| **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.
@@ -58,6 +66,93 @@ Munitor validates branch names and will fail on non-conforming names.
| `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.6` (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.6`.
- `.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.6"
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`
@@ -277,7 +372,7 @@ contexts:
**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).
**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`
@@ -395,7 +490,7 @@ 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 |
| `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) |
@@ -403,11 +498,20 @@ Each workflow runs on specific branches and includes jobs based on your config:
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
@@ -425,6 +529,7 @@ Production versions on `main` are unique per commit, even when merges land minut
- **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.
@@ -475,7 +580,7 @@ Tags follow the format `vX.Y.Z` (git tag) and `X.Y.Z` (Docker tag). Tags are imm
| 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., `1`, `dev:snapshot`) |
| `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 |
@@ -486,6 +591,17 @@ Tags follow the format `vX.Y.Z` (git tag) and `X.Y.Z` (Docker tag). Tags are imm
| `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 |
@@ -596,3 +712,15 @@ excepted version. Audit errors, unknown chains and expired exceptions fail. A ne
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)
+56
View File
@@ -0,0 +1,56 @@
# Security Policy
## Reporting a vulnerability
**Do not open a public issue for a security problem.**
Report it privately through GitHub's
[private vulnerability reporting](https://github.com/KofTwentyTwo/Munitor/security/advisories/new)
(the repository's **Security** tab, then **Report a vulnerability**). If that form is
unavailable, contact the maintainer privately through GitHub before disclosing anything.
Security contact: **James Maes** ([@KofTwentyTwo](https://github.com/KofTwentyTwo)),
maintainer.
Please include the affected orb version, the `.munitor.yml` pipeline type, how to
reproduce it, and the impact you see. Never include secrets or CircleCI context values.
## What to expect
KofTwentyTwo follows coordinated vulnerability disclosure. Timeframes are counted
from the day the report arrives and come from the
[KofTwentyTwo security program](https://github.com/KofTwentyTwo/standards/blob/main/policies/security-program.md):
| Step | Target |
| --- | --- |
| Acknowledge the report | 7 days |
| Assess it: confirm or reject, with a severity | 14 days |
| Release a fix for a CRITICAL vulnerability | 7 days |
| Release a fix for a HIGH vulnerability | 30 days |
| Release a fix for a MEDIUM vulnerability | 90 days |
| Release a fix for a LOW vulnerability | The next release |
| Publish the advisory (GitHub Security Advisory, CVE where applicable) | When the fix ships, or 90 days after the report, whichever comes first |
Reporters are credited in the advisory unless they ask not to be.
## Supported versions
| Version | Security fixes |
| --- | --- |
| 0.3.x (`kof22/munitor@0.3`) | Yes |
| Older | No; upgrade to the latest release |
Only the latest minor line receives fixes; a line ends when the next minor version is
published to the CircleCI orb registry.
## Scope notes
Munitor runs inside the consumer's CircleCI project with the consumer's contexts
(registry, GitHub, SonarCloud, NVD). Issues that let a generated pipeline leak context
secrets, push an image or GitOps change for the wrong commit, or bypass a security
gate are in scope.
## Published vulnerabilities
Fixed vulnerabilities are published as
[GitHub Security Advisories](https://github.com/KofTwentyTwo/Munitor/security/advisories).
+49
View File
@@ -1,5 +1,52 @@
# Munitor session state
## JVM runtime package freshness (2026-10-19)
Website-Backend release6258 and PR scans6266/6277 reused a cached apk upgrade
layer containing vulnerable libexpat. The shared docker_build command now pulls
the base and bypasses layer caching for generated Java/Gradle runtimes, while
Node caching and all Trivy gates remain intact. Narrow behavioral tests cover
both pipeline types, alternate/missing manifests, tags, provenance, and both
PR/release job routes. All eight focused shell cases passed via the coordinating
agent; ShellCheck and git diff --check passed. Full make/BATS/orb validation and
real-image scanning remain pending. Stable release and consumer adoption remain
pending; no unchanged vulnerable image should be retried.
## PR container scanning (2026-10-13)
Uncommitted work on `feature/pr-image-scan` adds `docker_scan` after
`build-and-test` in the four application PR templates. It restores the existing
feature/hotfix version metadata, builds locally, and applies the release Trivy
HIGH/CRITICAL policy from the repository root. Supplemental images use
`push: false`; the command defaults to publishing for existing callers. Main
and supplemental JSON reports are artifacts. Release candidates already scan
through `docker_build_push`, so no duplicate job was added.
Verified: native CircleCI orb pack/validate, ShellCheck on all source scripts
and the changed tests, template structure/filters/parameter wiring, packed
template synchronization, and `git diff --check`. Requested make and BATS runs
were attempted but Git Bash cannot start in this sandbox (CreateFileMapping,
Win32 error 5); yamllint is not installed. New behavioral tests therefore need
a Linux/CI run, including real Docker/Trivy integration. No commit or publish.
The Second Brain vault is outside writable roots; continuity is recorded here.
## Release 0.3.3 (2026-10-11)
PR22 (00f349c) fixes release version collisions. Website-Backend #65/#66 (0.1.24)
and #80/#79 (0.1.32) built concurrently, computed the same GitVersion version,
overwrote the GHCR tag, and github-release tagged the moving main head, so prod
ran an image missing the later merge. Build jobs now reserve vX.Y.Z atomically
(POST git/refs; next patch on conflict; tags never move), docker_build_push guards
immutable version tags via the org.opencontainers.image.revision label,
update_cd_repo verifies tag ownership, and github_release targets CIRCLE_SHA1.
The generator renders cd_path only when cd.path is set (older-orb manifests on
website develop branches, Concilium, and makers4 failed with "Unexpected
argument(s): cd_path"). Consumer pin bumps are coordinated per repo because a
main merge deploys.
Local Windows runs: 4 test_generate_config health_path assertions fail on clean
develop too (Git-for-Windows rewrites /api/health); CI on Linux is authoritative.
## Release candidate 0.3.2 (2026-10-10)
Munitor PR19 merged to develop at c8bbf2e. Release branch `release/0.3.2`
@@ -49,3 +96,5 @@ after Java installation. APT now receives noninteractive debconf and automatic
needrestart settings; a stubbed integration test verifies both APT invocations.
These fixes require normal feature-to-develop review and stable tag publication
from main before website consumers can select the new version. Nothing deployed.
Runtime freshness validation: coordinating agent completed make all (lint, pack, orb validation, scripts,277template assertions) and all7BATS tests in Ubuntu. Real consumer image scan remains required after release.
+8 -1
View File
@@ -2,8 +2,15 @@
## Pending
- [ ] Release JVM runtime cache fix and adopt the stable orb in Website-Backend; verify fresh image Trivy before release.
- [ ] Run PR image-scan script/template/BATS tests on Linux and verify a consumer PR with Docker/Trivy before publishing.
- [x] Add PR-only image scans for the four application templates, supplemental scan-only mode, JSON artifacts, and regression tests (uncommitted).
- [x] Review publishing CI fixes and merge PR19 to develop.
- [ ] Publish v0.3.2 from main; update website consumer orb pins.
- [x] Publish v0.3.2 from main.
- [ ] Publish v0.3.3 (atomic release versions); bump Website-Backend/Frontend pins to 0.3.3.
- [ ] Bump Concilium (0.2) and makers4 (0.1) to 0.3.3 when next active.
- [ ] Follow makers4 and terraform-proxmox-talos on CircleCI when those repos are next active.
- [ ] Adopt supplemental images on an appropriate consumer.
+11 -3
View File
@@ -1,8 +1,12 @@
description: >
Build, scan, and push supplemental Docker images alongside the main application image.
Each supplemental image shares the same version tag. Requires docker_push_ghcr to have
run first (for registry auth and Trivy installation).
Each supplemental image shares the same version tag. Requires run_trivy_image first
for Trivy installation, and docker_push_ghcr for registry auth only when push is true.
parameters:
push:
type: boolean
default: true
description: Publish scanned images to the registry. Disable for PR scans.
supplemental_images:
type: string
default: "[]"
@@ -16,9 +20,13 @@ parameters:
description: Container registry URL.
steps:
- run:
name: Build and push supplemental images
name: Build and scan supplemental images (optionally push)
environment:
PUSH_IMAGES: << parameters.push >>
SUPPLEMENTAL_IMAGES_JSON: << parameters.supplemental_images >>
IMAGE_NAME: << parameters.image_name >>
REGISTRY: << parameters.registry >>
command: << include(scripts/build_push_supplemental.sh) >>
- store_artifacts:
path: /tmp/trivy-supplemental-results
destination: trivy-supplemental-scans
+5 -2
View File
@@ -39,11 +39,14 @@ parameters:
ci_git_email:
type: string
default: ""
description: Git author email for CD repo commits.
description: >
Deprecated and ignored. GitOps commits are created through the GitHub API,
signed by GitHub and authored by the GITHUB_TOKEN identity.
ci_git_name:
type: string
default: ""
description: Git author name for CD repo commits.
description: >
Deprecated and ignored. See ci_git_email.
supplemental_images:
type: string
default: "[]"
+49
View File
@@ -0,0 +1,49 @@
description: >
Build and scan application and supplemental images without publishing.
Requires the source and version metadata workspace from build-and-test.
parameters:
image_name:
type: string
description: Docker image name (without registry prefix).
registry:
type: string
default: ghcr.io/KofTwentyTwo
description: Container registry URL.
resource_class:
type: enum
enum:
- medium
- large
default: large
description: Machine resource class.
docker_layer_caching:
type: boolean
default: true
description: Enable Docker Layer Caching for faster builds.
supplemental_images:
type: string
default: "[]"
description: JSON array of supplemental images to build and scan.
executor:
name: machine
resource_class: << parameters.resource_class >>
docker_layer_caching: << parameters.docker_layer_caching >>
steps:
- attach_workspace:
at: .
- run:
name: Restore version metadata
command: <<include(scripts/restore_version_metadata.sh)>>
- generate_dockerfile
- docker_build:
image_name: << parameters.image_name >>
registry: << parameters.registry >>
- restore_trivy_cache
- run_trivy_image:
image_tag: << parameters.registry >>/<< parameters.image_name >>:${DOCKER_TAG}
- save_trivy_cache
- build_push_supplemental:
supplemental_images: << parameters.supplemental_images >>
image_name: << parameters.image_name >>
registry: << parameters.registry >>
push: false
+5 -2
View File
@@ -31,11 +31,14 @@ parameters:
ci_git_email:
type: string
default: ""
description: Git author email for CD repo commits.
description: >
Deprecated and ignored. GitOps commits are created through the GitHub API,
signed by GitHub and authored by the GITHUB_TOKEN identity.
ci_git_name:
type: string
default: ""
description: Git author name for CD repo commits.
description: >
Deprecated and ignored. See ci_git_email.
supplemental_images:
type: string
default: "[]"
+28 -8
View File
@@ -27,6 +27,19 @@ IMAGE="${IMAGE_NAME:?IMAGE_NAME not set}"
REGISTRY="${REGISTRY:-ghcr.io/KofTwentyTwo}"
VERSION="${PROJECT_VERSION:?PROJECT_VERSION not set -- run export_version_vars first}"
ENV_TAG="${DOCKER_ENV_TAG:-}"
# CircleCI renders a boolean orb parameter as "1"/"0" in a step environment, not
# "true"/"false", so accept both spellings. Unset means push (release builds).
case "$(echo "${PUSH_IMAGES-true}" | tr '[:upper:]' '[:lower:]')" in
true | 1 | yes | on) PUSH_IMAGES=true ;;
false | 0 | no | off | "") PUSH_IMAGES=false ;;
*)
echo "ERROR: PUSH_IMAGES must be true/false (or 1/0), got '${PUSH_IMAGES}'" >&2
exit 1
;;
esac
# run_trivy_image installs here in a previous CircleCI step.
export PATH="${HOME}/bin:${PATH}"
mkdir -p /tmp/trivy-supplemental-results
COUNT=$(echo "${IMAGES_JSON}" | jq 'length')
echo "Found ${COUNT} supplemental image(s) to build."
@@ -76,26 +89,33 @@ for i in $(seq 0 $((COUNT - 1))); do
fi
docker build "${BUILD_ARGS[@]}" .
if [[ -n "${ENV_TAG}" ]]; then
if [[ "${PUSH_IMAGES}" == "true" && -n "${ENV_TAG}" ]]; then
ENV_FULL_TAG="${REGISTRY}/${DERIVED_NAME}:${ENV_TAG}"
docker tag "${FULL_TAG}" "${ENV_FULL_TAG}"
fi
# Trivy scan
echo "Scanning ${FULL_TAG} with Trivy ..."
trivy image --severity HIGH,CRITICAL --exit-code 1 "${FULL_TAG}"
trivy image --format json --output "/tmp/trivy-supplemental-results/${NAME}.json" \
--severity HIGH,CRITICAL --exit-code 1 "${FULL_TAG}" || {
EXIT_CODE=$?
trivy image --severity HIGH,CRITICAL "${FULL_TAG}"
exit "${EXIT_CODE}"
}
# Push
echo "Pushing ${FULL_TAG} ..."
docker push "${FULL_TAG}"
if [[ "${PUSH_IMAGES}" == "true" ]]; then
echo "Pushing ${FULL_TAG} ..."
docker push "${FULL_TAG}"
if [[ -n "${ENV_TAG}" ]]; then
echo "Pushing ${ENV_FULL_TAG} ..."
docker push "${ENV_FULL_TAG}"
if [[ -n "${ENV_TAG}" ]]; then
echo "Pushing ${ENV_FULL_TAG} ..."
docker push "${ENV_FULL_TAG}"
fi
fi
echo "--- Done: ${DERIVED_NAME} ---"
done
echo ""
echo "All supplemental images built, scanned, and pushed."
echo "All supplemental images built and scanned (push=${PUSH_IMAGES})."
+10 -1
View File
@@ -23,7 +23,16 @@ munitor_header "docker_build (${FULL_IMAGE}:${VERSION})"
munitor_check_tool docker --version
# Build with version tag; add env tag when set (e.g., develop, staging)
BUILD_CMD=(docker build -f "${DOCKERFILE}" -t "${FULL_IMAGE}:${VERSION}")
BUILD_CMD=(docker build --pull -f "${DOCKERFILE}" -t "${FULL_IMAGE}:${VERSION}")
# Generated JVM images only copy a prebuilt JAR. Re-run their package upgrades:
# a cached apk upgrade layer can retain vulnerable packages indefinitely.
CONFIG_FILE="${MUNITOR_CONFIG:-.munitor.yml}"
if [[ -f "${CONFIG_FILE}" ]]; then
munitor_check_tool yq --version
case "$(yq -r '.pipeline' "${CONFIG_FILE}")" in
java-webapp|gradle-webapp) BUILD_CMD+=(--no-cache) ;;
esac
fi
if [[ -n "${ENV_TAG}" ]]; then
BUILD_CMD+=(-t "${FULL_IMAGE}:${ENV_TAG}")
fi
+1 -1
View File
@@ -97,7 +97,7 @@ validate_required_fields "${PIPELINE_TYPE}" "${CONFIG_FILE}"
# --------------------------------------------------------------------------
# Warn about unknown top-level keys (catches typos like java_verion)
# --------------------------------------------------------------------------
KNOWN_KEYS="pipeline orb_version image_name server_module java_version node_version sonar docker cd coverage e2e sbom owasp contexts npm node services test terraform sast health kustomize kube_linter_config package_manager yamllint_paths supplemental_images"
KNOWN_KEYS="pipeline orb_version org image_name server_module java_version node_version sonar docker cd coverage e2e sbom owasp contexts npm node services test terraform sast health kustomize kube_linter_config package_manager yamllint_paths supplemental_images"
ACTUAL_KEYS=$(yq 'keys | .[]' "${CONFIG_FILE}" 2>/dev/null || true)
for key in ${ACTUAL_KEYS}; do
if ! echo "${KNOWN_KEYS}" | grep -qw "${key}"; then
+57 -1
View File
@@ -366,6 +366,20 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/docker_scan:
name: image-scan
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
@@ -694,6 +708,20 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/docker_scan:
name: image-scan
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
@@ -1037,6 +1065,20 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/docker_scan:
name: image-scan
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
@@ -1406,6 +1448,20 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/docker_scan:
name: image-scan
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
@@ -3249,7 +3305,7 @@ validate_required_fields "${PIPELINE_TYPE}" "${CONFIG_FILE}"
# --------------------------------------------------------------------------
# Warn about unknown top-level keys (catches typos like java_verion)
# --------------------------------------------------------------------------
KNOWN_KEYS="pipeline orb_version image_name server_module java_version node_version sonar docker cd coverage e2e sbom owasp contexts npm node services test terraform sast health kustomize kube_linter_config package_manager yamllint_paths supplemental_images"
KNOWN_KEYS="pipeline orb_version org image_name server_module java_version node_version sonar docker cd coverage e2e sbom owasp contexts npm node services test terraform sast health kustomize kube_linter_config package_manager yamllint_paths supplemental_images"
ACTUAL_KEYS=$(yq 'keys | .[]' "${CONFIG_FILE}" 2>/dev/null || true)
for key in ${ACTUAL_KEYS}; do
if ! echo "${KNOWN_KEYS}" | grep -qw "${key}"; then
@@ -16,6 +16,20 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/docker_scan:
name: image-scan
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
+14
View File
@@ -16,6 +16,20 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/docker_scan:
name: image-scan
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
+14
View File
@@ -31,6 +31,20 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/docker_scan:
name: image-scan
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
+14
View File
@@ -31,6 +31,20 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/docker_scan:
name: image-scan
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
+135 -104
View File
@@ -68,128 +68,159 @@ git clone "https://x-access-token:${GITHUB_TOKEN}@github.com/${REPO}.git" "${CLO
cd "${CLONE_DIR}"
if [[ "${FORMAT}" == "kustomize" ]]; then
# --- Kustomize mode ---
if [[ -z "${IMAGE_NAME}" ]]; then
echo "ERROR: cd_image_name is required for Kustomize repos."
exit 1
fi
# Explicit paths support consolidated GitOps repositories. Existing dedicated
# repositories retain the overlays/<environment> convention by default.
if [[ -n "${CONFIGURED_PATH}" ]]; then
if [[ "${CONFIGURED_PATH}" == /* || "${CONFIGURED_PATH}" == *".."* || "${CONFIGURED_PATH}" != *kustomization.yaml ]]; then
echo "ERROR: cd.path must be a relative path to a kustomization.yaml file without '..'."
apply_updates() {
if [[ "${FORMAT}" == "kustomize" ]]; then
# --- Kustomize mode ---
if [[ -z "${IMAGE_NAME}" ]]; then
echo "ERROR: cd_image_name is required for Kustomize repos."
exit 1
fi
KUST_FILE="${CONFIGURED_PATH}"
elif [[ -n "${ENVIRONMENT}" ]]; then
KUST_FILE="overlays/${ENVIRONMENT}/kustomization.yaml"
# Explicit paths support consolidated GitOps repositories. Existing dedicated
# repositories retain the overlays/<environment> convention by default.
if [[ -n "${CONFIGURED_PATH}" ]]; then
if [[ "${CONFIGURED_PATH}" == /* || "${CONFIGURED_PATH}" == *".."* || "${CONFIGURED_PATH}" != *kustomization.yaml ]]; then
echo "ERROR: cd.path must be a relative path to a kustomization.yaml file without '..'."
exit 1
fi
KUST_FILE="${CONFIGURED_PATH}"
elif [[ -n "${ENVIRONMENT}" ]]; then
KUST_FILE="overlays/${ENVIRONMENT}/kustomization.yaml"
else
KUST_FILE="kustomization.yaml"
fi
if [[ ! -f "${KUST_FILE}" ]]; then
echo "ERROR: Kustomize file '${KUST_FILE}' not found in CD repo."
if [[ -z "${CONFIGURED_PATH}" ]]; then
echo "Available overlays:"
ls overlays/ 2>/dev/null || echo " (no overlays directory)"
fi
exit 1
fi
echo "Updating ${KUST_FILE}: ${IMAGE_NAME} -> ${VERSION}"
if ! yq -e ".images[] | select(.name == \"${IMAGE_NAME}\")" "${KUST_FILE}" >/dev/null; then
echo "ERROR: Image '${IMAGE_NAME}' is not declared in ${KUST_FILE}."
exit 1
fi
yq -i "(.images[] | select(.name == \"${IMAGE_NAME}\")).newTag = \"${VERSION}\"" "${KUST_FILE}"
yq -i "del((.images[] | select(.name == \"${IMAGE_NAME}\")).digest)" "${KUST_FILE}"
git add "${KUST_FILE}"
else
KUST_FILE="kustomization.yaml"
fi
# --- Helm mode (default) ---
echo "Updating ${KEY} -> ${VERSION}"
if [[ ! -f "${KUST_FILE}" ]]; then
echo "ERROR: Kustomize file '${KUST_FILE}' not found in CD repo."
if [[ -z "${CONFIGURED_PATH}" ]]; then
echo "Available overlays:"
ls overlays/ 2>/dev/null || echo " (no overlays directory)"
fi
exit 1
fi
echo "Updating ${KUST_FILE}: ${IMAGE_NAME} -> ${VERSION}"
if ! yq -e ".images[] | select(.name == \"${IMAGE_NAME}\")" "${KUST_FILE}" >/dev/null; then
echo "ERROR: Image '${IMAGE_NAME}' is not declared in ${KUST_FILE}."
exit 1
fi
yq -i "(.images[] | select(.name == \"${IMAGE_NAME}\")).newTag = \"${VERSION}\"" "${KUST_FILE}"
yq -i "del((.images[] | select(.name == \"${IMAGE_NAME}\")).digest)" "${KUST_FILE}"
git add "${KUST_FILE}"
else
# --- Helm mode (default) ---
echo "Updating ${KEY} -> ${VERSION}"
# Resolve environment-specific values path when CD_ENVIRONMENT is set
if [[ -n "${ENVIRONMENT}" ]]; then
ENV_VALUES="environments/${ENVIRONMENT}/${VALUES}"
if [[ -f "${ENV_VALUES}" ]]; then
VALUES="${ENV_VALUES}"
echo "Using environment path: ${VALUES}"
else
echo "Environment path '${ENV_VALUES}' not found, falling back to root '${VALUES}'"
fi
fi
if [[ ! -f "${VALUES}" ]]; then
echo "ERROR: Values file '${VALUES}' not found in CD repo."
exit 1
fi
yq -i ".${KEY} = \"${VERSION}\"" "${VALUES}"
git add "${VALUES}"
fi
# Update supplemental image tags (if any)
SUPP_JSON="${SUPPLEMENTAL_IMAGES_JSON:-[]}"
if [[ "${SUPP_JSON}" != "[]" && "${SUPP_JSON}" != "null" ]]; then
SUPP_COUNT=$(echo "${SUPP_JSON}" | jq 'length')
echo "Updating ${SUPP_COUNT} supplemental image tag(s)..."
for ((i = 0; i < SUPP_COUNT; i++)); do
SUPP_NAME=$(echo "${SUPP_JSON}" | jq -r ".[$i].name")
if [[ "${FORMAT}" == "kustomize" ]]; then
SUPP_CD_IMAGE=$(echo "${SUPP_JSON}" | jq -r ".[$i].cd.image_name // \"\"")
if [[ -z "${SUPP_CD_IMAGE}" ]]; then
SUPP_CD_IMAGE="${IMAGE_NAME}-${SUPP_NAME}"
fi
echo " Kustomize: ${SUPP_CD_IMAGE} -> ${VERSION}"
# Upsert: add entry if it doesn't exist, then update
EXISTING=$(yq ".images[] | select(.name == \"${SUPP_CD_IMAGE}\") | .name" "${KUST_FILE}")
if [[ -z "${EXISTING}" ]]; then
echo " (creating new image entry)"
yq -i ".images += [{\"name\": \"${SUPP_CD_IMAGE}\", \"newTag\": \"${VERSION}\"}]" "${KUST_FILE}"
# Resolve environment-specific values path when CD_ENVIRONMENT is set
if [[ -n "${ENVIRONMENT}" ]]; then
ENV_VALUES="environments/${ENVIRONMENT}/${VALUES}"
if [[ -f "${ENV_VALUES}" ]]; then
VALUES="${ENV_VALUES}"
echo "Using environment path: ${VALUES}"
else
yq -i "(.images[] | select(.name == \"${SUPP_CD_IMAGE}\")).newTag = \"${VERSION}\"" "${KUST_FILE}"
yq -i "del((.images[] | select(.name == \"${SUPP_CD_IMAGE}\")).digest)" "${KUST_FILE}"
fi
git add "${KUST_FILE}"
else
SUPP_IMAGE_KEY=$(echo "${SUPP_JSON}" | jq -r ".[$i].cd.image_key // \"\"")
if [[ -n "${SUPP_IMAGE_KEY}" ]]; then
echo " Helm: ${SUPP_IMAGE_KEY} -> ${VERSION}"
yq -i ".${SUPP_IMAGE_KEY} = \"${VERSION}\"" "${VALUES}"
git add "${VALUES}"
echo "Environment path '${ENV_VALUES}' not found, falling back to root '${VALUES}'"
fi
fi
done
fi
# Commit and push
git config user.email "${CI_GIT_EMAIL:-${MUNITOR_CI_EMAIL:-munitor-ci@koftwentytwo.com}}"
git config user.name "${CI_GIT_NAME:-${MUNITOR_CI_NAME:-Munitor CI}}"
if [[ ! -f "${VALUES}" ]]; then
echo "ERROR: Values file '${VALUES}' not found in CD repo."
exit 1
fi
if git diff --cached --quiet; then
echo "No changes to commit (version already up to date)."
exit 0
fi
yq -i ".${KEY} = \"${VERSION}\"" "${VALUES}"
git add "${VALUES}"
fi
git commit -m "chore: update ${IMAGE_NAME:-image} to ${VERSION}" -m "Source: ${CIRCLE_PROJECT_REPONAME:-unknown}@${CIRCLE_SHA1:-unknown}"
# Update supplemental image tags (if any)
SUPP_JSON="${SUPPLEMENTAL_IMAGES_JSON:-[]}"
if [[ "${SUPP_JSON}" != "[]" && "${SUPP_JSON}" != "null" ]]; then
SUPP_COUNT=$(echo "${SUPP_JSON}" | jq 'length')
echo "Updating ${SUPP_COUNT} supplemental image tag(s)..."
for ((i = 0; i < SUPP_COUNT; i++)); do
SUPP_NAME=$(echo "${SUPP_JSON}" | jq -r ".[$i].name")
if [[ "${FORMAT}" == "kustomize" ]]; then
SUPP_CD_IMAGE=$(echo "${SUPP_JSON}" | jq -r ".[$i].cd.image_name // \"\"")
if [[ -z "${SUPP_CD_IMAGE}" ]]; then
SUPP_CD_IMAGE="${IMAGE_NAME}-${SUPP_NAME}"
fi
echo " Kustomize: ${SUPP_CD_IMAGE} -> ${VERSION}"
# Upsert: add entry if it doesn't exist, then update
EXISTING=$(yq ".images[] | select(.name == \"${SUPP_CD_IMAGE}\") | .name" "${KUST_FILE}")
if [[ -z "${EXISTING}" ]]; then
echo " (creating new image entry)"
yq -i ".images += [{\"name\": \"${SUPP_CD_IMAGE}\", \"newTag\": \"${VERSION}\"}]" "${KUST_FILE}"
else
yq -i "(.images[] | select(.name == \"${SUPP_CD_IMAGE}\")).newTag = \"${VERSION}\"" "${KUST_FILE}"
yq -i "del((.images[] | select(.name == \"${SUPP_CD_IMAGE}\")).digest)" "${KUST_FILE}"
fi
git add "${KUST_FILE}"
else
SUPP_IMAGE_KEY=$(echo "${SUPP_JSON}" | jq -r ".[$i].cd.image_key // \"\"")
if [[ -n "${SUPP_IMAGE_KEY}" ]]; then
echo " Helm: ${SUPP_IMAGE_KEY} -> ${VERSION}"
yq -i ".${SUPP_IMAGE_KEY} = \"${VERSION}\"" "${VALUES}"
git add "${VALUES}"
fi
fi
done
fi
}
# Commit through the GitHub GraphQL API (createCommitOnBranch) rather than pushing local commits.
# GitHub signs API-created commits, so they show as Verified and satisfy a
# "require signed commits" ruleset on the GitOps branch without a bypass actor.
# The commit author is the GITHUB_TOKEN identity; the source build is recorded
# in the commit body. expectedHeadOid makes concurrent updates fail cleanly and
# retry against the new head instead of overwriting each other.
BRANCH="$(git branch --show-current)"
HEADLINE="chore: update ${IMAGE_NAME:-image} to ${VERSION}"
MESSAGE_BODY="Source: ${CIRCLE_PROJECT_REPONAME:-unknown}@${CIRCLE_SHA1:-unknown}"
commit_via_api() {
local head_oid additions payload response file
head_oid="$(git rev-parse HEAD)"
additions='[]'
while IFS= read -r file; do
base64 -w0 "${file}" > /tmp/cd-file.b64
additions="$(jq --arg path "${file}" --rawfile contents /tmp/cd-file.b64 '. + [{path: $path, contents: $contents}]' <<< "${additions}")"
done < <(git diff --cached --name-only)
payload="$(jq -n --arg repo "${REPO}" --arg branch "${BRANCH}" --arg oid "${head_oid}" --arg headline "${HEADLINE}" --arg body "${MESSAGE_BODY}" --argjson additions "${additions}" '{query: "mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid url } } }",
variables: {input: {branch: {repositoryNameWithOwner: $repo, branchName: $branch},
expectedHeadOid: $oid, message: {headline: $headline, body: $body},
fileChanges: {additions: $additions}}}}')"
if ! response="$(curl -sS -X POST -H "Authorization: bearer ${GITHUB_TOKEN}" -H "Content-Type: application/json" https://api.github.com/graphql --data @- <<< "${payload}")"; then
echo "GraphQL request failed."
return 1
fi
if [[ "$(jq -r '.data.createCommitOnBranch.commit.oid // empty' <<< "${response}")" == "" ]]; then
echo "GraphQL commit rejected: $(jq -c '.errors // .' <<< "${response}")"
return 1
fi
echo "Signed commit: $(jq -r '.data.createCommitOnBranch.commit.url' <<< "${response}")"
}
# Independent pipelines may update a shared GitOps repository concurrently.
for attempt in 1 2 3; do
if git push origin HEAD; then
apply_updates
if git diff --cached --quiet; then
echo "No changes to commit (version already up to date)."
exit 0
fi
if commit_via_api; then
echo "CD repo updated successfully."
exit 0
fi
if [[ "${attempt}" == "3" ]]; then
echo "ERROR: Could not push the GitOps update after ${attempt} attempts."
echo "ERROR: Could not commit the GitOps update after ${attempt} attempts."
exit 1
fi
echo "Push raced with another GitOps update; rebasing before retry ${attempt}/3."
git pull --rebase origin "$(git branch --show-current)"
echo "Commit raced with another GitOps update; refreshing before retry ${attempt}/3."
git fetch -q origin "${BRANCH}"
git reset -q --hard "origin/${BRANCH}"
done
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bats
@test "PR and release JVM builds refresh packages without disabling Node caching or Trivy" {
run bash "${BATS_TEST_DIRNAME}/test_docker_build.sh"
[ "$status" -eq 0 ]
}
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Assert the actual shared PR/release build command without a Docker daemon.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
WORK=$(mktemp -d)
trap 'rm -rf "${WORK}"' EXIT
mkdir -p "${WORK}/bin" "${WORK}/repo"
cat > "${WORK}/bin/docker" <<'MOCK'
#!/usr/bin/env bash
[[ "$1" == --version ]] && exit 0
printf '%s\n' "$@" > "${CALL_LOG}"
MOCK
cat > "${WORK}/bin/yq" <<'MOCK'
#!/usr/bin/env bash
[[ "$1" == --version ]] && exit 0
[[ "$1" == -r && "$2" == .pipeline ]] || exit 2
sed -n 's/^pipeline: //p' "$3"
MOCK
chmod +x "${WORK}/bin/docker" "${WORK}/bin/yq"
export PATH="${WORK}/bin:${PATH}"
export CALL_LOG="${WORK}/calls" BASH_ENV="${WORK}/env"
export IMAGE_NAME=app REGISTRY=ghcr.io/example PROJECT_VERSION=1.2.3
export CIRCLE_SHA1=abc123 DOCKER_ENV_TAG=develop
cd "${WORK}/repo"
for pipeline in java-webapp gradle-webapp node-api node-webapp; do
printf 'pipeline: %s\n' "${pipeline}" > .munitor.yml
bash "${PROJECT_DIR}/src/scripts/docker_build.sh" > "${WORK}/output"
grep -qx -- '--pull' "${CALL_LOG}"
if [[ "${pipeline}" == java-webapp || "${pipeline}" == gradle-webapp ]]; then
grep -qx -- '--no-cache' "${CALL_LOG}"
else
! grep -qx -- '--no-cache' "${CALL_LOG}"
fi
grep -qx 'org.opencontainers.image.revision=abc123' "${CALL_LOG}"
grep -qx 'ghcr.io/example/app:1.2.3' "${CALL_LOG}"
grep -qx 'ghcr.io/example/app:develop' "${CALL_LOG}"
echo "PASS: ${pipeline} cache policy, provenance and tags"
done
mv .munitor.yml 'custom config.yml'
printf 'pipeline: java-webapp\n' > 'custom config.yml'
MUNITOR_CONFIG='custom config.yml' bash "${PROJECT_DIR}/src/scripts/docker_build.sh" > "${WORK}/output"
grep -qx -- '--no-cache' "${CALL_LOG}"
echo 'PASS: alternate manifest path'
bash "${PROJECT_DIR}/src/scripts/docker_build.sh" > "${WORK}/output"
grep -qx -- '--pull' "${CALL_LOG}"
! grep -qx -- '--no-cache' "${CALL_LOG}"
echo 'PASS: standalone build without manifest'
for job in docker_scan docker_build_push; do
grep -q '^ - docker_build:' "${PROJECT_DIR}/src/jobs/${job}.yml"
grep -q '^ - run_trivy_image:' "${PROJECT_DIR}/src/jobs/${job}.yml"
echo "PASS: ${job} uses shared builder and retains Trivy"
done
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
# Exercise image scanning with local tool stubs; no Docker daemon or network needed.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SRC_SCRIPTS="$(cd "${SCRIPT_DIR}/../src/scripts" && pwd)"
WORK=$(mktemp -d)
trap 'rm -rf "${WORK}"' EXIT
PASS=0
FAIL=0
pass() { echo "PASS"; PASS=$((PASS + 1)); }
fail() { echo "FAIL"; FAIL=$((FAIL + 1)); }
mkdir -p "${WORK}/bin" "${WORK}/home/bin" "${WORK}/repo"
cat > "${WORK}/bin/docker" <<'EOF'
#!/usr/bin/env bash
printf 'docker %s\n' "$*" >> "${CALL_LOG}"
EOF
cat > "${WORK}/home/bin/trivy" <<'EOF'
#!/usr/bin/env bash
[[ -f .trivyignore ]] || exit 99
printf 'trivy %s\n' "$*" >> "${CALL_LOG}"
# Redirect the production /tmp output path into the test workspace.
while [[ $# -gt 0 ]]; do
if [[ "$1" == --output ]]; then
printf '{"Results":[]}\n' > "${REPORT_DIR}/$(basename "$2")"
fi
shift
done
exit "${SCAN_EXIT:-0}"
EOF
chmod +x "${WORK}/bin/docker" "${WORK}/home/bin/trivy"
export PATH="${WORK}/bin:${PATH}" HOME="${WORK}/home"
export CALL_LOG="${WORK}/calls" REPORT_DIR="${WORK}"
export IMAGE_NAME=app REGISTRY=ghcr.io/example CIRCLE_SHA1=abcdef123456
export SUPPLEMENTAL_IMAGES_JSON='[{"name":"migrations","dockerfile":"Dockerfile.migrations"},{"name":"worker","base":"example/base:1"}]'
cd "${WORK}/repo"
touch .trivyignore
echo "=== PR image scan tests ==="
echo -n " TEST: feature metadata survives workspace restore and supplies Docker scan tag... "
export BASH_ENV="${WORK}/build-env" CIRCLE_BRANCH=feature/image-scan
export GITVERSION_MAJOR=1 GITVERSION_MINOR=2 GITVERSION_PATCH=3
export GITVERSION_SHA=abcdef123456
bash "${SRC_SCRIPTS}/export_version_vars.sh" > "${WORK}/version.log"
bash "${SRC_SCRIPTS}/persist_version_metadata.sh" >> "${WORK}/version.log"
export BASH_ENV="${WORK}/scan-env"
bash "${SRC_SCRIPTS}/restore_version_metadata.sh" >> "${WORK}/version.log"
bash "${SRC_SCRIPTS}/docker_build.sh" >> "${WORK}/version.log"
# shellcheck disable=SC1090
source "${BASH_ENV}"
export IMAGE_TAG='ghcr.io/example/app:${DOCKER_TAG}'
if bash "${SRC_SCRIPTS}/run_trivy_image.sh" >> "${WORK}/version.log" &&
grep -q 'trivy image .*--severity HIGH,CRITICAL --exit-code 1 ghcr.io/example/app:1.2.3-image-scan-abcdef123456-SNAPSHOT' "${CALL_LOG}"; then
pass
else
fail
fi
echo -n " TEST: push=false builds and scans every supplemental without publishing... "
: > "${CALL_LOG}"
echo 'export DOCKER_ENV_TAG=develop' >> "${BASH_ENV}"
if PUSH_IMAGES=false bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
[[ $(grep -c '^docker build ' "${CALL_LOG}") == 2 ]] &&
[[ $(grep -c '^trivy image .*--severity HIGH,CRITICAL --exit-code 1 ' "${CALL_LOG}") == 2 ]] &&
! grep -Eq '^docker (push|login|tag|buildx) ' "${CALL_LOG}" &&
[[ -s "${WORK}/migrations.json" && -s "${WORK}/worker.json" ]]; then
pass
else
fail
fi
echo -n " TEST: default push still publishes version and environment tags... "
: > "${CALL_LOG}"
unset PUSH_IMAGES
if bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
[[ $(grep -c '^docker push ' "${CALL_LOG}") == 4 ]]; then
pass
else
fail
fi
# Regression (0.3.5): CircleCI renders the boolean "push" parameter as "1"/"0" in
# the step environment, and release builds silently skipped the push.
for push in 1 true TRUE; do
echo -n " TEST: push=${push} (release builds) publishes every supplemental image... "
: > "${CALL_LOG}"
if PUSH_IMAGES="${push}" bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
[[ $(grep -c '^docker push ' "${CALL_LOG}") == 4 ]] &&
grep -q '^docker push ghcr.io/example/app-migrations:' "${CALL_LOG}" &&
grep -q '^docker push ghcr.io/example/app-worker:' "${CALL_LOG}" &&
grep -q 'push=true' "${WORK}/supplemental.log"; then
pass
else
fail
fi
done
for push in 0 false ""; do
echo -n " TEST: push='${push}' (PR scans) publishes nothing... "
: > "${CALL_LOG}"
if PUSH_IMAGES="${push}" bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
! grep -Eq '^docker (push|tag) ' "${CALL_LOG}"; then
pass
else
fail
fi
done
echo -n " TEST: an unrecognised push value fails instead of guessing... "
: > "${CALL_LOG}"
if PUSH_IMAGES=maybe bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" 2>&1; then
fail
elif ! grep -Eq '^docker (build|tag|push) ' "${CALL_LOG}" && grep -q "must be true/false" "${WORK}/supplemental.log"; then
pass
else
fail
fi
for push in false true; do
echo -n " TEST: push=${push} fails on HIGH/CRITICAL findings before any push... "
: > "${CALL_LOG}"
rm -f "${WORK}/migrations.json"
if SCAN_EXIT=1 PUSH_IMAGES="${push}" bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" 2>&1; then
fail
elif ! grep -q '^docker push ' "${CALL_LOG}" && [[ -s "${WORK}/migrations.json" ]]; then
pass
else
fail
fi
done
echo "Results: ${PASS} passed, ${FAIL} failed"
[[ ${FAIL} -eq 0 ]]
+30
View File
@@ -157,6 +157,36 @@ run_negative_context_test() {
echo "=== Template Rendering Tests ==="
# Container scans belong only to PR jobs; RC already builds and scans before release.
for module in java-webapp gradle-webapp node-api node-webapp; do
fixture="${FIXTURES_DIR}/${module}.munitor.yml"
run_context_test "${module}: PR builds and scans the image" \
"${fixture}" "pr-checks:" " develop:" 'munitor/docker_scan:'
run_context_test "${module}: image scan is named image-scan" \
"${fixture}" "munitor/docker_scan:" "munitor/secrets_scan:" 'name: image-scan'
run_context_test "${module}: image scan waits for build-and-test" \
"${fixture}" "munitor/docker_scan:" "munitor/secrets_scan:" '\- build-and-test'
run_negative_context_test "${module}: no duplicate scan outside PR workflow" \
"${fixture}" " develop:" "END_OF_CONFIG" 'munitor/docker_scan:'
run_context_test "${module}: RC already builds and scans" \
"${fixture}" "release-candidate:" "production:" 'munitor/docker_build_push:'
echo -n " TEST: ${module}: every image scan has exactly the PR branch filters... "
output=$(render_fixture "${fixture}")
filters=$(yq -o=json -I=0 '[.workflows[].jobs[] | select(has("munitor/docker_scan")) | ."munitor/docker_scan".filters.branches.only]' <<< "${output}")
if [[ "${filters}" == '[["/feature\\/.*/","/hotfix\\/.*/"]]' ]]; then
echo "PASS"
PASS=$((PASS + 1))
else
echo "FAIL (unexpected scan filters: ${filters})"
FAIL=$((FAIL + 1))
fi
done
run_context_test "supplemental images are wired into the PR image scan" \
"${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml" \
"munitor/docker_scan:" "munitor/secrets_scan:" 'supplemental_images:.*migrations'
# Test java-webapp renders with correct values
run_test "java-webapp: renders image_name" \
"${FIXTURES_DIR}/java-webapp.munitor.yml" \
+12 -3
View File
@@ -328,12 +328,21 @@ else
fail "should support and validate an explicit Kustomize path"
fi
echo -n " TEST: retries concurrent shared-repository pushes... "
echo -n " TEST: retries concurrent shared-repository commits against the new head... "
if grep -q 'for attempt in 1 2 3' "${SRC_SCRIPTS}/update_cd_repo.sh" &&
grep -q 'git pull --rebase' "${SRC_SCRIPTS}/update_cd_repo.sh"; then
grep -q 'expectedHeadOid' "${SRC_SCRIPTS}/update_cd_repo.sh" &&
grep -q 'git reset -q --hard "origin/${BRANCH}"' "${SRC_SCRIPTS}/update_cd_repo.sh"; then
pass
else
fail "should rebase and retry shared-repository push races"
fail "should retry commit races against the refreshed branch head"
fi
echo -n " TEST: commits through the signed GraphQL API, never git push... "
if grep -q 'createCommitOnBranch' "${SRC_SCRIPTS}/update_cd_repo.sh" &&
! grep -q 'git push' "${SRC_SCRIPTS}/update_cd_repo.sh"; then
pass
else
fail "should create GitOps commits with createCommitOnBranch so GitHub signs them"
fi
echo -n " TEST: deletes digest field when setting newTag (kustomize)... "