Compare commits
86
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77cdf931c2
|
||
|
|
d7c278b344
|
||
|
|
c8fd974bfd
|
||
|
|
6cd50b0a90 | ||
|
|
5d4a9ea420 | ||
|
|
f402fb8347
|
||
|
|
11d5704b47 | ||
|
|
576de90e03
|
||
|
|
9678be0cff | ||
|
|
baca55bd77 | ||
|
|
01176862d4
|
||
|
|
f7487b356a | ||
|
|
21517b25e7
|
||
|
|
34ddd25ac9 | ||
|
|
4aeccb801e
|
||
|
|
a3d680f130 | ||
|
|
57dffdd9a3 | ||
|
|
9e2c45e79a | ||
|
|
fbeec65cb5 | ||
|
|
69898922be
|
||
|
|
59eb5294fe | ||
|
|
16079c5ca8 | ||
|
|
578c7ea0eb | ||
|
|
53c04937b0 | ||
|
|
b09ced056f
|
||
|
|
fb232fb6fe | ||
|
|
687944e872 | ||
|
|
e54d739554 | ||
|
|
71c262fb62
|
||
|
|
6cbe4256d5 | ||
|
|
d7960161fb
|
||
|
|
807120f2af
|
||
|
|
1c64d1e18e
|
||
|
|
24482fd030
|
||
|
|
ad1d30681b | ||
|
|
b4a9d120eb
|
||
|
|
b3b90f5886 | ||
|
|
d9b4da9e9e | ||
|
|
4b99bbec09
|
||
|
|
00f349cb0c | ||
|
|
57cb7a2aca
|
||
|
|
f1d26b69d7 | ||
|
|
ac3eb0e162 | ||
|
|
50ae000759 | ||
|
|
b357559760 | ||
|
|
c8bbf2ef39 | ||
|
|
ae7d93328f | ||
|
|
d2e31ed001 | ||
|
|
e84008ee19 | ||
|
|
d0ce5266ee | ||
|
|
9c487cfa04 | ||
|
|
836cb20c5b | ||
|
|
1dc88ef564 | ||
|
|
db5e076569 | ||
|
|
0fcd218b9f | ||
|
|
e1433d913b | ||
|
|
15d436a8fa | ||
|
|
4b11b3acc8 | ||
|
|
894a550312 | ||
|
|
b83dc90cc6 | ||
|
|
e408693ff5 | ||
|
|
c6e993677b | ||
|
|
7519d62b48 | ||
|
|
6de8e9998d | ||
|
|
4fbae520ee | ||
|
|
f560062cc3 | ||
|
|
452a59fe89 | ||
|
|
11a142b353 | ||
|
|
c795fbdc12 | ||
|
|
86b72f4e3b | ||
|
|
f1d3bf674e | ||
|
|
facd9691c4 | ||
|
|
84ceae9e89 | ||
|
|
270325371e | ||
|
|
c146ff0bb8 | ||
|
|
9f862b1ccd | ||
|
|
05aac6bbea | ||
|
|
5550411187 | ||
|
|
3fa00dc9a5 | ||
|
|
90bcb49ca9 | ||
|
|
7acb976d16 | ||
|
|
9ac1e09a0d | ||
|
|
7527751532 | ||
|
|
1e558c18af | ||
|
|
70917472e9 | ||
|
|
e262339fa7 |
No files matched your search
+54
-9
@@ -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,36 +49,55 @@ 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:
|
||||
- orb-tools/pack:
|
||||
filters: *tag-only
|
||||
- orb-tools/publish:
|
||||
orb_name: kof22/munitor
|
||||
vcs_type: << pipeline.project.type >>
|
||||
pub_type: production
|
||||
requires:
|
||||
- orb-tools/lint
|
||||
- orb-tools/pack
|
||||
- orb-tools/review
|
||||
- test
|
||||
context:
|
||||
- orb-publishing
|
||||
filters: *tag-only
|
||||
|
||||
snapshot:
|
||||
jobs:
|
||||
- orb-tools/pack:
|
||||
filters: *filters
|
||||
# All publishers consume the same packed workspace after all checks pass.
|
||||
# Only develop updates dev:snapshot; feature branches publish dev:<sha>.
|
||||
- orb-tools/publish:
|
||||
name: publish-develop-snapshot
|
||||
orb_name: kof22/munitor
|
||||
vcs_type: << pipeline.project.type >>
|
||||
pub_type: dev
|
||||
dev_tags: "dev:${CIRCLE_SHA1},dev:snapshot"
|
||||
requires:
|
||||
- orb-tools/lint
|
||||
- orb-tools/pack
|
||||
- orb-tools/review
|
||||
- test
|
||||
context:
|
||||
- orb-publishing
|
||||
filters:
|
||||
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/lint
|
||||
- orb-tools/pack
|
||||
- orb-tools/review
|
||||
- test
|
||||
context:
|
||||
- orb-publishing
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
*.sh text eol=lf
|
||||
*.bats text eol=lf
|
||||
@@ -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
|
||||
+2
-5
@@ -15,8 +15,5 @@ node_modules/
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
docs/TODO.md
|
||||
docs/PLAN-*.md
|
||||
docs/SESSION-STATE.md
|
||||
# Claude Code (keep settings.local.json out -- machine-specific permissions)
|
||||
.claude/settings.local.json
|
||||
@@ -0,0 +1,16 @@
|
||||
# Repository instructions
|
||||
|
||||
Read `CLAUDE.md`, `CONTRIBUTING.md`, `docs/SESSION-STATE.md`, and `docs/TODO.md`
|
||||
before substantive work. Munitor is a CircleCI orb: source changes belong under
|
||||
`src/`, generated packed scripts must remain synchronized, and consumer behavior
|
||||
must be covered by BATS fixtures and assertions.
|
||||
|
||||
Use feature branches targeting `develop`; `main` is release-only. Run `make all`
|
||||
before opening a pull request and `make validate` when the CircleCI CLI is
|
||||
available. Publish development snapshots from feature or `develop`; publish
|
||||
stable orb versions only from semantic version tags on `main`.
|
||||
|
||||
For deployment changes, preserve the established contract: repository-owned
|
||||
Dockerfiles are opt-in, consolidated GitOps paths are repository-relative
|
||||
`kustomization.yaml` files, and `production_only` prevents non-main write-back.
|
||||
Never put credentials in manifests, fixtures, logs, or documentation.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Munitor
|
||||
|
||||
CircleCI orb that generates CI/CD pipeline configurations from a `.munitor.yml` manifest. Supports 8 pipeline types: java-webapp, gradle-webapp, node-api, node-webapp, terraform, validate-cd-repo, sdk-distribution, argocd-apps.
|
||||
|
||||
## Key Commands
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `src/commands/` - CircleCI orb commands (YAML)
|
||||
- `src/jobs/` - CircleCI orb jobs (YAML)
|
||||
- `src/scripts/` - Shell scripts included by commands/jobs
|
||||
- `src/scripts/templates/` - Pipeline config templates (`.yml.tpl`)
|
||||
- `src/scripts/templates/partials/` - Include partials for templates
|
||||
- `tests/` - BATS test files
|
||||
- `tests/fixtures/` - Test fixture `.munitor.yml` files
|
||||
|
||||
## Issue Tracker
|
||||
|
||||
GitHub Issues (org: KofTwentyTwo). Commit format: `feat(#N): description` or `Closes #N` in body.
|
||||
|
||||
## CircleCI Orb
|
||||
|
||||
- Namespace: `kof22`
|
||||
- Orb: `kof22/munitor`
|
||||
- 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). `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
|
||||
|
||||
- Session state: `docs/SESSION-STATE.md`
|
||||
- Active tasks: `docs/TODO.md`
|
||||
- Plans: `docs/PLAN-*.md`
|
||||
|
||||
Read `AGENTS.md` and `CONTRIBUTING.md` before changing orb behavior. Feature
|
||||
branches target `develop`; release branches promote to `main`, and stable orb
|
||||
publication is driven by a `vX.Y.Z` tag.
|
||||
+11
-1
@@ -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.
|
||||
@@ -66,7 +75,7 @@ tests/fixtures/
|
||||
g. Validates output YAML
|
||||
|
||||
3. CircleCI continuation runs the generated pipeline, which references
|
||||
munitor orb jobs/commands by name (e.g., munitor/npm_build_and_test)
|
||||
munitor orb jobs/commands by name (e.g., munitor/node_build_and_test)
|
||||
```
|
||||
|
||||
## Adding a New Module
|
||||
@@ -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`) |
|
||||
|
||||
@@ -30,7 +30,7 @@ lint:
|
||||
test-scripts:
|
||||
@echo "Running script tests..."
|
||||
@for f in tests/test_*.sh; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
if [ -f "$$f" ] && [ "$$f" != "tests/test_generate_config.sh" ]; then \
|
||||
echo " Running $$f"; \
|
||||
bash "$$f" || exit 1; \
|
||||
fi; \
|
||||
|
||||
@@ -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.8` |
|
||||
| **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.
|
||||
@@ -20,7 +28,7 @@ This file is identical across all repos:
|
||||
version: 2.1
|
||||
setup: true
|
||||
orbs:
|
||||
munitor: kof22/munitor@1
|
||||
munitor: kof22/munitor@0.3
|
||||
workflows:
|
||||
setup:
|
||||
jobs:
|
||||
@@ -53,11 +61,98 @@ Munitor validates branch names and will fail on non-conforming names.
|
||||
|
||||
| Context | Secrets | Used By |
|
||||
|---------|---------|---------|
|
||||
| `ghcr` | `GHCR_TOKEN`, `GHCR_USER` | Docker push to GitHub Container Registry |
|
||||
| `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.8` (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.8`.
|
||||
- `.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.8"
|
||||
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`
|
||||
@@ -68,7 +163,7 @@ For Node.js/Next.js applications with Docker deployment.
|
||||
|
||||
```yaml
|
||||
pipeline: node-api
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
image_name: my-app
|
||||
docker:
|
||||
registry: ghcr.io/KofTwentyTwo
|
||||
@@ -81,7 +176,7 @@ contexts:
|
||||
|
||||
```yaml
|
||||
pipeline: node-api
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
image_name: my-app
|
||||
node_version: "22" # default: 20
|
||||
|
||||
@@ -145,10 +240,11 @@ For Node.js applications that provide their own Dockerfile (unlike `node-api` wh
|
||||
|
||||
```yaml
|
||||
pipeline: node-webapp
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
image_name: my-webapp
|
||||
docker:
|
||||
registry: ghcr.io/KofTwentyTwo
|
||||
use_repo_dockerfile: true
|
||||
contexts:
|
||||
registry: ghcr
|
||||
github: github
|
||||
@@ -158,7 +254,7 @@ contexts:
|
||||
|
||||
```yaml
|
||||
pipeline: node-webapp
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
image_name: my-webapp
|
||||
node_version: "22" # default: 20
|
||||
|
||||
@@ -167,9 +263,13 @@ sonar:
|
||||
|
||||
docker:
|
||||
registry: ghcr.io/KofTwentyTwo
|
||||
use_repo_dockerfile: true # preserve the repository Dockerfile
|
||||
|
||||
cd:
|
||||
repo: KofTwentyTwo/my-webapp-cd # omit to skip GitOps CD updates
|
||||
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)
|
||||
|
||||
@@ -212,7 +312,7 @@ contexts:
|
||||
sonar: sonarcloud
|
||||
```
|
||||
|
||||
**Dockerfile:** You must provide your own `Dockerfile` in the repo root. Munitor builds it, runs a health check, scans with Trivy, and pushes to the registry. This is the key difference from `node-api`, which auto-generates a Dockerfile.
|
||||
**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`
|
||||
|
||||
@@ -222,7 +322,7 @@ For Java/Maven applications with Docker deployment.
|
||||
|
||||
```yaml
|
||||
pipeline: java-webapp
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
image_name: my-service
|
||||
docker:
|
||||
registry: ghcr.io/KofTwentyTwo
|
||||
@@ -236,7 +336,7 @@ contexts:
|
||||
|
||||
```yaml
|
||||
pipeline: java-webapp
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
image_name: my-service
|
||||
java_version: "21" # default: 21
|
||||
|
||||
@@ -272,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`
|
||||
|
||||
@@ -282,7 +382,7 @@ For Terraform/Terragrunt infrastructure repos.
|
||||
|
||||
```yaml
|
||||
pipeline: terraform
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
terraform:
|
||||
path: terraform/
|
||||
```
|
||||
@@ -291,7 +391,7 @@ terraform:
|
||||
|
||||
```yaml
|
||||
pipeline: terraform
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
terraform:
|
||||
path: terraform/ # default: terraform/
|
||||
live_path: terraform/live # default: terraform/live
|
||||
@@ -313,14 +413,14 @@ For CD/GitOps repositories containing Kubernetes manifests, Kustomize overlays,
|
||||
|
||||
```yaml
|
||||
pipeline: validate-cd-repo
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
```
|
||||
|
||||
**Full config:**
|
||||
|
||||
```yaml
|
||||
pipeline: validate-cd-repo
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
|
||||
kustomize:
|
||||
version: "5.5.0" # default: 5.5.0
|
||||
@@ -345,14 +445,14 @@ For ArgoCD app-of-apps repositories that use environment directories (`envs/`) i
|
||||
|
||||
```yaml
|
||||
pipeline: argocd-apps
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
```
|
||||
|
||||
**Full config:**
|
||||
|
||||
```yaml
|
||||
pipeline: argocd-apps
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
|
||||
kustomize:
|
||||
version: "5.5.0" # default: 5.5.0
|
||||
@@ -377,7 +477,7 @@ For SDK packaging and GitHub Releases. Triggered by semver tags (`v1.2.3`).
|
||||
|
||||
```yaml
|
||||
pipeline: sdk-distribution
|
||||
orb_version: dev:snapshot
|
||||
orb_version: "0.3"
|
||||
contexts:
|
||||
github: github
|
||||
```
|
||||
@@ -390,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) |
|
||||
@@ -398,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
|
||||
@@ -411,7 +520,19 @@ checkout
|
||||
--> 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 auto-creates an annotated git tag and GitHub Release using the version calculated by GitVersion.
|
||||
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
|
||||
|
||||
@@ -459,14 +580,28 @@ 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 |
|
||||
| `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 |
|
||||
@@ -508,7 +643,7 @@ Default endpoint: `GET /api/health` on port 3000 (node-api, node-webapp) or 8080
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Missing required fields: orb_version"** -- Add `orb_version` to your `.munitor.yml`. Use `dev:snapshot` for pre-release testing or `1` once a stable release is published.
|
||||
**"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.
|
||||
|
||||
@@ -561,3 +696,31 @@ Pipeline generation flow:
|
||||
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)
|
||||
+56
@@ -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).
|
||||
@@ -0,0 +1,41 @@
|
||||
# Fast Java CI
|
||||
|
||||
Backend ticket work is paused while this performance change is delivered.
|
||||
|
||||
## Measured baseline
|
||||
|
||||
Website-Backend PR311, CircleCI jobs6314–6318 on2026-10-19:
|
||||
|
||||
- Build/test job:30m15s. Compilation49s; tests14m24s; Java installation4m27s.
|
||||
- Maven cache48GiB: restore5m32s, save4m19s.
|
||||
- Quality, coverage and security each restore the same48GiB again (5–6min).
|
||||
- Coverage computation itself takes5.4s. Quality tools take57s combined.
|
||||
- OWASP is disabled in this consumer, but security still installs Java and restores Maven.
|
||||
- Test results are uploaded by both the test command and its enclosing job:777 results appear twice.
|
||||
|
||||
Evidence: coordinator cache/cicd-baseline.json, cicd-test-timings.json and cicd-github-friction.md.
|
||||
|
||||
## First implementation
|
||||
|
||||
1. Replace the old broad Maven fallback with an exact v2 key based on checked-out dependency definitions. Keep that key stable across release-version rewrites and invalidate for nested POM or Maven configuration changes. Never restore the48GiB v1 cache.
|
||||
2. Skip Java/Maven/NVD setup when OWASP is disabled; preserve Trivy.
|
||||
3. Upload Maven test results once.
|
||||
4. Pack the orb once per pipeline; publish only after its existing checks pass, preserving branch/tag behavior and required check names.
|
||||
5. Run template tests only in their dedicated Make target, rather than also discovering them in the generic script loop.
|
||||
6. Run Checkstyle, SpotBugs, PMD and coverage on the existing build worker for generated PR/develop/staging/release workflows. Keep the standalone jobs available for custom consumers and preserve production behavior. Generated downstream jobs depend on the completed build gate.
|
||||
7. Render each immutable test fixture once per test run; retain all277 assertions.
|
||||
|
||||
Maven caches are dependency reuse, not artifact handoff. A real three-module experiment failed SpotBugs with current target outputs and an older installed-module cache. Preserve locally installed version directories, mutable SNAPSHOT dependencies and ancestor metadata in a workspace archive, then overlay it after dependency-cache restore. The experiment passes for release versions, snapshots and classifier dependencies. Maven Resolver identifies local installs with empty repository IDs in `_remote.repositories`; this implementation depends on that Maven3 format. Do not replace this archive with the entire local repository.
|
||||
|
||||
Cold dependency downloads on changed POMs are deliberate: no cross-version fallback accumulation. Do not delete caches, artifacts, tags or change branch protections.
|
||||
|
||||
## Verification and rollout
|
||||
|
||||
- Focused behavioral/config regressions, then required make all and BATS suites once the combined patch is complete.
|
||||
- Publish immutable feature orb; run a backend feature canary with both pins matching that SHA.
|
||||
- Compare real cache size and build/check durations against the baseline. No claimed speedup before measurement; do not merge the consumer until tests and all existing security gates pass.
|
||||
- Release the proven orb and adopt its stable version through normal PRs. Track the already-running backend release separately; do not resume paused ticket work.
|
||||
|
||||
## Follow-up decisions after measurement
|
||||
|
||||
If tests dominate after cache repair, inspect measured slow fixtures and isolate/parallelize only tests proven independent. Exact-source artifact promotion is a separate future change requiring verified source identity; do not skip merged-source validation on the strength of an earlier PR run. Avoid a new orchestration framework or speculative configuration options.
|
||||
@@ -0,0 +1,13 @@
|
||||
# PLAN: Preserve node-webapp Dockerfiles
|
||||
|
||||
## Goal
|
||||
|
||||
Allow `node-webapp` consumers to preserve a repository-owned Dockerfile without breaking existing generated-image consumers.
|
||||
|
||||
## Steps
|
||||
|
||||
1. [x] Add an opt-in `docker.use_repo_dockerfile` manifest field.
|
||||
2. [x] Fail clearly when an opted-in repository has no root Dockerfile.
|
||||
3. [x] Add regression coverage and update the public reference.
|
||||
4. [ ] Pack, validate, test, and publish the development orb.
|
||||
5. [ ] Verify the Investing in Chester production image pipeline with the new orb.
|
||||
@@ -0,0 +1,127 @@
|
||||
## Fast Java CI (2026-10-19)
|
||||
|
||||
Active user priority: fast, agile, lightweight CI/CD; backend ticket goal paused.
|
||||
Measured48GiB Maven cache restored four times and saved once per backend PR.
|
||||
Work on feature/lean-java-ci: stable exact dependency cache keys with new namespace,
|
||||
conditional OWASP setup, single test-result upload, and one orb pack per pipeline.
|
||||
The first patch passed make all and15BATS. Further simplification combines quality
|
||||
and coverage with the build, renders fixtures once, and carries installed reactor
|
||||
artifacts in a small workspace archive. A real multi-module experiment proved this
|
||||
artifact handoff necessary, including snapshot/classifier coverage. Expanded make
|
||||
all and20BATS pass, including all277template assertions. Real consumer timing and
|
||||
stable publication/adoption remain pending. No deployed speedup claimed.
|
||||
See docs/PLAN-lean-java-ci.md.
|
||||
|
||||
## Supplemental runtime freshness (2026-10-19)
|
||||
|
||||
Backend PR311 image scan6307 failed because db-backup reused a cached apk-upgrade layer (libexpat2.8.5-r0, fixed2.9.0-r0). Supplemental builds now use --pull --no-cache in both PR scan-only and release publishing paths. Trivy and immutable-tag guards remain unchanged. Targeted tests cover both Dockerfile forms, fresh-build flags, scan-before-push ordering and scan failure propagation. Validation pending; no release or consumer adoption yet.
|
||||
|
||||
# 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`
|
||||
promotes the bounded publishing CI fixes to main. Full make all and the feature
|
||||
CircleCI lint/pack/review/snapshot publication passed. Stable publication still
|
||||
requires the v0.3.2 tag on main and successful production orb publication.
|
||||
|
||||
EX-0008 is registered by standards PR27 (5c21fc7); Website-Frontend may select the
|
||||
new stable orb after publication. Default audit policy, Trivy and GitOps contracts
|
||||
are preserved. Website-Backend's Java installation now avoids interactive Ubuntu
|
||||
service-restart dialogs. Website consumers and production remain pending.
|
||||
|
||||
## Current status
|
||||
|
||||
The `develop` branch contains the complete `0.3.0` feature set. PR #6 added
|
||||
first-class pnpm support and the Obsidian pipeline, PR #8 added opt-in
|
||||
repository Dockerfile preservation for `node-webapp`, and PR #10 added
|
||||
consolidated Kustomize GitOps write-back with production-only controls and
|
||||
bounded push-race retries. Investing in Chester and QRun marketing both proved
|
||||
the release path through CircleCI, GHCR, `KofTwentyTwo/cluster-gitops`, and
|
||||
ArgoCD.
|
||||
|
||||
## Release state
|
||||
|
||||
- Latest stable target: `kof22/munitor@0.3.0`
|
||||
- Stable consumer pin: `kof22/munitor@0.3`
|
||||
- Development aliases: `dev:<commit>` and `dev:snapshot`
|
||||
- Default branch: `develop`; `main` is release-only
|
||||
- Release tag: `v0.3.0`
|
||||
|
||||
## Verification
|
||||
|
||||
Run `make all` before release. The stable tag must publish successfully through
|
||||
CircleCI before consumer repositories move from immutable development pins to
|
||||
`0.3`.
|
||||
|
||||
## Publishing release CI fixes (2026-10-10)
|
||||
|
||||
Feature branch `feature/scoped-audit-exceptions` prepares development-only npm
|
||||
exceptions with expiry, exact advisory/package/version checks, an independent
|
||||
production audit and reassessment on a new upstream release. Website-Frontend's
|
||||
proposed EX-0008 covers braces 3.0.3 through 2026-11-10. Regression tests exercise
|
||||
acceptance and rejection paths. The default audit and Trivy remain unchanged.
|
||||
|
||||
Website-Backend CI job 1135 timed out in Ubuntu's interactive needrestart prompt
|
||||
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.
|
||||
|
||||
## Release 0.3.7 preparation (2026-10-19)
|
||||
Includes merged PR38 (filesystem Trivy findings fail CI; current Semgrep image and zero-scan/configuration failures) and PR39 (pull fresh runtime bases and bypass cached JVM package-upgrade layers). Both feature PRs passed all CircleCI gates; PR39 also passed full local make all and seven BATS tests. Publish the stable orb only from a signed v0.3.7 tag on the reviewed main merge. Website-Backend must update both orb pins and pass real image/SAST scans before rollout. No vulnerability waiver or immutable-tag overwrite is part of this release.
|
||||
|
||||
Validation completed: focused scan tests12/12, full make all (lint, packed orb validation, script/template tests), and7BATS passed under WSL/Nix on2026-10-19. Real backend supplemental image scans remain required after stable release adoption.
|
||||
|
||||
## Release candidate 0.3.8 (2026-10-19)
|
||||
|
||||
Promotes PR42 supplemental build freshness fix (develop11d5704) to main. Full local make all and seven BATS passed; all PR42 CI checks passed. Release metadata changes only; source/tests match the verified feature. Stable publication still requires the approved main merge and signed v0.3.8 tag. Backend adoption and real supplemental scans remain pending.
|
||||
@@ -0,0 +1,55 @@
|
||||
# TODO: Munitor
|
||||
|
||||
## Pending
|
||||
|
||||
- [ ] Validate and publish fast Java CI patch, canary actual cache size/timings, then adopt stable orb (docs/PLAN-lean-java-ci.md).
|
||||
|
||||
- [ ] Validate and release supplemental-image freshness fix, then adopt it in backend PR311 and verify all supplemental Trivy scans.
|
||||
|
||||
- [ ] 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.
|
||||
- [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.
|
||||
|
||||
## Completed
|
||||
|
||||
- [x] Prove consolidated production write-back with Investing in Chester and QRun marketing.
|
||||
- [x] Release v0.3.0 and move the verified consumers to the stable 0.3 line.
|
||||
- [x] Merge repository Dockerfile preservation and consolidated GitOps write-back.
|
||||
- [x] Validate first-class pnpm support and merge PR #6.
|
||||
- [x] Add opt-in repository Dockerfile preservation for node-webapp (GH-7)
|
||||
- [x] Implement first-class pnpm support (GH-5, 17 commits, 219 tests)
|
||||
- [x] Rename all `npm_*` orb components to `node_*` (backward-incompatible, pre-1.0)
|
||||
- [x] Thread package_manager through full job/command/script chain
|
||||
- [x] Add corepack activation for pnpm/yarn in install_node
|
||||
- [x] Add pnpm Dockerfile variants for node-api and node-webapp
|
||||
- [x] Add Prisma auto-detection after dependency install
|
||||
- [x] Support inline commands in test.setup
|
||||
- [x] Bootstrap pnpm in secondary jobs (code-quality, coverage, e2e, security-scan, sonar, sbom)
|
||||
- [x] Add configurable coverage_summary_path with monorepo glob support
|
||||
- [x] Publish dev:snapshot with pnpm support
|
||||
- [x] Implement gradle-webapp pipeline type (20 new files, 7 modified, 605+ tests)
|
||||
- [x] Fix pack_generate_config.sh to embed gradle templates
|
||||
- [x] Fix RC009 orb review (extract inline commands to included script)
|
||||
- [x] Fix SC2155 shellcheck warnings in extract_munitor_vars.sh
|
||||
- [x] Add JaCoCo diagnostic output to gradle_jacoco.sh
|
||||
- [x] Merge gradle-webapp PR #2 to develop
|
||||
- [x] Release v0.2.0 and publish kof22/munitor@0.2.0
|
||||
- [x] Create GitHub Release for v0.2.0
|
||||
- [x] Bump Concilium from dev:snapshot to @0.2 (PR #41)
|
||||
- [x] Bump Website-Backend from @0.1 to @0.2 (PR #44)
|
||||
- [x] Bump Website-Frontend from @0.1 to @0.2 (PR #6)
|
||||
- [x] Onboard Concilium as first gradle-webapp consumer
|
||||
- [x] Create GitHub Releases for v0.1.0, v0.1.1, v0.1.2
|
||||
- [x] Publish orb kof22/munitor@0.1.2 (overridable org defaults)
|
||||
- [x] Implement supplemental Docker image support
|
||||
- [x] All 8 pipeline types implemented and tested
|
||||
- [x] Full rebrand from Faber to Munitor
|
||||
@@ -0,0 +1,174 @@
|
||||
# Spec: First-Class Package Manager Support (Issue #5)
|
||||
|
||||
## Goal
|
||||
|
||||
Make `package_manager` a first-class config in Munitor that drives behavior across the full Node.js lifecycle: dependency install, test execution, caching, Dockerfile generation, and post-install hooks (Prisma). Rename all `npm_*` orb jobs/commands to `node_*` to reflect multi-PM support. Release as v0.3.0.
|
||||
|
||||
## Context
|
||||
|
||||
`MUNITOR_PACKAGE_MANAGER` is parsed from `.munitor.yml` and available at config-generation time via envsubst, but no runtime script uses it. All scripts hardcode npm commands. Projects using `package_manager: pnpm` hit `pnpm: command not found` and must work around it with custom test commands.
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
- Rename `npm_*` jobs/commands/scripts to `node_*` (except `npm_auth`)
|
||||
- Thread `package_manager` parameter through job -> command -> script chain
|
||||
- Corepack activation for pnpm (and yarn) in `install_node.sh`
|
||||
- Package-manager-aware dependency install, test fallback, cache, Dockerfile generation
|
||||
- Prisma schema auto-detection and `prisma generate` after install
|
||||
- Fix `test.setup` to support inline commands (not just file paths)
|
||||
- Update all templates, packed scripts, and tests
|
||||
- v0.3.0 release
|
||||
|
||||
### Out of scope
|
||||
- Yarn support beyond basic corepack plumbing (no yarn-specific testing or validation)
|
||||
- Monorepo-aware Dockerfile generation (user provides custom Dockerfile for complex monorepos)
|
||||
- Changes to `npm_auth` (works for all PMs via `.npmrc`)
|
||||
|
||||
## Design
|
||||
|
||||
### 1. Rename Map
|
||||
|
||||
Keep `npm_auth` as-is (genuinely npm-registry-specific; `.npmrc` auth works for pnpm too). Everything else:
|
||||
|
||||
**Jobs:**
|
||||
| Old | New |
|
||||
|---|---|
|
||||
| `npm_build_and_test` | `node_build_and_test` |
|
||||
| `npm_code_quality` | `node_code_quality` |
|
||||
| `npm_coverage` | `node_coverage` |
|
||||
| `npm_security_scan` | `node_security_scan` |
|
||||
| `npm_e2e_test` | `node_e2e_test` |
|
||||
| `npm_sonar_scan` | `node_sonar_scan` |
|
||||
| `npm_sbom` | `node_sbom` |
|
||||
|
||||
**Commands:**
|
||||
| Old | New |
|
||||
|---|---|
|
||||
| `npm_install` | `node_install_deps` |
|
||||
| `npm_test` | `node_test` |
|
||||
| `npm_lint` | `node_lint` |
|
||||
| `npm_audit` | `node_audit` |
|
||||
| `npm_coverage_check` | `node_coverage_check` |
|
||||
| `npm_sonar` | `node_sonar` |
|
||||
| `npm_sbom` (cmd) | `node_sbom` (cmd) |
|
||||
| `restore_npm_cache` | `restore_node_cache` |
|
||||
| `save_npm_cache` | `save_node_cache` |
|
||||
|
||||
**Scripts:** Same pattern, `.sh` suffix (e.g., `npm_install.sh` -> `node_install_deps.sh`).
|
||||
|
||||
### 2. Parameter Threading
|
||||
|
||||
Add `package_manager` (type: string, default: `"npm"`) to:
|
||||
- `node_build_and_test` job
|
||||
- `install_node` command
|
||||
- `node_install_deps` command
|
||||
- `node_test` command
|
||||
- `restore_node_cache` / `save_node_cache` commands
|
||||
|
||||
Templates pass it: `package_manager: "${MUNITOR_PACKAGE_MANAGER}"`.
|
||||
|
||||
At runtime, each command sets `MUNITOR_PACKAGE_MANAGER: << parameters.package_manager >>` in the step's `environment` block, making it available to the included script.
|
||||
|
||||
### 3. Corepack Activation (`install_node.sh`)
|
||||
|
||||
After nvm install/use, when `MUNITOR_PACKAGE_MANAGER != npm`:
|
||||
|
||||
1. Run `corepack enable`
|
||||
2. Detect version from `package.json` with this priority:
|
||||
- `packageManager` field (e.g., `"pnpm@9.15.9"`) -- corepack-native declaration
|
||||
- `engines.<pm>` field (e.g., `"engines": {"pnpm": ">=9"}`)
|
||||
- Fallback: no version specified (corepack uses latest)
|
||||
3. If version found: `corepack prepare <pm>@<version> --activate`
|
||||
4. Print version to build output
|
||||
5. Export PM binary path to `BASH_ENV` so downstream steps have it on PATH
|
||||
|
||||
### 4. Dependency Install (`node_install_deps.sh`, was `npm_install.sh`)
|
||||
|
||||
Branch on `MUNITOR_PACKAGE_MANAGER`:
|
||||
|
||||
**npm (default):** Current behavior unchanged. Check `package-lock.json`, run `npm ci`, lockfile version warning.
|
||||
|
||||
**pnpm:** Check `pnpm-lock.yaml` exists (error message should suggest `pnpm install` to create it, not `npm install`). Run `pnpm install --frozen-lockfile`. Note: `pnpm install` at the repo root handles workspaces natively; `pnpm -r` is NOT needed.
|
||||
|
||||
After install (all PMs): Prisma auto-detection (see section 7).
|
||||
|
||||
### 5. Test Execution (`node_test.sh`, was `npm_test.sh`)
|
||||
|
||||
**Custom commands path:** Unchanged. Already runs arbitrary commands via `bash -c`.
|
||||
|
||||
**Default fallback (no custom commands):**
|
||||
- npm: `npx jest` (current behavior)
|
||||
- pnpm: `pnpm test` (delegates to package.json scripts, where Jest or Vitest is typically configured)
|
||||
|
||||
### 6. Cache (`restore_node_cache.yml` / `save_node_cache.yml`)
|
||||
|
||||
Use CircleCI `when/equal` conditions on `package_manager` parameter:
|
||||
|
||||
**npm:** Checksum `package-lock.json`, cache `~/.npm`.
|
||||
**pnpm:** Checksum `pnpm-lock.yaml`, cache pnpm store (get path via `pnpm store path` at save time, use `~/.local/share/pnpm/store` as restore default).
|
||||
|
||||
Cache key prefix changes per PM to avoid collisions: `npm-v1-` vs `pnpm-v1-`.
|
||||
|
||||
### 7. Prisma Auto-Detection
|
||||
|
||||
Added to `node_install_deps.sh` after the dependency install step:
|
||||
|
||||
1. `find . -path "*/prisma/schema.prisma" -not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/build/*"`
|
||||
2. For each found schema, run `<exec> prisma generate --schema=<path>`:
|
||||
- npm: `npx prisma generate --schema=<path>`
|
||||
- pnpm: `pnpm exec prisma generate --schema=<path>`
|
||||
3. If no schemas found, skip silently (no output).
|
||||
4. If schemas found but `prisma` is not in dependencies, log a warning and continue (don't fail the build).
|
||||
|
||||
This is not pnpm-specific; it helps any monorepo where Prisma's postinstall hook can't find the schema at the default location.
|
||||
|
||||
### 8. Dockerfile Generation (`generate_dockerfile.sh`)
|
||||
|
||||
Already has `MUNITOR_PACKAGE_MANAGER` via `extract_munitor_vars.sh`. Changes needed in node-api and node-webapp Dockerfile templates:
|
||||
|
||||
**npm (default):** No change. `COPY package*.json` / `npm ci` / `npm run build`.
|
||||
|
||||
**pnpm:** In builder stage:
|
||||
```dockerfile
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY . .
|
||||
RUN pnpm run build
|
||||
```
|
||||
|
||||
Runner stage: strip pnpm (same pattern as stripping npm). For distroless images (node-webapp), pnpm isn't present in the runner anyway since only standalone output is copied.
|
||||
|
||||
**Monorepo note:** Generated Dockerfiles assume single-package or standalone-output builds. Complex monorepo setups should provide a custom Dockerfile (existing escape hatch: place a `Dockerfile` in the repo and Munitor skips generation).
|
||||
|
||||
### 9. test.setup Fix (`run_test_setup.sh`)
|
||||
|
||||
Current behavior: if the value doesn't point to an existing file, exit with "script not found." This silently confuses users who pass command strings.
|
||||
|
||||
Fix: if the value is not a file path, treat it as an inline shell command and run it via `bash -c`. Log clearly which mode was used:
|
||||
- File: `"Running test setup script: ./scripts/setup.sh"`
|
||||
- Inline: `"Running test setup command: corepack enable && ..."`
|
||||
|
||||
This reduces the need for the inline workaround now that pnpm is auto-handled, but prevents future confusion.
|
||||
|
||||
### 10. Template Updates
|
||||
|
||||
All 4 templates (`node-webapp.yml.tpl`, `node-api.yml.tpl`) and 2 deploy partials (`node-webapp-deploy.yml.tpl`, `node-api-deploy.yml.tpl`):
|
||||
- Replace all `munitor/npm_*` references with `munitor/node_*`
|
||||
- Add `package_manager: "${MUNITOR_PACKAGE_MANAGER}"` to `node_build_and_test` invocations
|
||||
|
||||
`packed_generate_config.sh` and `packed_generate_dockerfile.sh`: Regenerated from source (these embed templates).
|
||||
|
||||
### 11. Test Updates
|
||||
|
||||
- `test_generate_config.sh`: Update all `npm_*` assertions to `node_*`. Add pnpm fixture and tests.
|
||||
- `test_extract_munitor_vars.sh`: Already tests `MUNITOR_PACKAGE_MANAGER`. Add pnpm-specific fixture.
|
||||
- `test_scripts.sh`: Update script path references. Add tests for pnpm branching in `node_install_deps.sh` and Prisma detection.
|
||||
- New fixture: `.munitor.yml` with `package_manager: pnpm` for generate_config tests.
|
||||
|
||||
### 12. Version and Release
|
||||
|
||||
- Version: **0.3.0** (breaking rename, pre-1.0)
|
||||
- Consumer repos must regenerate `.circleci/config.yml` after bumping orb version
|
||||
- `generate_config.sh` output automatically uses new job names
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,46 @@
|
||||
# PLAN: Gradle-Webapp Pipeline Type
|
||||
|
||||
## Goal
|
||||
|
||||
Add Gradle build support to Munitor so Gradle-based Java projects can use the same quality gates and deployment workflows as Maven projects.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
1. **Approach A: Full parallel pipeline** - Completely independent pipeline type with its own commands, scripts, jobs, and template. No shared code with Maven.
|
||||
2. **JAR discovery (C)**: Use `server_module` config field to construct path `{server_module}/build/libs/*.jar`, excluding `-plain.jar`, `-sources.jar`, `-javadoc.jar`.
|
||||
3. **No version injection (C)**: Skip Gradle version setting entirely. Rely on `calculate_version` + `export_version_vars` for Docker image tagging.
|
||||
4. **Cache key (B)**: `gradle-wrapper.properties` + `settings.gradle.kts` with `gradle-v1-` fallback.
|
||||
|
||||
## Config Shape
|
||||
|
||||
```yaml
|
||||
pipeline: gradle-webapp
|
||||
orb_version: "0.2"
|
||||
image_name: concilium
|
||||
java_version: "21"
|
||||
server_module: "concilium-server" # optional, for multi-module shadow JAR
|
||||
docker:
|
||||
registry: ghcr.io/koftwentytwo
|
||||
```
|
||||
|
||||
## Files Created (19)
|
||||
|
||||
- Scripts (6): `gradle_build.sh`, `gradle_test.sh`, `gradle_jacoco.sh`, `gradle_checkstyle.sh`, `gradle_spotbugs.sh`, `gradle_pmd.sh`, `collect_gradle_test_results.sh`
|
||||
- Commands (8): `gradle_build.yml`, `gradle_test.yml`, `gradle_jacoco.yml`, `gradle_checkstyle.yml`, `gradle_spotbugs.yml`, `gradle_pmd.yml`, `restore_gradle_cache.yml`, `save_gradle_cache.yml`
|
||||
- Jobs (3): `gradle_build_and_test.yml`, `gradle_code_quality.yml`, `gradle_coverage.yml`
|
||||
- Templates (2): `gradle-webapp.yml.tpl`, `partials/gradle-webapp-deploy.yml.tpl`
|
||||
|
||||
## Files Modified (6+)
|
||||
|
||||
- `extract_munitor_vars.sh` - Added `MUNITOR_SERVER_MODULE`, fixed SC2155 warnings
|
||||
- `generate_config.sh` - Added `gradle-webapp` to case + `server_module` to known keys
|
||||
- `generate_dockerfile.sh` - Added `gradle-webapp` case with shadow JAR discovery
|
||||
- `pack_generate_config.sh` - Added gradle templates to embed loops
|
||||
- Test files (3) - Added gradle-webapp assertions
|
||||
|
||||
## Gotchas Discovered
|
||||
|
||||
1. **pack_generate_config.sh has hardcoded template lists** - New templates must be added to the for loops manually. Not auto-discovered.
|
||||
2. **RC009 orb review** - CircleCI orb-tools review fails on inline commands > 64 chars. Must use `<< include() >>` syntax.
|
||||
3. **Gradle JaCoCo CSV not default** - Gradle's JaCoCo plugin only generates HTML+XML by default. Projects must explicitly enable `csv.required.set(true)`.
|
||||
4. **orb_version resolution** - `orb_version: "0.1"` in `.munitor.yml` resolves to latest stable `@0.1.x`, not `dev:snapshot`. New features only available via `dev:snapshot` until released.
|
||||
@@ -0,0 +1,120 @@
|
||||
# PLAN: Supplemental Images
|
||||
|
||||
## Goal
|
||||
|
||||
Add support for building, scanning, and pushing supplemental Docker images (e.g., Liquibase migrations, database backup containers) alongside the main application image. Each supplemental image shares the same version tag as the app, gets Trivy-scanned, and can update the CD repo.
|
||||
|
||||
## Config Shape
|
||||
|
||||
```yaml
|
||||
# .munitor.yml
|
||||
supplemental_images:
|
||||
- name: migrations
|
||||
# Option A: auto-generate Dockerfile from base + copy
|
||||
base: liquibase/liquibase:4.31
|
||||
copy:
|
||||
- src: db/changelog/
|
||||
dest: /liquibase/changelog/
|
||||
cd:
|
||||
image_key: migrations.image.tag # Helm mode
|
||||
# image_name: ghcr.io/org/app-migrations # Kustomize (auto-derived if omitted)
|
||||
|
||||
- name: db-backup
|
||||
# Option B: use repo-provided Dockerfile
|
||||
dockerfile: docker/Dockerfile.backup
|
||||
cd:
|
||||
image_key: dbBackup.image.tag
|
||||
```
|
||||
|
||||
Image name derivation: `{image_name}-{name}` (e.g., `me-health-portal-migrations`).
|
||||
Version: always same as main app (`PROJECT_VERSION`).
|
||||
Env tag: same as main app (`DOCKER_ENV_TAG`).
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
docker_build_push job steps:
|
||||
attach_workspace
|
||||
restore_version_metadata
|
||||
generate_dockerfile # Main app Dockerfile
|
||||
docker_build # Build main app image
|
||||
health_check # Verify main app boots
|
||||
restore_trivy_cache
|
||||
run_trivy_image # Scan main app image
|
||||
save_trivy_cache
|
||||
docker_push_ghcr # Push main app (establishes registry auth)
|
||||
build_push_supplemental [NEW] # For each supplemental: generate Dockerfile, build, scan, push
|
||||
```
|
||||
|
||||
CD repo update: `update_cd_repo` receives supplemental_images JSON, updates all tags in a single commit.
|
||||
|
||||
## Files Affected
|
||||
|
||||
### New Files
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/scripts/build_push_supplemental.sh` | Loop: auto-generate Dockerfile, build, Trivy scan, push each supplemental image |
|
||||
| `src/commands/build_push_supplemental.yml` | Orb command wrapping the script |
|
||||
| `tests/fixtures/java-webapp-supplemental.munitor.yml` | Test fixture with supplemental_images config |
|
||||
|
||||
### Modified Files
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/scripts/extract_munitor_vars.sh` | Parse `supplemental_images` array, export `MUNITOR_SUPPLEMENTAL_IMAGES_JSON` + `MUNITOR_SUPPLEMENTAL` flag |
|
||||
| `src/scripts/generate_config.sh` | Add `SUPPLEMENTAL` to conditional flags, `supplemental_images` to KNOWN_KEYS |
|
||||
| `src/scripts/update_cd_repo.sh` | Read `SUPPLEMENTAL_IMAGES_JSON`, update all supplemental image tags in same commit |
|
||||
| `src/commands/update_cd_repo.yml` | Add `supplemental_images` parameter |
|
||||
| `src/jobs/docker_build_push.yml` | Add `supplemental_images` param + `build_push_supplemental` step |
|
||||
| `src/scripts/templates/java-webapp.yml.tpl` | `##IF_SUPPLEMENTAL##` blocks on docker_build_push + update_cd_repo (release-candidate + production) |
|
||||
| `src/scripts/templates/node-api.yml.tpl` | Same |
|
||||
| `src/scripts/templates/node-webapp.yml.tpl` | Same |
|
||||
| `src/scripts/templates/partials/java-webapp-deploy.yml.tpl` | `##IF_SUPPLEMENTAL##` blocks on docker_build_push + update_cd_repo |
|
||||
| `src/scripts/templates/partials/node-api-deploy.yml.tpl` | Same |
|
||||
| `src/scripts/templates/partials/node-webapp-deploy.yml.tpl` | Same |
|
||||
| `tests/test_extract_munitor_vars.sh` | Test supplemental_images parsing |
|
||||
| `tests/test_generate_config.sh` | Test ##IF_SUPPLEMENTAL## rendering |
|
||||
| `tests/test_scripts.sh` | Test build_push_supplemental.sh structure |
|
||||
|
||||
## Design Decisions
|
||||
|
||||
**Loop in same job, not separate jobs.** Supplemental images are lightweight (FROM base + COPY). Sequential build in the same executor avoids spinning up more machines and reuses Docker auth + Trivy installation.
|
||||
|
||||
**JSON array config.** Variable number of supplemental images (0-N). JSON array parsed with `jq` handles this cleanly. Follows the same pattern as `MUNITOR_SERVICES_JSON`.
|
||||
|
||||
**Auto-generate Dockerfiles.** Most supplemental images are trivial. Auto-generation from `base` + `copy` eliminates boilerplate. Users can still provide custom Dockerfiles.
|
||||
|
||||
**Single CD commit.** All image tags (app + supplemental) are updated atomically so ArgoCD picks them up together.
|
||||
|
||||
**Docker-producing pipelines only.** supplemental_images only applies to java-webapp, node-api, node-webapp. Not terraform, argocd-apps, validate-cd-repo, sdk-distribution.
|
||||
|
||||
## Variable Flow
|
||||
|
||||
```
|
||||
.munitor.yml extract_munitor_vars.sh templates
|
||||
-------------- ----------------------- ---------
|
||||
supplemental_images: [...] --> MUNITOR_SUPPLEMENTAL="true" --> ##IF_SUPPLEMENTAL##
|
||||
MUNITOR_SUPPLEMENTAL_IMAGES_JSON --> docker_build_push(supplemental_images=...)
|
||||
update_cd_repo(supplemental_images=...)
|
||||
```
|
||||
|
||||
## Script Details
|
||||
|
||||
### build_push_supplemental.sh
|
||||
|
||||
```
|
||||
For each entry in SUPPLEMENTAL_IMAGES_JSON:
|
||||
1. Derive image name: {IMAGE_NAME}-{entry.name}
|
||||
2. If entry.dockerfile exists: use it
|
||||
Else: auto-generate Dockerfile.{name} from entry.base + entry.copy
|
||||
3. docker build -f {dockerfile} -t {REGISTRY}/{derived_name}:{VERSION} .
|
||||
4. If DOCKER_ENV_TAG set: also tag with env tag
|
||||
5. trivy image --severity HIGH,CRITICAL --exit-code 1 {full_tag}
|
||||
6. docker push {full_tag}
|
||||
7. If env tag: docker push {full_tag_env}
|
||||
```
|
||||
|
||||
### update_cd_repo.sh changes
|
||||
|
||||
After updating main image tag, if SUPPLEMENTAL_IMAGES_JSON is non-empty:
|
||||
- Helm: for each entry with cd.image_key, run `yq -i ".{key} = {VERSION}"`
|
||||
- Kustomize: for each entry, derive image_name as `{CD_IMAGE_NAME}-{name}` (or use cd.image_name), run yq on kustomization.yaml
|
||||
@@ -0,0 +1,126 @@
|
||||
## Prompt: Adopting Munitor Supplemental Images for Pre-Rollout Containers
|
||||
|
||||
You are helping a development team configure their `.munitor.yml` file to use the **supplemental images** feature in the Munitor CircleCI orb. This feature builds, Trivy-scans, and pushes additional Docker images (sidecar/init containers) alongside the main application image during CI/CD.
|
||||
|
||||
### When to use supplemental images
|
||||
|
||||
Use this for any container that runs **before or alongside** the main application deployment:
|
||||
- **Liquibase migrations** — runs DB changelogs before the app starts
|
||||
- **Database backup** — snapshots the DB before a migration
|
||||
- **Data seeding** — loads reference data into a fresh environment
|
||||
- **Schema validation** — validates DB schema compatibility
|
||||
- Any other init container or Job that needs its own image
|
||||
|
||||
### How it works
|
||||
|
||||
1. Munitor builds the main app image as usual
|
||||
2. For each entry in `supplemental_images`, it either:
|
||||
- **Auto-generates a Dockerfile** from `base` + `copy` directives, OR
|
||||
- **Uses a provided Dockerfile** from the repo
|
||||
3. Each supplemental image is built, scanned with Trivy (HIGH/CRITICAL), and pushed to the same registry
|
||||
4. Image naming: `{image_name}-{supplemental_name}` (e.g., `me-health-portal-migrations`)
|
||||
5. All images share the same version tag
|
||||
6. The CD repo update commits all image tags (main + supplemental) atomically in a single commit
|
||||
|
||||
### Configuration reference
|
||||
|
||||
Add a `supplemental_images` array to your `.munitor.yml`:
|
||||
|
||||
```yaml
|
||||
supplemental_images:
|
||||
- name: <string> # REQUIRED. Suffix appended to image_name (e.g., "migrations")
|
||||
base: <string> # Option A: base Docker image (e.g., "liquibase/liquibase:4.31")
|
||||
copy: # Files to COPY into the auto-generated Dockerfile
|
||||
- src: <path> # Source path relative to repo root
|
||||
dest: <path> # Destination path in the container
|
||||
dockerfile: <string> # Option B: path to a Dockerfile in the repo (mutually exclusive with base)
|
||||
cd: # CD repo tag update config
|
||||
image_key: <string> # For Helm: dot-path in values.yaml (e.g., "migrations.image.tag")
|
||||
image_name: <string> # For Kustomize: image name in kustomization.yaml (defaults to {image_name}-{name})
|
||||
```
|
||||
|
||||
Each entry must have either `base` or `dockerfile`, not both.
|
||||
|
||||
### Example: Liquibase migrations
|
||||
|
||||
```yaml
|
||||
pipeline: java-webapp
|
||||
image_name: me-health-portal
|
||||
java_version: "21"
|
||||
|
||||
docker:
|
||||
registry: ghcr.io/dmdbrands
|
||||
|
||||
cd:
|
||||
repo: dmdbrands/me-health-portal-cd-pipeline
|
||||
|
||||
supplemental_images:
|
||||
- name: migrations
|
||||
base: liquibase/liquibase:4.31
|
||||
copy:
|
||||
- src: db/changelog/
|
||||
dest: /liquibase/changelog/
|
||||
cd:
|
||||
image_key: migrations.image.tag
|
||||
```
|
||||
|
||||
This produces:
|
||||
- Main image: `ghcr.io/dmdbrands/me-health-portal:1.2.3`
|
||||
- Supplemental: `ghcr.io/dmdbrands/me-health-portal-migrations:1.2.3`
|
||||
- CD repo `values.yaml` gets both `image.tag: "1.2.3"` and `migrations.image.tag: "1.2.3"` in one commit
|
||||
|
||||
### Example: Multiple supplemental images
|
||||
|
||||
```yaml
|
||||
supplemental_images:
|
||||
- name: migrations
|
||||
base: liquibase/liquibase:4.31
|
||||
copy:
|
||||
- src: db/changelog/
|
||||
dest: /liquibase/changelog/
|
||||
cd:
|
||||
image_key: migrations.image.tag
|
||||
|
||||
- name: db-backup
|
||||
dockerfile: docker/Dockerfile.db-backup
|
||||
cd:
|
||||
image_key: dbBackup.image.tag
|
||||
```
|
||||
|
||||
### Example: Kustomize CD format
|
||||
|
||||
```yaml
|
||||
supplemental_images:
|
||||
- name: migrations
|
||||
base: liquibase/liquibase:4.31
|
||||
copy:
|
||||
- src: db/changelog/
|
||||
dest: /liquibase/changelog/
|
||||
cd:
|
||||
image_name: me-health-portal-migrations # optional, defaults to {image_name}-{name}
|
||||
```
|
||||
|
||||
This updates `kustomization.yaml`:
|
||||
```yaml
|
||||
images:
|
||||
- name: me-health-portal
|
||||
newTag: "1.2.3"
|
||||
- name: me-health-portal-migrations
|
||||
newTag: "1.2.3"
|
||||
```
|
||||
|
||||
### Key constraints
|
||||
|
||||
- The `name` field becomes part of the Docker image name, so use lowercase alphanumeric + hyphens only
|
||||
- `base` + `copy` auto-generates a simple `FROM` + `COPY` Dockerfile — for anything more complex (RUN commands, ENV, etc.), use `dockerfile` instead
|
||||
- All supplemental images are Trivy-scanned with `--severity HIGH,CRITICAL --exit-code 1` — the build fails if vulnerabilities are found
|
||||
- The feature is available on all pipeline types: `java-webapp`, `node-api`, `node-webapp`
|
||||
- If `supplemental_images` is not present in `.munitor.yml`, no supplemental processing occurs (fully backward compatible)
|
||||
|
||||
### Your task
|
||||
|
||||
Given the team's application context, help them:
|
||||
1. Identify which pre-rollout tasks need their own container image
|
||||
2. Determine whether each needs `base` + `copy` (simple) or a custom `dockerfile` (complex)
|
||||
3. Configure the `supplemental_images` section in their `.munitor.yml`
|
||||
4. Ensure the CD repo's `values.yaml` or `kustomization.yaml` has matching entries for the supplemental image tags
|
||||
@@ -0,0 +1,22 @@
|
||||
# TODO: Supplemental Images
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] **Step 1:** `extract_munitor_vars.sh` - Parse `supplemental_images` array, export `MUNITOR_SUPPLEMENTAL` + `MUNITOR_SUPPLEMENTAL_IMAGES_JSON`, add to exports and `get_envsubst_vars()`
|
||||
- [ ] **Step 2:** `generate_config.sh` - Add `SUPPLEMENTAL` to conditional flags list, add `supplemental_images` to KNOWN_KEYS
|
||||
- [ ] **Step 3:** `build_push_supplemental.sh` (NEW) - Loop: auto-generate Dockerfile, build, Trivy scan, push each supplemental image
|
||||
- [ ] **Step 4:** `build_push_supplemental.yml` (NEW) - Orb command wrapping the script
|
||||
- [ ] **Step 5:** `docker_build_push.yml` - Add `supplemental_images` param + `build_push_supplemental` step after `docker_push_ghcr`
|
||||
- [ ] **Step 6:** `update_cd_repo.sh` - Read `SUPPLEMENTAL_IMAGES_JSON`, update supplemental image tags in same commit (both Helm + Kustomize)
|
||||
- [ ] **Step 7:** `update_cd_repo.yml` - Add `supplemental_images` parameter + env var
|
||||
- [ ] **Step 8a:** `java-webapp.yml.tpl` - Add `##IF_SUPPLEMENTAL##` blocks on docker_build_push + update_cd_repo (release-candidate + production)
|
||||
- [ ] **Step 8b:** `node-api.yml.tpl` - Same
|
||||
- [ ] **Step 8c:** `node-webapp.yml.tpl` - Same
|
||||
- [ ] **Step 8d:** `java-webapp-deploy.yml.tpl` - Add `##IF_SUPPLEMENTAL##` blocks on docker_build_push + update_cd_repo
|
||||
- [ ] **Step 8e:** `node-api-deploy.yml.tpl` - Same
|
||||
- [ ] **Step 8f:** `node-webapp-deploy.yml.tpl` - Same
|
||||
- [ ] **Step 9a:** `java-webapp-supplemental.munitor.yml` (NEW) - Test fixture
|
||||
- [ ] **Step 9b:** `test_extract_munitor_vars.sh` - Test supplemental_images parsing (enabled + default disabled)
|
||||
- [ ] **Step 9c:** `test_generate_config.sh` - Test ##IF_SUPPLEMENTAL## conditional rendering
|
||||
- [ ] **Step 9d:** `test_scripts.sh` - Test build_push_supplemental.sh structure (helpers, error handling, jq usage)
|
||||
- [ ] **Step 10:** Run `make all` - Validate everything passes
|
||||
@@ -0,0 +1,157 @@
|
||||
# Design: obsidian-plugin Pipeline Type
|
||||
|
||||
New Munitor pipeline type for Obsidian plugins. Handles building, testing, quality gating, and releasing TypeScript/esbuild-based Obsidian plugins to GitHub Releases. No Docker, no CD deployment.
|
||||
|
||||
## Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| Pipeline naming | `obsidian-plugin` (specific) | Bakes in Obsidian conventions (hardcoded artifacts, no Docker) |
|
||||
| Release model | Branch-based (release-candidate + production) | Aligns with SDLC Section 6.3 and org branching strategy |
|
||||
| Quality gates | All node gates supported as conditionals | SAST, coverage, SBOM, security-scan enabled for obsidian-penny |
|
||||
| Artifact handling | Hardcoded `main.js manifest.json styles.css` | Convention over configuration for Obsidian-specific type |
|
||||
| Artifact upload mechanism | Extend existing `github_release` job | Adds `release_artifacts` parameter; minimal orb changes |
|
||||
| Build output persistence | Use `test.commands` override to chain build + test | Works today with no orb changes; dedicated `build_command` can come later |
|
||||
|
||||
## Template Structure
|
||||
|
||||
File: `src/scripts/templates/obsidian-plugin.yml.tpl`
|
||||
|
||||
Four workflows matching the SDLC branch model:
|
||||
|
||||
### pr-checks (feature/\*, hotfix/\*)
|
||||
|
||||
Full quality pipeline. Jobs:
|
||||
- `node_build_and_test` (npm install + build + test via `test.commands`)
|
||||
- `secrets_scan`
|
||||
- `sast_scan` (conditional: `##IF_SAST##`)
|
||||
- `node_code_quality` (requires: build-and-test)
|
||||
- `node_coverage` (requires: build-and-test)
|
||||
- `node_security_scan` (requires: build-and-test)
|
||||
- `node_e2e_test` (conditional: `##IF_E2E##`, requires: build-and-test)
|
||||
|
||||
### develop (develop branch)
|
||||
|
||||
Smoke check only. Jobs:
|
||||
- `node_build_and_test`
|
||||
|
||||
No deploy partials, no Docker, no CD. This differs from node-api/node-webapp which use `##INCLUDE_DEPLOY##` for develop.
|
||||
|
||||
### release-candidate (release/\*)
|
||||
|
||||
Full quality pipeline gated before release. Jobs:
|
||||
- All pr-checks jobs
|
||||
- `node_sonar_scan` (conditional: `##IF_SONAR##`)
|
||||
- `node_sbom` (conditional: `##IF_SBOM##`, gated on quality gates)
|
||||
- `github_release` with `release_artifacts: "main.js manifest.json styles.css"` (gated on quality gates, pre-release via RC version detection)
|
||||
|
||||
The `github_release` job requires all quality gate jobs. No Docker gate since there is no Docker job.
|
||||
|
||||
### production (main)
|
||||
|
||||
Fast-path promotion (quality gates already passed on release/\*). Jobs:
|
||||
- `node_build_and_test`
|
||||
- `github_release` with `release_artifacts: "main.js manifest.json styles.css"` (requires: build-and-test, full release)
|
||||
|
||||
## Changes to `github_release` Job/Script
|
||||
|
||||
### `src/jobs/github_release.yml`
|
||||
|
||||
Add optional parameter:
|
||||
|
||||
```yaml
|
||||
parameters:
|
||||
release_artifacts:
|
||||
type: string
|
||||
default: ""
|
||||
description: "Space-separated list of files to upload to the GitHub Release"
|
||||
```
|
||||
|
||||
Pass to script as `MUNITOR_RELEASE_ARTIFACTS` environment variable.
|
||||
|
||||
### `src/scripts/github_release.sh`
|
||||
|
||||
After the `gh release create` block, add artifact upload:
|
||||
|
||||
```bash
|
||||
if [[ -n "${MUNITOR_RELEASE_ARTIFACTS:-}" ]]; then
|
||||
echo "Uploading release artifacts: ${MUNITOR_RELEASE_ARTIFACTS}"
|
||||
# shellcheck disable=SC2086
|
||||
gh release upload "${TAG}" ${MUNITOR_RELEASE_ARTIFACTS}
|
||||
fi
|
||||
```
|
||||
|
||||
Uses `gh release upload` (separate from create) so it works whether the release was just created or already existed. Backward compatible -- existing pipelines pass no artifacts.
|
||||
|
||||
## `.munitor.yml` Schema
|
||||
|
||||
obsidian-penny configuration:
|
||||
|
||||
```yaml
|
||||
pipeline: obsidian-plugin
|
||||
orb_version: "0.3"
|
||||
node_version: "20"
|
||||
sast: true
|
||||
sbom: true
|
||||
test:
|
||||
commands: ["npm run build", "npm test"]
|
||||
coverage:
|
||||
tool: vitest
|
||||
command: npx vitest run --coverage
|
||||
contexts:
|
||||
github: github
|
||||
```
|
||||
|
||||
No new variables in `extract_munitor_vars.sh`. The template uses a subset of existing `MUNITOR_*` variables: `NODE_VERSION`, `PACKAGE_MANAGER`, `SAST`, `SAST_FAIL_ON_FINDINGS`, `SBOM`, `COVERAGE_MIN`, `COVERAGE_TOOL`, `COVERAGE_COMMAND`, `CONTEXT_GITHUB`, and the node feature flags.
|
||||
|
||||
### `generate_config.sh` validation
|
||||
|
||||
Add `obsidian-plugin` to the pipeline type case statement. Required: `contexts.github`. Not required: `image_name`, `docker.registry`, `cd.repo`.
|
||||
|
||||
### `packed_generate_config.sh`
|
||||
|
||||
Embed `obsidian-plugin.yml.tpl` in the packed script using the same heredoc pattern as other templates.
|
||||
|
||||
## Testing
|
||||
|
||||
### Test fixture
|
||||
|
||||
`tests/fixtures/obsidian-plugin.munitor.yml` -- mirrors the schema above.
|
||||
|
||||
### `tests/test_generate_config.sh`
|
||||
|
||||
Positive tests (patterns present):
|
||||
- Valid YAML output
|
||||
- All 4 workflows: `pr-checks`, `develop`, `release-candidate`, `production`
|
||||
- `build-and-test` in all workflows
|
||||
- `secrets-scan` and `sast-scan` in pr-checks and release-candidate
|
||||
- `github-release` in release-candidate and production
|
||||
- `release_artifacts: "main.js manifest.json styles.css"` on github-release jobs
|
||||
- `security-scan` present
|
||||
- `sbom` present
|
||||
- Correct branch filters
|
||||
|
||||
Negative tests (patterns absent):
|
||||
- No `docker-build-push`
|
||||
- No `update-cd-repo`
|
||||
- No `image_name` or `registry` references
|
||||
- No `INCLUDE_DEPLOY` partials
|
||||
|
||||
### `tests/test_scripts.sh`
|
||||
|
||||
- `github_release.sh` with `MUNITOR_RELEASE_ARTIFACTS` set -- verify upload behavior
|
||||
- `github_release.sh` with `MUNITOR_RELEASE_ARTIFACTS` empty -- verify no upload (backward compat)
|
||||
|
||||
## Consumer Repo Changes (obsidian-penny, post-release)
|
||||
|
||||
1. Create `.munitor.yml` (schema above)
|
||||
2. Bump `.circleci/config.yml` orb version to new release
|
||||
3. Remove `.github/workflows/release.yml` (redundant)
|
||||
4. Verify workspace persistence delivers `main.js` to `github_release` job
|
||||
|
||||
## Not Changed
|
||||
|
||||
- `extract_munitor_vars.sh` -- no new variables needed
|
||||
- `node_build_and_test` job -- works as-is with `test.commands` override
|
||||
- Other pipeline templates -- no modifications
|
||||
- `sdk-distribution.yml.tpl` -- remains tag-based, separate use case
|
||||
@@ -39,7 +39,7 @@ echo "Packing generate_config.sh..."
|
||||
echo ''
|
||||
|
||||
# Embed each template
|
||||
for tpl in java-webapp gradle-webapp node-api node-webapp terraform sdk-distribution validate-cd-repo argocd-apps; do
|
||||
for tpl in java-webapp gradle-webapp node-api node-webapp terraform sdk-distribution validate-cd-repo argocd-apps obsidian-plugin; do
|
||||
echo "cat > /tmp/munitor/templates/${tpl}.yml.tpl << 'MUNITOR_TPL_EOF'"
|
||||
cat "${SRC}/templates/${tpl}.yml.tpl"
|
||||
echo 'MUNITOR_TPL_EOF'
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
description: >
|
||||
Atomically reserve the release version on main by creating the v<version>
|
||||
git tag through the GitHub API. If another commit already owns the tag
|
||||
(concurrent merges), the next patch version is reserved instead. Existing
|
||||
tags are never moved. No-op on non-main branches and pre-release versions.
|
||||
steps:
|
||||
- run:
|
||||
name: Reserve release version (atomic git tag)
|
||||
command: << include(scripts/allocate_version.sh) >>
|
||||
@@ -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
|
||||
@@ -1,7 +1,15 @@
|
||||
description: >
|
||||
Create an annotated git tag and GitHub Release using the version
|
||||
calculated by GitVersion. Skips if tag or release already exists.
|
||||
Optionally uploads release artifacts to the created release.
|
||||
parameters:
|
||||
release_artifacts:
|
||||
type: string
|
||||
default: ""
|
||||
description: "Space-separated list of files to upload to the GitHub Release."
|
||||
steps:
|
||||
- run:
|
||||
name: Create GitHub Release
|
||||
environment:
|
||||
MUNITOR_RELEASE_ARTIFACTS: << parameters.release_artifacts >>
|
||||
command: << include(scripts/github_release.sh) >>
|
||||
@@ -0,0 +1,34 @@
|
||||
description: >
|
||||
Refuse to overwrite immutable version tags. Fails when the main image or any
|
||||
supplemental image already exists at this version for a different commit
|
||||
(org.opencontainers.image.revision label). Reruns from the same commit pass.
|
||||
parameters:
|
||||
image_name:
|
||||
type: string
|
||||
description: Docker image name (without registry prefix).
|
||||
registry:
|
||||
type: string
|
||||
default: ghcr.io/KofTwentyTwo
|
||||
description: Container registry URL.
|
||||
supplemental_images:
|
||||
type: string
|
||||
default: "[]"
|
||||
description: JSON array of supplemental image definitions.
|
||||
ghcr_token_env:
|
||||
type: env_var_name
|
||||
default: GHCR_TOKEN
|
||||
description: Environment variable containing the GHCR token.
|
||||
ghcr_user_env:
|
||||
type: env_var_name
|
||||
default: GHCR_USERNAME
|
||||
description: Environment variable containing the GHCR username.
|
||||
steps:
|
||||
- run:
|
||||
name: Guard immutable version tags
|
||||
environment:
|
||||
IMAGE_NAME: << parameters.image_name >>
|
||||
REGISTRY: << parameters.registry >>
|
||||
SUPPLEMENTAL_IMAGES_JSON: << parameters.supplemental_images >>
|
||||
GHCR_TOKEN_VAR: << parameters.ghcr_token_env >>
|
||||
GHCR_USER_VAR: << parameters.ghcr_user_env >>
|
||||
command: << include(scripts/guard_image_tags.sh) >>
|
||||
@@ -5,9 +5,14 @@ parameters:
|
||||
type: string
|
||||
default: "20"
|
||||
description: Node.js major version to install (e.g., 20, 22).
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager to use (npm, pnpm, yarn).
|
||||
steps:
|
||||
- run:
|
||||
name: Install Node.js << parameters.version >>
|
||||
environment:
|
||||
NODE_VERSION: << parameters.version >>
|
||||
MUNITOR_PACKAGE_MANAGER: << parameters.package_manager >>
|
||||
command: << include(scripts/install_node.sh) >>
|
||||
@@ -0,0 +1,6 @@
|
||||
description: >
|
||||
Run a security audit to check for known vulnerabilities in dependencies.
|
||||
steps:
|
||||
- run:
|
||||
name: Security audit
|
||||
command: << include(scripts/node_audit.sh) >>
|
||||
@@ -14,6 +14,12 @@ parameters:
|
||||
type: string
|
||||
default: ""
|
||||
description: Custom command to generate coverage report before threshold check.
|
||||
coverage_summary_path:
|
||||
type: string
|
||||
default: "coverage/coverage-summary.json"
|
||||
description: >
|
||||
Path to coverage-summary.json. Supports glob patterns for monorepos
|
||||
(e.g., packages/*/coverage/coverage-summary.json).
|
||||
steps:
|
||||
- run:
|
||||
name: Coverage threshold check
|
||||
@@ -21,4 +27,5 @@ steps:
|
||||
MIN_COVERAGE: << parameters.min_coverage >>
|
||||
MUNITOR_COVERAGE_TOOL: << parameters.coverage_tool >>
|
||||
MUNITOR_COVERAGE_COMMAND: << parameters.coverage_command >>
|
||||
command: << include(scripts/npm_coverage_check.sh) >>
|
||||
MUNITOR_COVERAGE_SUMMARY_PATH: << parameters.coverage_summary_path >>
|
||||
command: << include(scripts/node_coverage_check.sh) >>
|
||||
@@ -0,0 +1,13 @@
|
||||
description: >
|
||||
Install project dependencies using the configured package manager.
|
||||
parameters:
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager to use (npm, pnpm, yarn).
|
||||
steps:
|
||||
- run:
|
||||
name: Install dependencies
|
||||
environment:
|
||||
MUNITOR_PACKAGE_MANAGER: << parameters.package_manager >>
|
||||
command: << include(scripts/node_install_deps.sh) >>
|
||||
@@ -3,4 +3,4 @@ description: >
|
||||
steps:
|
||||
- run:
|
||||
name: ESLint
|
||||
command: << include(scripts/npm_lint.sh) >>
|
||||
command: << include(scripts/node_lint.sh) >>
|
||||
@@ -0,0 +1,6 @@
|
||||
description: >
|
||||
Generate a CycloneDX SBOM from a Node.js project.
|
||||
steps:
|
||||
- run:
|
||||
name: Generate SBOM
|
||||
command: << include(scripts/node_sbom.sh) >>
|
||||
@@ -19,4 +19,4 @@ steps:
|
||||
SONAR_PROJECT_KEY: << parameters.sonar_project_key >>
|
||||
SONAR_ORG: << parameters.sonar_org >>
|
||||
SONAR_TOKEN_VAR: << parameters.sonar_token_env >>
|
||||
command: << include(scripts/npm_sonar.sh) >>
|
||||
command: << include(scripts/node_sonar.sh) >>
|
||||
@@ -1,17 +1,22 @@
|
||||
description: >
|
||||
Run tests with CI flags, coverage, and JUnit reporter. Supports custom
|
||||
test commands via JSON array or falls back to Jest defaults.
|
||||
test commands via JSON array or falls back to package manager defaults.
|
||||
parameters:
|
||||
test_commands:
|
||||
type: string
|
||||
default: ""
|
||||
description: JSON array of custom test commands to run instead of Jest.
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager to use (npm, pnpm, yarn).
|
||||
steps:
|
||||
- run:
|
||||
name: npm test
|
||||
name: Run tests
|
||||
environment:
|
||||
MUNITOR_TEST_COMMANDS_JSON: << parameters.test_commands >>
|
||||
command: << include(scripts/npm_test.sh) >>
|
||||
MUNITOR_PACKAGE_MANAGER: << parameters.package_manager >>
|
||||
command: << include(scripts/node_test.sh) >>
|
||||
- store_test_results:
|
||||
path: reports/junit
|
||||
- store_artifacts:
|
||||
@@ -1,6 +0,0 @@
|
||||
description: >
|
||||
Run npm audit to check for known vulnerabilities in dependencies.
|
||||
steps:
|
||||
- run:
|
||||
name: npm audit
|
||||
command: << include(scripts/npm_audit.sh) >>
|
||||
@@ -1,6 +0,0 @@
|
||||
description: >
|
||||
Run npm ci for a clean, reproducible dependency install.
|
||||
steps:
|
||||
- run:
|
||||
name: npm ci
|
||||
command: << include(scripts/npm_install.sh) >>
|
||||
@@ -1,6 +0,0 @@
|
||||
description: >
|
||||
Generate a CycloneDX SBOM from an npm project.
|
||||
steps:
|
||||
- run:
|
||||
name: Generate SBOM (npm)
|
||||
command: << include(scripts/npm_sbom.sh) >>
|
||||
@@ -1,7 +1,12 @@
|
||||
description: >
|
||||
Restore the Maven local repository cache based on pom.xml checksum.
|
||||
Restore only the cache for the checked-out Maven dependency definitions.
|
||||
steps:
|
||||
- run:
|
||||
name: Identify Maven dependency cache
|
||||
command: <<include(scripts/maven_cache_key.sh)>>
|
||||
- restore_cache:
|
||||
keys:
|
||||
- maven-v1-{{ checksum "pom.xml" }}
|
||||
- maven-v1-
|
||||
- maven-v2-{{ checksum ".munitor-maven-cache-key" }}
|
||||
- run:
|
||||
name: Restore this build's installed Maven artifacts
|
||||
command: <<include(scripts/maven_workspace.sh)>>
|
||||
@@ -0,0 +1,24 @@
|
||||
description: >
|
||||
Restore the dependency cache based on lockfile checksum.
|
||||
parameters:
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager (npm, pnpm, yarn).
|
||||
steps:
|
||||
- when:
|
||||
condition:
|
||||
equal: ["npm", << parameters.package_manager >>]
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- npm-v1-{{ checksum "package-lock.json" }}
|
||||
- npm-v1-
|
||||
- when:
|
||||
condition:
|
||||
equal: ["pnpm", << parameters.package_manager >>]
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- pnpm-v1-{{ checksum "pnpm-lock.yaml" }}
|
||||
- pnpm-v1-
|
||||
@@ -1,7 +0,0 @@
|
||||
description: >
|
||||
Restore the npm cache based on package-lock.json checksum.
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- npm-v1-{{ checksum "package-lock.json" }}
|
||||
- npm-v1-
|
||||
@@ -1,7 +1,12 @@
|
||||
description: >
|
||||
Save the Maven local repository to cache for future builds.
|
||||
Save the Maven repository under the same immutable key used at restore time.
|
||||
steps:
|
||||
- save_cache:
|
||||
key: maven-v1-{{ checksum "pom.xml" }}
|
||||
key: maven-v2-{{ checksum ".munitor-maven-cache-key" }}
|
||||
paths:
|
||||
- ~/.m2/repository
|
||||
- run:
|
||||
name: Preserve installed Maven artifacts for this workspace
|
||||
environment:
|
||||
MAVEN_WORKSPACE_ACTION: save
|
||||
command: <<include(scripts/maven_workspace.sh)>>
|
||||
@@ -0,0 +1,24 @@
|
||||
description: >
|
||||
Save the dependency cache for future builds.
|
||||
parameters:
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager (npm, pnpm, yarn).
|
||||
steps:
|
||||
- when:
|
||||
condition:
|
||||
equal: ["npm", << parameters.package_manager >>]
|
||||
steps:
|
||||
- save_cache:
|
||||
key: npm-v1-{{ checksum "package-lock.json" }}
|
||||
paths:
|
||||
- ~/.npm
|
||||
- when:
|
||||
condition:
|
||||
equal: ["pnpm", << parameters.package_manager >>]
|
||||
steps:
|
||||
- save_cache:
|
||||
key: pnpm-v1-{{ checksum "pnpm-lock.yaml" }}
|
||||
paths:
|
||||
- ~/.local/share/pnpm/store
|
||||
@@ -1,7 +0,0 @@
|
||||
description: >
|
||||
Save the npm cache for future builds.
|
||||
steps:
|
||||
- save_cache:
|
||||
key: npm-v1-{{ checksum "package-lock.json" }}
|
||||
paths:
|
||||
- ~/.npm
|
||||
@@ -30,14 +30,23 @@ parameters:
|
||||
description: >
|
||||
Full image name for Kustomize repos (e.g., ghcr.io/KofTwentyTwo/my-app).
|
||||
Used to select the correct entry in the images[] array.
|
||||
cd_path:
|
||||
type: string
|
||||
default: ""
|
||||
description: >
|
||||
Explicit repository-relative kustomization.yaml path. When omitted,
|
||||
Kustomize mode uses overlays/<environment>/kustomization.yaml.
|
||||
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: "[]"
|
||||
@@ -54,6 +63,7 @@ steps:
|
||||
IMAGE_KEY: << parameters.image_key >>
|
||||
CD_FORMAT: << parameters.cd_format >>
|
||||
CD_IMAGE_NAME: << parameters.cd_image_name >>
|
||||
CD_PATH: << parameters.cd_path >>
|
||||
CI_GIT_EMAIL: << parameters.ci_git_email >>
|
||||
CI_GIT_NAME: << parameters.ci_git_name >>
|
||||
SUPPLEMENTAL_IMAGES_JSON: << parameters.supplemental_images >>
|
||||
|
||||
@@ -3,8 +3,8 @@ description: >
|
||||
parameters:
|
||||
version:
|
||||
type: string
|
||||
default: "1.61.1"
|
||||
default: "1.181.0"
|
||||
description: Semgrep image version tag.
|
||||
docker:
|
||||
- image: returntocorp/semgrep:<< parameters.version >>
|
||||
- image: semgrep/semgrep:<< parameters.version >>
|
||||
resource_class: medium
|
||||
@@ -53,16 +53,14 @@ steps:
|
||||
- run:
|
||||
name: Restore version metadata
|
||||
command: <<include(scripts/restore_version_metadata.sh)>>
|
||||
- guard_image_tags:
|
||||
image_name: << parameters.image_name >>
|
||||
registry: << parameters.registry >>
|
||||
supplemental_images: << parameters.supplemental_images >>
|
||||
- generate_dockerfile
|
||||
- docker_build:
|
||||
image_name: << parameters.image_name >>
|
||||
registry: << parameters.registry >>
|
||||
- health_check:
|
||||
health_path: << parameters.health_path >>
|
||||
health_port: << parameters.health_port >>
|
||||
health_db: << parameters.health_db >>
|
||||
health_migrations: << parameters.health_migrations >>
|
||||
health_smoke_paths: << parameters.health_smoke_paths >>
|
||||
- restore_trivy_cache
|
||||
- run_trivy_image:
|
||||
image_tag: << parameters.registry >>/<< parameters.image_name >>:${DOCKER_TAG}
|
||||
@@ -72,3 +70,9 @@ steps:
|
||||
supplemental_images: << parameters.supplemental_images >>
|
||||
image_name: << parameters.image_name >>
|
||||
registry: << parameters.registry >>
|
||||
- health_check:
|
||||
health_path: << parameters.health_path >>
|
||||
health_port: << parameters.health_port >>
|
||||
health_db: << parameters.health_db >>
|
||||
health_migrations: << parameters.health_migrations >>
|
||||
health_smoke_paths: << parameters.health_smoke_paths >>
|
||||
@@ -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
|
||||
@@ -1,6 +1,12 @@
|
||||
description: >
|
||||
Create an annotated git tag and GitHub Release on main branch merges.
|
||||
Uses PROJECT_VERSION from GitVersion (persisted by the build job).
|
||||
Optionally uploads release artifacts (e.g., main.js, manifest.json).
|
||||
parameters:
|
||||
release_artifacts:
|
||||
type: string
|
||||
default: ""
|
||||
description: "Space-separated list of files to upload to the GitHub Release."
|
||||
executor: base
|
||||
steps:
|
||||
- checkout
|
||||
@@ -10,4 +16,5 @@ steps:
|
||||
name: Restore version metadata
|
||||
command: <<include(scripts/restore_version_metadata.sh)>>
|
||||
- install_gh
|
||||
- github_release
|
||||
- github_release:
|
||||
release_artifacts: << parameters.release_artifacts >>
|
||||
@@ -25,6 +25,7 @@ steps:
|
||||
- restore_gradle_cache
|
||||
- calculate_version
|
||||
- export_version_vars
|
||||
- allocate_version
|
||||
- gradle_build
|
||||
- gradle_test
|
||||
- run:
|
||||
|
||||
@@ -3,6 +3,14 @@ description: >
|
||||
version, builds, tests, saves cache, and persists the workspace for
|
||||
downstream jobs.
|
||||
parameters:
|
||||
quality_checks:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Run quality and coverage checks on the build worker before sharing artifacts.
|
||||
min_instruction:
|
||||
type: string
|
||||
default: "70"
|
||||
description: Minimum instruction coverage when quality_checks is enabled.
|
||||
java_version:
|
||||
type: string
|
||||
default: "21"
|
||||
@@ -25,11 +33,24 @@ steps:
|
||||
- restore_maven_cache
|
||||
- calculate_version
|
||||
- export_version_vars
|
||||
- allocate_version
|
||||
- mvn_set_version
|
||||
- mvn_build
|
||||
- mvn_test
|
||||
- store_test_results:
|
||||
path: target/surefire-reports
|
||||
- when:
|
||||
condition: << parameters.quality_checks >>
|
||||
steps:
|
||||
- mvn_checkstyle
|
||||
- mvn_spotbugs
|
||||
- mvn_pmd
|
||||
- mvn_jacoco:
|
||||
min_instruction: << parameters.min_instruction >>
|
||||
- store_artifacts:
|
||||
path: target/checkstyle-result.xml
|
||||
destination: checkstyle-result.xml
|
||||
- store_artifacts:
|
||||
path: target/site/jacoco
|
||||
destination: coverage-report
|
||||
- run:
|
||||
name: Persist version metadata
|
||||
command: <<include(scripts/persist_version_metadata.sh)>>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
description: >
|
||||
Full Node.js build and test job. Installs Node.js, restores cache, runs
|
||||
npm ci, calculates version, tests with coverage, saves cache, and persists
|
||||
dependency install, calculates version, tests with coverage, saves cache, and persists
|
||||
the workspace for downstream jobs.
|
||||
parameters:
|
||||
node_version:
|
||||
type: string
|
||||
default: "20"
|
||||
description: Node.js major version to install.
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager to use (npm, pnpm, yarn).
|
||||
npm_auth:
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -46,14 +50,17 @@ steps:
|
||||
- checkout
|
||||
- install_node:
|
||||
version: << parameters.node_version >>
|
||||
package_manager: << parameters.package_manager >>
|
||||
- when:
|
||||
condition: << parameters.npm_auth >>
|
||||
steps:
|
||||
- npm_auth:
|
||||
npm_scopes: << parameters.npm_scopes >>
|
||||
npm_default_scope: << parameters.npm_default_scope >>
|
||||
- restore_npm_cache
|
||||
- npm_install
|
||||
- restore_node_cache:
|
||||
package_manager: << parameters.package_manager >>
|
||||
- node_install_deps:
|
||||
package_manager: << parameters.package_manager >>
|
||||
- when:
|
||||
condition: << parameters.services >>
|
||||
steps:
|
||||
@@ -66,14 +73,17 @@ steps:
|
||||
test_setup: << parameters.test_setup >>
|
||||
- calculate_version
|
||||
- export_version_vars
|
||||
- allocate_version
|
||||
- run:
|
||||
name: Persist version metadata
|
||||
command: <<include(scripts/persist_version_metadata.sh)>>
|
||||
- npm_test:
|
||||
- node_test:
|
||||
test_commands: << parameters.test_commands >>
|
||||
package_manager: << parameters.package_manager >>
|
||||
- store_test_results:
|
||||
path: reports/junit
|
||||
- save_npm_cache
|
||||
- save_node_cache:
|
||||
package_manager: << parameters.package_manager >>
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
@@ -1,7 +1,15 @@
|
||||
description: >
|
||||
Run ESLint code quality checks.
|
||||
Requires workspace from npm_build_and_test.
|
||||
Requires workspace from node_build_and_test.
|
||||
parameters:
|
||||
node_version:
|
||||
type: string
|
||||
default: "20"
|
||||
description: Node.js major version to install.
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager (npm, pnpm, yarn).
|
||||
resource_class:
|
||||
type: enum
|
||||
enum:
|
||||
@@ -15,7 +23,10 @@ executor:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- npm_lint
|
||||
- install_node:
|
||||
version: << parameters.node_version >>
|
||||
package_manager: << parameters.package_manager >>
|
||||
- node_lint
|
||||
- store_artifacts:
|
||||
path: /tmp/eslint-report.json
|
||||
destination: eslint-report.json
|
||||
@@ -1,7 +1,15 @@
|
||||
description: >
|
||||
Enforce minimum coverage threshold from coverage output.
|
||||
Requires workspace from npm_build_and_test.
|
||||
Requires workspace from node_build_and_test.
|
||||
parameters:
|
||||
node_version:
|
||||
type: string
|
||||
default: "20"
|
||||
description: Node.js major version to install.
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager (npm, pnpm, yarn).
|
||||
min_coverage:
|
||||
type: string
|
||||
default: "70"
|
||||
@@ -14,6 +22,11 @@ parameters:
|
||||
type: string
|
||||
default: ""
|
||||
description: Custom command to generate coverage report before threshold check.
|
||||
coverage_summary_path:
|
||||
type: string
|
||||
default: "coverage/coverage-summary.json"
|
||||
description: >
|
||||
Path to coverage-summary.json. Supports glob patterns for monorepos.
|
||||
resource_class:
|
||||
type: enum
|
||||
enum:
|
||||
@@ -27,10 +40,14 @@ executor:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- npm_coverage_check:
|
||||
- install_node:
|
||||
version: << parameters.node_version >>
|
||||
package_manager: << parameters.package_manager >>
|
||||
- node_coverage_check:
|
||||
min_coverage: << parameters.min_coverage >>
|
||||
coverage_tool: << parameters.coverage_tool >>
|
||||
coverage_command: << parameters.coverage_command >>
|
||||
coverage_summary_path: << parameters.coverage_summary_path >>
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
destination: coverage-report
|
||||
@@ -1,6 +1,14 @@
|
||||
description: >
|
||||
Run Playwright E2E tests. Requires workspace from npm_build_and_test.
|
||||
Run Playwright E2E tests. Requires workspace from node_build_and_test.
|
||||
parameters:
|
||||
node_version:
|
||||
type: string
|
||||
default: "20"
|
||||
description: Node.js major version to install.
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager (npm, pnpm, yarn).
|
||||
test_dir:
|
||||
type: string
|
||||
default: e2e
|
||||
@@ -18,6 +26,9 @@ executor:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- install_node:
|
||||
version: << parameters.node_version >>
|
||||
package_manager: << parameters.package_manager >>
|
||||
- run_e2e_playwright:
|
||||
test_dir: << parameters.test_dir >>
|
||||
- store_artifacts:
|
||||
@@ -1,11 +1,15 @@
|
||||
description: >
|
||||
Generate SBOM and submit dependency graph for Node.js projects.
|
||||
Requires workspace from npm_build_and_test.
|
||||
Requires workspace from node_build_and_test.
|
||||
parameters:
|
||||
node_version:
|
||||
type: string
|
||||
default: "20"
|
||||
description: Node.js major version to install.
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager (npm, pnpm, yarn).
|
||||
resource_class:
|
||||
type: enum
|
||||
enum:
|
||||
@@ -21,7 +25,8 @@ steps:
|
||||
at: .
|
||||
- install_node:
|
||||
version: << parameters.node_version >>
|
||||
- npm_sbom
|
||||
package_manager: << parameters.package_manager >>
|
||||
- node_sbom
|
||||
- submit_dependency_graph:
|
||||
sbom_path: bom.json
|
||||
detector_name: CycloneDX npm
|
||||
@@ -1,7 +1,15 @@
|
||||
description: >
|
||||
Run npm audit and Trivy filesystem scan.
|
||||
Requires workspace from npm_build_and_test.
|
||||
Requires workspace from node_build_and_test.
|
||||
parameters:
|
||||
node_version:
|
||||
type: string
|
||||
default: "20"
|
||||
description: Node.js major version to install.
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager (npm, pnpm, yarn).
|
||||
resource_class:
|
||||
type: enum
|
||||
enum:
|
||||
@@ -15,7 +23,10 @@ executor:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- npm_audit
|
||||
- install_node:
|
||||
version: << parameters.node_version >>
|
||||
package_manager: << parameters.package_manager >>
|
||||
- node_audit
|
||||
- restore_trivy_cache
|
||||
- run_trivy
|
||||
- save_trivy_cache
|
||||
@@ -1,11 +1,15 @@
|
||||
description: >
|
||||
Run SonarCloud analysis for Node.js projects. Attaches workspace from
|
||||
npm_build_and_test and uses the sonar-scanner CLI with bundled JRE.
|
||||
node_build_and_test and uses the sonar-scanner CLI with bundled JRE.
|
||||
parameters:
|
||||
node_version:
|
||||
type: string
|
||||
default: "20"
|
||||
description: Node.js major version to install.
|
||||
package_manager:
|
||||
type: string
|
||||
default: "npm"
|
||||
description: Package manager (npm, pnpm, yarn).
|
||||
sonar_project_key:
|
||||
type: string
|
||||
description: SonarCloud project key.
|
||||
@@ -29,6 +33,7 @@ steps:
|
||||
at: .
|
||||
- install_node:
|
||||
version: << parameters.node_version >>
|
||||
- npm_sonar:
|
||||
package_manager: << parameters.package_manager >>
|
||||
- node_sonar:
|
||||
sonar_project_key: << parameters.sonar_project_key >>
|
||||
sonar_org: << parameters.sonar_org >>
|
||||
@@ -23,12 +23,12 @@ executor:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- install_java:
|
||||
version: << parameters.java_version >>
|
||||
- restore_maven_cache
|
||||
- when:
|
||||
condition: << parameters.owasp >>
|
||||
steps:
|
||||
- install_java:
|
||||
version: << parameters.java_version >>
|
||||
- restore_maven_cache
|
||||
- restore_cache:
|
||||
keys:
|
||||
- nvd-cache-v1-{{ .Branch }}
|
||||
|
||||
@@ -24,14 +24,21 @@ parameters:
|
||||
type: string
|
||||
default: ""
|
||||
description: Full image name for Kustomize repos.
|
||||
cd_path:
|
||||
type: string
|
||||
default: ""
|
||||
description: Explicit repository-relative kustomization.yaml path.
|
||||
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: "[]"
|
||||
@@ -52,6 +59,7 @@ steps:
|
||||
image_key: << parameters.image_key >>
|
||||
cd_format: << parameters.cd_format >>
|
||||
cd_image_name: << parameters.cd_image_name >>
|
||||
cd_path: << parameters.cd_path >>
|
||||
ci_git_email: << parameters.ci_git_email >>
|
||||
ci_git_name: << parameters.ci_git_name >>
|
||||
supplemental_images: << parameters.supplemental_images >>
|
||||
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared helpers
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
munitor_header "allocate_version"
|
||||
|
||||
# Atomically reserve the release version for this commit.
|
||||
#
|
||||
# GitVersion derives the next version from the tags it can see, so two main
|
||||
# merges that build concurrently both compute the same version. The reservation
|
||||
# is the git tag itself: GitHub creates refs/tags/v<version> only if it does not
|
||||
# exist, which serializes concurrent pipelines without a lock service.
|
||||
# - created -> this commit owns the version
|
||||
# - exists on this commit -> rerun of the same pipeline; reuse it
|
||||
# - exists on another -> taken; try the next patch version
|
||||
# An existing tag is never moved or deleted.
|
||||
|
||||
VERSION="${PROJECT_VERSION:?PROJECT_VERSION not set -- run export_version_vars first}"
|
||||
BRANCH="${CIRCLE_BRANCH:-}"
|
||||
|
||||
if [[ "${BRANCH}" != "main" ]]; then
|
||||
echo "Branch '${BRANCH:-none}' is not main; version ${VERSION} needs no reservation."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! "${VERSION}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
echo "Version ${VERSION} is not a release version; no reservation needed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MAJOR="${BASH_REMATCH[1]}"
|
||||
MINOR="${BASH_REMATCH[2]}"
|
||||
PATCH="${BASH_REMATCH[3]}"
|
||||
|
||||
SHA="${CIRCLE_SHA1:?CIRCLE_SHA1 not set -- cannot reserve a version without the commit SHA}"
|
||||
REPO="${MUNITOR_SOURCE_REPO:-${CIRCLE_PROJECT_USERNAME:-}/${CIRCLE_PROJECT_REPONAME:-}}"
|
||||
MAX_ATTEMPTS="${MUNITOR_VERSION_ALLOC_ATTEMPTS:-25}"
|
||||
|
||||
if [[ "${REPO}" == "/" || "${REPO}" == */ || "${REPO}" == /* ]]; then
|
||||
echo "ERROR: Cannot determine the source repository (CIRCLE_PROJECT_USERNAME/CIRCLE_PROJECT_REPONAME)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${MUNITOR_GH_API_CMD:-}" && -z "${GITHUB_TOKEN:-}" ]]; then
|
||||
echo "ERROR: GITHUB_TOKEN is not set; release versions on main must be reserved as git tags."
|
||||
echo "Add the GitHub context to the build-and-test job."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# gh_api METHOD PATH [JSON_BODY]
|
||||
# Prints the response body. Returns 0 on 2xx, 2 on 404, 3 on 409/422, 1 otherwise.
|
||||
# MUNITOR_GH_API_CMD substitutes the transport (used by tests).
|
||||
gh_api() {
|
||||
if [[ -n "${MUNITOR_GH_API_CMD:-}" ]]; then
|
||||
"${MUNITOR_GH_API_CMD}" "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
local method="$1" path="$2" body="${3:-}"
|
||||
local out code
|
||||
out=$(mktemp)
|
||||
local args=(-sS -o "${out}" -w '%{http_code}' -X "${method}"
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}"
|
||||
-H "Accept: application/vnd.github+json"
|
||||
-H "X-GitHub-Api-Version: 2022-11-28")
|
||||
if [[ -n "${body}" ]]; then
|
||||
args+=(-H "Content-Type: application/json" -d "${body}")
|
||||
fi
|
||||
|
||||
if ! code=$(curl "${args[@]}" "https://api.github.com/repos/${REPO}/${path}"); then
|
||||
cat "${out}"
|
||||
rm -f "${out}"
|
||||
return 1
|
||||
fi
|
||||
cat "${out}"
|
||||
rm -f "${out}"
|
||||
|
||||
case "${code}" in
|
||||
2??) return 0 ;;
|
||||
404) return 2 ;;
|
||||
409|422) return 3 ;;
|
||||
*) echo "GitHub API ${method} ${path} returned HTTP ${code}" >&2; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# tag_commit TAG -> prints the commit SHA the tag points at (dereferences annotated tags)
|
||||
tag_commit() {
|
||||
local tag="$1" ref obj_type obj_sha rc=0
|
||||
ref=$(gh_api GET "git/ref/tags/${tag}") || rc=$?
|
||||
if [[ ${rc} -ne 0 ]]; then
|
||||
echo "ERROR: Could not read tag ${tag} (rc=${rc}): ${ref}" >&2
|
||||
return 1
|
||||
fi
|
||||
obj_type=$(echo "${ref}" | jq -r '.object.type')
|
||||
obj_sha=$(echo "${ref}" | jq -r '.object.sha')
|
||||
if [[ "${obj_type}" == "tag" ]]; then
|
||||
rc=0
|
||||
ref=$(gh_api GET "git/tags/${obj_sha}") || rc=$?
|
||||
if [[ ${rc} -ne 0 ]]; then
|
||||
echo "ERROR: Could not dereference annotated tag ${tag} (rc=${rc})" >&2
|
||||
return 1
|
||||
fi
|
||||
obj_sha=$(echo "${ref}" | jq -r '.object.sha')
|
||||
fi
|
||||
echo "${obj_sha}"
|
||||
}
|
||||
|
||||
ALLOCATED=""
|
||||
for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do
|
||||
CANDIDATE="${MAJOR}.${MINOR}.${PATCH}"
|
||||
TAG="v${CANDIDATE}"
|
||||
rc=0
|
||||
response=$(gh_api POST "git/refs" "{\"ref\":\"refs/tags/${TAG}\",\"sha\":\"${SHA}\"}") || rc=$?
|
||||
|
||||
if [[ ${rc} -eq 0 ]]; then
|
||||
echo "Reserved ${TAG} for ${SHA}."
|
||||
ALLOCATED="${CANDIDATE}"
|
||||
break
|
||||
fi
|
||||
|
||||
if [[ ${rc} -ne 3 ]]; then
|
||||
echo "ERROR: Failed to create tag ${TAG} (rc=${rc}): ${response}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OWNER=$(tag_commit "${TAG}")
|
||||
if [[ "${OWNER}" == "${SHA}" ]]; then
|
||||
echo "${TAG} already points at ${SHA} (pipeline rerun); reusing it."
|
||||
ALLOCATED="${CANDIDATE}"
|
||||
break
|
||||
fi
|
||||
|
||||
echo "${TAG} is already taken by ${OWNER}; trying the next patch version."
|
||||
PATCH=$((PATCH + 1))
|
||||
done
|
||||
|
||||
if [[ -z "${ALLOCATED}" ]]; then
|
||||
echo "ERROR: Could not reserve a release version after ${MAX_ATTEMPTS} attempts (last tried ${TAG})."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${ALLOCATED}" != "${VERSION}" ]]; then
|
||||
echo "GitVersion computed ${VERSION}, but it belongs to another commit; this build is ${ALLOCATED}."
|
||||
fi
|
||||
|
||||
{
|
||||
echo "export PROJECT_VERSION='${ALLOCATED}'"
|
||||
echo "export GITVERSION_PATCH='${PATCH}'"
|
||||
echo "export MUNITOR_RELEASE_TAG='v${ALLOCATED}'"
|
||||
} >> "${BASH_ENV}"
|
||||
|
||||
echo "Release version: ${ALLOCATED} (tag v${ALLOCATED} -> ${SHA})"
|
||||
@@ -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."
|
||||
@@ -70,28 +83,40 @@ for i in $(seq 0 $((COUNT - 1))); do
|
||||
|
||||
# Build
|
||||
echo "Building ${FULL_TAG} ..."
|
||||
docker build -f "${DOCKERFILE}" -t "${FULL_TAG}" .
|
||||
# Refresh base images and rerun package upgrades in supplemental Dockerfiles.
|
||||
BUILD_ARGS=(--pull --no-cache -f "${DOCKERFILE}" -t "${FULL_TAG}")
|
||||
if [[ -n "${CIRCLE_SHA1:-}" ]]; then
|
||||
BUILD_ARGS+=(--label "org.opencontainers.image.revision=${CIRCLE_SHA1}")
|
||||
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})."
|
||||
@@ -23,10 +23,24 @@ 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
|
||||
if [[ -n "${CIRCLE_SHA1:-}" ]]; then
|
||||
# Provenance label: guard_image_tags and update_cd_repo use it to prove which
|
||||
# commit a version tag was built from.
|
||||
BUILD_CMD+=(--label "org.opencontainers.image.revision=${CIRCLE_SHA1}")
|
||||
fi
|
||||
BUILD_CMD+=(--build-arg "VERSION=${VERSION}" --build-arg "GIT_COMMIT_SHA=${CIRCLE_SHA1:-}" .)
|
||||
|
||||
"${BUILD_CMD[@]}"
|
||||
|
||||
@@ -38,6 +38,8 @@ extract_munitor_vars() {
|
||||
fi
|
||||
MUNITOR_CD_REPO=$(yq '.cd.repo // ""' "${config_file}")
|
||||
MUNITOR_CD_FORMAT=$(yq '.cd.format // "helm"' "${config_file}")
|
||||
MUNITOR_CD_PATH=$(yq '.cd.path // ""' "${config_file}")
|
||||
MUNITOR_CD_PRODUCTION_ONLY=$(yq '.cd.production_only // false' "${config_file}")
|
||||
MUNITOR_CD_ENV_DEVELOP=$(yq '.cd.env.develop // "develop"' "${config_file}")
|
||||
MUNITOR_CD_ENV_STAGING=$(yq '.cd.env.staging // "staging"' "${config_file}")
|
||||
MUNITOR_CD_ENV_PROD=$(yq '.cd.env.prod // "prod"' "${config_file}")
|
||||
@@ -96,6 +98,7 @@ extract_munitor_vars() {
|
||||
if [[ -n "${MUNITOR_TEST_COVERAGE_MIN}" ]]; then
|
||||
MUNITOR_COVERAGE_MIN="${MUNITOR_TEST_COVERAGE_MIN}"
|
||||
fi
|
||||
MUNITOR_COVERAGE_SUMMARY_PATH=$(yq '.test.coverage.summary_path // "coverage/coverage-summary.json"' "${config_file}")
|
||||
|
||||
# Terraform pipeline settings
|
||||
MUNITOR_TF_PATH=$(yq '.terraform.path // "terraform/"' "${config_file}")
|
||||
@@ -193,8 +196,22 @@ extract_munitor_vars() {
|
||||
|
||||
if [[ -n "${MUNITOR_CD_REPO}" ]]; then
|
||||
MUNITOR_CD="true"
|
||||
if [[ "${MUNITOR_CD_PRODUCTION_ONLY}" == "true" ]]; then
|
||||
MUNITOR_CD_NON_PROD="false"
|
||||
else
|
||||
MUNITOR_CD_NON_PROD="true"
|
||||
fi
|
||||
else
|
||||
MUNITOR_CD="false"
|
||||
MUNITOR_CD_NON_PROD="false"
|
||||
fi
|
||||
|
||||
# cd_path is only emitted when set, so manifests without cd.path still render
|
||||
# pipelines that validate against orb versions predating the parameter.
|
||||
if [[ -n "${MUNITOR_CD_PATH}" ]]; then
|
||||
MUNITOR_CD_EXPLICIT_PATH="true"
|
||||
else
|
||||
MUNITOR_CD_EXPLICIT_PATH="false"
|
||||
fi
|
||||
|
||||
if [[ -n "${MUNITOR_CONTEXT_NVD}" ]]; then
|
||||
@@ -205,7 +222,7 @@ extract_munitor_vars() {
|
||||
|
||||
# Export all variables
|
||||
export MUNITOR_PIPELINE MUNITOR_ORB_VERSION MUNITOR_IMAGE_NAME MUNITOR_JAVA_VERSION MUNITOR_NODE_VERSION MUNITOR_SERVER_MODULE
|
||||
export MUNITOR_SONAR_PROJECT_KEY MUNITOR_DOCKER_REGISTRY MUNITOR_CD_REPO MUNITOR_CD_FORMAT
|
||||
export MUNITOR_SONAR_PROJECT_KEY MUNITOR_DOCKER_REGISTRY MUNITOR_CD_REPO MUNITOR_CD_FORMAT MUNITOR_CD_PATH MUNITOR_CD_PRODUCTION_ONLY
|
||||
export MUNITOR_CD_ENV_DEVELOP MUNITOR_CD_ENV_STAGING MUNITOR_CD_ENV_PROD MUNITOR_CD_ENV_RELEASE
|
||||
export MUNITOR_COVERAGE_MIN MUNITOR_E2E MUNITOR_SBOM MUNITOR_OWASP
|
||||
export MUNITOR_CONTEXT_REGISTRY MUNITOR_CONTEXT_GITHUB MUNITOR_CONTEXT_SONAR MUNITOR_CONTEXT_NVD
|
||||
@@ -214,7 +231,7 @@ extract_munitor_vars() {
|
||||
export MUNITOR_SERVICES MUNITOR_SERVICES_JSON
|
||||
export MUNITOR_TEST_SETUP MUNITOR_TEST_SETUP_SCRIPT
|
||||
export MUNITOR_CUSTOM_TEST MUNITOR_TEST_COMMANDS_JSON
|
||||
export MUNITOR_COVERAGE_TOOL MUNITOR_COVERAGE_CMD MUNITOR_COVERAGE_COMMAND
|
||||
export MUNITOR_COVERAGE_TOOL MUNITOR_COVERAGE_CMD MUNITOR_COVERAGE_COMMAND MUNITOR_COVERAGE_SUMMARY_PATH
|
||||
export MUNITOR_TF_PATH MUNITOR_TF_LIVE_PATH MUNITOR_TF_ENVIRONMENTS MUNITOR_CHECKOV_SKIP MUNITOR_SAST MUNITOR_SAST_FAIL_ON_FINDINGS
|
||||
export MUNITOR_KUSTOMIZE_VERSION MUNITOR_KUSTOMIZE_BASE_PATH MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR
|
||||
export MUNITOR_KUSTOMIZE_SCAN_OVERLAY MUNITOR_KUSTOMIZE_OVERLAYS MUNITOR_KUSTOMIZE_OVERLAY_DIR
|
||||
@@ -223,11 +240,11 @@ extract_munitor_vars() {
|
||||
export MUNITOR_CI_EMAIL MUNITOR_CI_NAME MUNITOR_NPM_DEFAULT_SCOPE MUNITOR_ORB_SLUG
|
||||
export MUNITOR_HEALTH_PATH MUNITOR_HEALTH_PORT MUNITOR_HEALTH_DB MUNITOR_HEALTH_MIGRATIONS MUNITOR_HEALTH_SMOKE_PATHS
|
||||
export MUNITOR_SUPPLEMENTAL MUNITOR_SUPPLEMENTAL_IMAGES_JSON
|
||||
export MUNITOR_SONAR MUNITOR_CD MUNITOR_NVD
|
||||
export MUNITOR_SONAR MUNITOR_CD MUNITOR_CD_NON_PROD MUNITOR_NVD MUNITOR_CD_EXPLICIT_PATH
|
||||
}
|
||||
|
||||
# Build the envsubst variable list
|
||||
get_envsubst_vars() {
|
||||
# shellcheck disable=SC2016
|
||||
echo '${MUNITOR_PIPELINE} ${MUNITOR_ORB_VERSION} ${MUNITOR_IMAGE_NAME} ${MUNITOR_JAVA_VERSION} ${MUNITOR_NODE_VERSION} ${MUNITOR_SONAR_PROJECT_KEY} ${MUNITOR_DOCKER_REGISTRY} ${MUNITOR_CD_REPO} ${MUNITOR_CD_FORMAT} ${MUNITOR_CD_ENV_DEVELOP} ${MUNITOR_CD_ENV_STAGING} ${MUNITOR_CD_ENV_PROD} ${MUNITOR_CD_ENV_RELEASE} ${MUNITOR_COVERAGE_MIN} ${MUNITOR_E2E} ${MUNITOR_SBOM} ${MUNITOR_OWASP} ${MUNITOR_CONTEXT_REGISTRY} ${MUNITOR_CONTEXT_GITHUB} ${MUNITOR_CONTEXT_SONAR} ${MUNITOR_CONTEXT_NVD} ${MUNITOR_NPM_AUTH} ${MUNITOR_NPM_SCOPES} ${MUNITOR_SERVICES_JSON} ${MUNITOR_TEST_SETUP_SCRIPT} ${MUNITOR_TEST_COMMANDS_JSON} ${MUNITOR_COVERAGE_TOOL} ${MUNITOR_COVERAGE_COMMAND} ${MUNITOR_GITHUB_RELEASE} ${MUNITOR_SAST} ${MUNITOR_SAST_FAIL_ON_FINDINGS} ${MUNITOR_HEALTH_PATH} ${MUNITOR_HEALTH_PORT} ${MUNITOR_HEALTH_DB} ${MUNITOR_HEALTH_MIGRATIONS} ${MUNITOR_HEALTH_SMOKE_PATHS} ${MUNITOR_TF_PATH} ${MUNITOR_TF_LIVE_PATH} ${MUNITOR_TF_ENVIRONMENTS} ${MUNITOR_CHECKOV_SKIP} ${MUNITOR_KUSTOMIZE_VERSION} ${MUNITOR_KUSTOMIZE_BASE_PATH} ${MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR} ${MUNITOR_KUSTOMIZE_SCAN_OVERLAY} ${MUNITOR_KUSTOMIZE_OVERLAYS} ${MUNITOR_KUSTOMIZE_OVERLAY_DIR} ${MUNITOR_KUBE_LINTER_CONFIG} ${MUNITOR_YAMLLINT_PATHS} ${MUNITOR_CI_EMAIL} ${MUNITOR_CI_NAME} ${MUNITOR_NPM_DEFAULT_SCOPE} ${MUNITOR_ORB_SLUG} ${MUNITOR_PACKAGE_MANAGER} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON} ${MUNITOR_SERVER_MODULE}'
|
||||
echo '${MUNITOR_PIPELINE} ${MUNITOR_ORB_VERSION} ${MUNITOR_IMAGE_NAME} ${MUNITOR_JAVA_VERSION} ${MUNITOR_NODE_VERSION} ${MUNITOR_SONAR_PROJECT_KEY} ${MUNITOR_DOCKER_REGISTRY} ${MUNITOR_CD_REPO} ${MUNITOR_CD_FORMAT} ${MUNITOR_CD_PATH} ${MUNITOR_CD_ENV_DEVELOP} ${MUNITOR_CD_ENV_STAGING} ${MUNITOR_CD_ENV_PROD} ${MUNITOR_CD_ENV_RELEASE} ${MUNITOR_COVERAGE_MIN} ${MUNITOR_E2E} ${MUNITOR_SBOM} ${MUNITOR_OWASP} ${MUNITOR_CONTEXT_REGISTRY} ${MUNITOR_CONTEXT_GITHUB} ${MUNITOR_CONTEXT_SONAR} ${MUNITOR_CONTEXT_NVD} ${MUNITOR_NPM_AUTH} ${MUNITOR_NPM_SCOPES} ${MUNITOR_SERVICES_JSON} ${MUNITOR_TEST_SETUP_SCRIPT} ${MUNITOR_TEST_COMMANDS_JSON} ${MUNITOR_COVERAGE_TOOL} ${MUNITOR_COVERAGE_COMMAND} ${MUNITOR_GITHUB_RELEASE} ${MUNITOR_SAST} ${MUNITOR_SAST_FAIL_ON_FINDINGS} ${MUNITOR_HEALTH_PATH} ${MUNITOR_HEALTH_PORT} ${MUNITOR_HEALTH_DB} ${MUNITOR_HEALTH_MIGRATIONS} ${MUNITOR_HEALTH_SMOKE_PATHS} ${MUNITOR_TF_PATH} ${MUNITOR_TF_LIVE_PATH} ${MUNITOR_TF_ENVIRONMENTS} ${MUNITOR_CHECKOV_SKIP} ${MUNITOR_KUSTOMIZE_VERSION} ${MUNITOR_KUSTOMIZE_BASE_PATH} ${MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR} ${MUNITOR_KUSTOMIZE_SCAN_OVERLAY} ${MUNITOR_KUSTOMIZE_OVERLAYS} ${MUNITOR_KUSTOMIZE_OVERLAY_DIR} ${MUNITOR_KUBE_LINTER_CONFIG} ${MUNITOR_YAMLLINT_PATHS} ${MUNITOR_CI_EMAIL} ${MUNITOR_CI_NAME} ${MUNITOR_NPM_DEFAULT_SCOPE} ${MUNITOR_ORB_SLUG} ${MUNITOR_PACKAGE_MANAGER} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON} ${MUNITOR_SERVER_MODULE} ${MUNITOR_COVERAGE_SUMMARY_PATH}'
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_FILE="${MUNITOR_CONFIG:-.munitor.yml}"
|
||||
OUTPUT_FILE="/tmp/generated-config.yml"
|
||||
OUTPUT_FILE="${MUNITOR_OUTPUT_FILE:-/tmp/generated-config.yml}"
|
||||
|
||||
# When run via CircleCI << include() >>, BASH_SOURCE is empty.
|
||||
# Fall back to MUNITOR_SCRIPT_DIR set by the command YAML.
|
||||
@@ -80,6 +80,9 @@ validate_required_fields() {
|
||||
validate-cd-repo|argocd-apps)
|
||||
# No additional required fields beyond orb_version
|
||||
;;
|
||||
obsidian-plugin)
|
||||
[[ -z "$(yq '.contexts.github // ""' "${config}")" ]] && missing+=("contexts.github")
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
@@ -94,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
|
||||
@@ -198,7 +201,7 @@ process_conditionals() {
|
||||
cp "${input}" "${tmpfile}"
|
||||
|
||||
# Process each conditional flag
|
||||
for flag in E2E SBOM SONAR NPM_AUTH SERVICES TEST_SETUP CUSTOM_TEST COVERAGE_CMD GITHUB_RELEASE SAST CD OWASP NVD SUPPLEMENTAL; do
|
||||
for flag in E2E SBOM SONAR NPM_AUTH SERVICES TEST_SETUP CUSTOM_TEST COVERAGE_CMD GITHUB_RELEASE SAST CD CD_NON_PROD CD_EXPLICIT_PATH OWASP NVD SUPPLEMENTAL; do
|
||||
local var_name="MUNITOR_${flag}"
|
||||
local value="${!var_name:-false}"
|
||||
local flag_file
|
||||
|
||||
@@ -42,7 +42,42 @@ case "${MUNITOR_PIPELINE}" in
|
||||
nextjs)
|
||||
# Multi-stage Next.js standalone Dockerfile.
|
||||
# Assumes `output: 'standalone'` in next.config.js.
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
case "${MUNITOR_PACKAGE_MANAGER}" in
|
||||
pnpm)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
ENV STANDALONE=true
|
||||
ARG GIT_COMMIT_SHA
|
||||
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
|
||||
RUN pnpm run build
|
||||
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
|
||||
WORKDIR /app
|
||||
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
|
||||
RUN npm cache clean --force && rm -rf /usr/local/lib/node_modules /usr/local/bin/npm /usr/local/bin/npx
|
||||
RUN addgroup --system --gid 1001 nodejs \\
|
||||
&& adduser --system --uid 1001 nextjs
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
CMD ["node", "server.js"]
|
||||
DOCKERFILE
|
||||
;;
|
||||
*)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
@@ -73,11 +108,38 @@ EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
CMD ["node", "server.js"]
|
||||
DOCKERFILE
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
express)
|
||||
# Two-stage Express Dockerfile. Uses package.json "main" field via `node .`.
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
case "${MUNITOR_PACKAGE_MANAGER}" in
|
||||
pnpm)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --prod
|
||||
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
|
||||
WORKDIR /app
|
||||
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
|
||||
RUN npm cache clean --force && rm -rf /usr/local/lib/node_modules /usr/local/bin/npm /usr/local/bin/npx
|
||||
RUN addgroup --system --gid 1001 nodejs \\
|
||||
&& adduser --system --uid 1001 appuser -G nodejs
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
USER appuser
|
||||
EXPOSE ${MUNITOR_HEALTH_PORT}
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:${MUNITOR_HEALTH_PORT}${MUNITOR_HEALTH_PATH} || exit 1
|
||||
CMD ["node", "."]
|
||||
DOCKERFILE
|
||||
;;
|
||||
*)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
@@ -97,6 +159,8 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:${MUNITOR_HEALTH_PORT}${MUNITOR_HEALTH_PATH} || exit 1
|
||||
CMD ["node", "."]
|
||||
DOCKERFILE
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
*)
|
||||
@@ -197,8 +261,42 @@ DOCKERFILE
|
||||
;;
|
||||
|
||||
node-webapp)
|
||||
echo "Generating Dockerfile for node-webapp (node ${MUNITOR_NODE_VERSION})"
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
if yq -e '.docker.use_repo_dockerfile == true' "${CONFIG_FILE}" &>/dev/null; then
|
||||
echo "Using repository Dockerfile for node-webapp"
|
||||
if [[ ! -f Dockerfile ]]; then
|
||||
echo "ERROR: docker.use_repo_dockerfile requires a Dockerfile in the repository root." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Generating Dockerfile for node-webapp (node ${MUNITOR_NODE_VERSION})"
|
||||
# Single source for the runtime base. debian12 ships libssl3 3.0.18 with
|
||||
# open HIGH/CRITICAL CVEs that fail the Trivy gate; debian13 scans clean.
|
||||
NODE_WEBAPP_RUNTIME_IMAGE="gcr.io/distroless/nodejs${MUNITOR_NODE_VERSION}-debian13"
|
||||
case "${MUNITOR_PACKAGE_MANAGER}" in
|
||||
pnpm)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY . .
|
||||
ARG GIT_COMMIT_SHA
|
||||
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
|
||||
RUN pnpm run build
|
||||
|
||||
FROM ${NODE_WEBAPP_RUNTIME_IMAGE} AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/public ./public
|
||||
EXPOSE ${MUNITOR_HEALTH_PORT}
|
||||
CMD ["server.js"]
|
||||
DOCKERFILE
|
||||
;;
|
||||
*)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
@@ -208,7 +306,7 @@ ARG GIT_COMMIT_SHA
|
||||
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
|
||||
RUN npm run build
|
||||
|
||||
FROM gcr.io/distroless/nodejs${MUNITOR_NODE_VERSION}-debian12 AS runner
|
||||
FROM ${NODE_WEBAPP_RUNTIME_IMAGE} AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
@@ -217,6 +315,9 @@ COPY --from=builder /app/public ./public
|
||||
EXPOSE ${MUNITOR_HEALTH_PORT}
|
||||
CMD ["server.js"]
|
||||
DOCKERFILE
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
|
||||
@@ -35,7 +35,9 @@ if [[ "${VERSION}" =~ -(RC|alpha|beta|SNAPSHOT) ]]; then
|
||||
TARGET="${CIRCLE_BRANCH:-main}"
|
||||
else
|
||||
PRERELEASE_FLAG=""
|
||||
TARGET="main"
|
||||
# Target the commit this pipeline built, never the moving branch head: a
|
||||
# later merge must not receive this version's tag.
|
||||
TARGET="${CIRCLE_SHA1:-main}"
|
||||
fi
|
||||
|
||||
echo "Target: ${TARGET}"
|
||||
@@ -44,6 +46,16 @@ echo "Pre-release: ${PRERELEASE_FLAG:-no}"
|
||||
# Create tag + GitHub Release via API (skip if already exists)
|
||||
# Uses gh release create which handles both tag and release in one call,
|
||||
# bypassing CircleCI's read-only SSH deploy key entirely.
|
||||
# The release tag is normally reserved by allocate_version. If it exists it
|
||||
# must point at this commit; an existing tag is never moved.
|
||||
if [[ -n "${CIRCLE_SHA1:-}" ]] && TAG_SHA=$(gh api "repos/{owner}/{repo}/commits/${TAG}" --jq '.sha' 2>/dev/null); then
|
||||
if [[ "${TAG_SHA}" != "${CIRCLE_SHA1}" ]]; then
|
||||
echo "ERROR: Tag ${TAG} already points at ${TAG_SHA}, not ${CIRCLE_SHA1}. Refusing to release."
|
||||
exit 1
|
||||
fi
|
||||
echo "Tag ${TAG} points at ${CIRCLE_SHA1}."
|
||||
fi
|
||||
|
||||
if gh release view "${TAG}" >/dev/null 2>&1; then
|
||||
echo "Release ${TAG} already exists, skipping."
|
||||
else
|
||||
@@ -56,4 +68,20 @@ else
|
||||
echo "GitHub Release ${TAG} created."
|
||||
fi
|
||||
|
||||
# Upload release artifacts if specified
|
||||
if [[ -n "${MUNITOR_RELEASE_ARTIFACTS:-}" ]]; then
|
||||
echo "Uploading release artifacts: ${MUNITOR_RELEASE_ARTIFACTS}"
|
||||
for artifact in ${MUNITOR_RELEASE_ARTIFACTS}; do
|
||||
if [[ ! -f "${artifact}" ]]; then
|
||||
echo "ERROR: Release artifact '${artifact}' not found."
|
||||
echo " Working directory: $(pwd)"
|
||||
echo " Available files: $(ls -la)"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# shellcheck disable=SC2086
|
||||
gh release upload "${TAG}" ${MUNITOR_RELEASE_ARTIFACTS} --clobber
|
||||
echo "Artifacts uploaded to ${TAG}."
|
||||
fi
|
||||
|
||||
echo "github_release completed"
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared helpers
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
munitor_header "guard_image_tags"
|
||||
|
||||
# Version tags are immutable. Before anything is pushed, verify that every
|
||||
# version tag this job will publish is either absent from the registry or was
|
||||
# built from this same commit (a rerun). A tag built from a different commit
|
||||
# means two pipelines claimed the same version; overwriting it would silently
|
||||
# change what an already-deployed tag points at, so fail instead.
|
||||
# Mutable environment tags (develop, staging) are not checked.
|
||||
|
||||
munitor_check_tool docker --version
|
||||
munitor_check_tool jq --version
|
||||
|
||||
IMAGE="${IMAGE_NAME:?IMAGE_NAME not set}"
|
||||
REGISTRY="${REGISTRY:-ghcr.io/KofTwentyTwo}"
|
||||
VERSION="${PROJECT_VERSION:?PROJECT_VERSION not set -- run export_version_vars first}"
|
||||
SHA="${CIRCLE_SHA1:?CIRCLE_SHA1 not set}"
|
||||
SUPP_JSON="${SUPPLEMENTAL_IMAGES_JSON:-[]}"
|
||||
REVISION_LABEL="org.opencontainers.image.revision"
|
||||
|
||||
if [[ -z "${MUNITOR_SKIP_REGISTRY_LOGIN:-}" ]]; then
|
||||
TOKEN="${!GHCR_TOKEN_VAR:-}"
|
||||
USER="${!GHCR_USER_VAR:-}"
|
||||
if [[ -z "${TOKEN}" || -z "${USER}" ]]; then
|
||||
echo "ERROR: GHCR credentials not set (\$${GHCR_TOKEN_VAR:-GHCR_TOKEN}, \$${GHCR_USER_VAR:-GHCR_USERNAME})"
|
||||
exit 1
|
||||
fi
|
||||
echo "${TOKEN}" | docker login "${REGISTRY%%/*}" -u "${USER}" --password-stdin
|
||||
fi
|
||||
|
||||
REFS=("${REGISTRY}/${IMAGE}:${VERSION}")
|
||||
if [[ "${SUPP_JSON}" != "[]" && "${SUPP_JSON}" != "null" && -n "${SUPP_JSON}" ]]; then
|
||||
while IFS= read -r name; do
|
||||
name="${name%$'\r'}"
|
||||
REFS+=("${REGISTRY}/${IMAGE}-${name}:${VERSION}")
|
||||
done < <(echo "${SUPP_JSON}" | jq -r '.[].name')
|
||||
fi
|
||||
|
||||
CONFLICTS=0
|
||||
for ref in "${REFS[@]}"; do
|
||||
err=$(mktemp)
|
||||
rc=0
|
||||
config=$(docker buildx imagetools inspect "${ref}" --format '{{json .Image}}' 2>"${err}") || rc=$?
|
||||
if [[ ${rc} -ne 0 ]]; then
|
||||
if grep -qiE 'not found|manifest unknown|MANIFEST_UNKNOWN|no such manifest' "${err}"; then
|
||||
echo "OK: ${ref} does not exist yet."
|
||||
rm -f "${err}"
|
||||
continue
|
||||
fi
|
||||
echo "ERROR: Could not inspect ${ref}; refusing to push without proving the tag is free:"
|
||||
cat "${err}"
|
||||
rm -f "${err}"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "${err}"
|
||||
|
||||
# Single-platform images return one config; multi-platform images return a map of configs.
|
||||
revision=$(echo "${config}" | jq -r --arg l "${REVISION_LABEL}" \
|
||||
'[.. | objects | select(has("Labels")) | .Labels // {} | .[$l] // empty] | first // ""')
|
||||
|
||||
if [[ "${revision}" == "${SHA}" ]]; then
|
||||
echo "OK: ${ref} already exists for this commit (rerun); it will be republished from ${SHA}."
|
||||
else
|
||||
echo "ERROR: ${ref} already exists for a different commit (${revision:-unknown, no ${REVISION_LABEL} label})."
|
||||
CONFLICTS=$((CONFLICTS + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${CONFLICTS} -gt 0 ]]; then
|
||||
echo "Refusing to overwrite ${CONFLICTS} immutable version tag(s). Version ${VERSION} was claimed by another build;"
|
||||
echo "rerun from a fresh pipeline so allocate_version reserves a new version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All ${#REFS[@]} version tag(s) are free for ${SHA}."
|
||||
@@ -26,8 +26,9 @@ echo "deb [signed-by=/etc/apt/keyrings/adoptium.asc] \
|
||||
$(. /etc/os-release && echo "${VERSION_CODENAME}") main" \
|
||||
| sudo tee /etc/apt/sources.list.d/adoptium.list > /dev/null
|
||||
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq "temurin-${VERSION}-jdk"
|
||||
# CI cannot answer debconf or needrestart's service-restart dialog.
|
||||
sudo env DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get update -qq
|
||||
sudo env DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y -qq "temurin-${VERSION}-jdk"
|
||||
|
||||
# Set JAVA_HOME for subsequent steps
|
||||
JAVA_HOME_DIR="/usr/lib/jvm/temurin-${VERSION}-jdk-$(dpkg --print-architecture)"
|
||||
|
||||
@@ -64,3 +64,41 @@ NODE_BIN_DIR="$(dirname "$(command -v node)")"
|
||||
} >> "${BASH_ENV}"
|
||||
|
||||
echo " BASH_ENV: exported PATH with ${NODE_BIN_DIR}"
|
||||
|
||||
# Activate non-npm package managers via corepack
|
||||
PACKAGE_MANAGER="${MUNITOR_PACKAGE_MANAGER:-npm}"
|
||||
if [[ "${PACKAGE_MANAGER}" != "npm" ]]; then
|
||||
echo ""
|
||||
echo "=== Activating ${PACKAGE_MANAGER} via corepack ==="
|
||||
corepack enable
|
||||
|
||||
PM_VERSION=""
|
||||
if [[ -f "package.json" ]]; then
|
||||
# Priority 1: packageManager field (corepack-native, e.g. "pnpm@9.15.9")
|
||||
PM_FIELD=$(node -e "try{const p=require('./package.json').packageManager||'';console.log(p)}catch{console.log('')}" 2>/dev/null)
|
||||
if [[ "${PM_FIELD}" == "${PACKAGE_MANAGER}@"* ]]; then
|
||||
PM_VERSION="${PM_FIELD#*@}"
|
||||
fi
|
||||
|
||||
# Priority 2: engines field (e.g. "engines": {"pnpm": ">=9"})
|
||||
if [[ -z "${PM_VERSION}" ]]; then
|
||||
PM_VERSION=$(node -e "try{console.log(require('./package.json').engines?.['${PACKAGE_MANAGER}']||'')}catch{console.log('')}" 2>/dev/null)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${PM_VERSION}" ]]; then
|
||||
echo " Detected version: ${PM_VERSION}"
|
||||
corepack prepare "${PACKAGE_MANAGER}@${PM_VERSION}" --activate
|
||||
else
|
||||
echo " No version constraint found, using corepack default"
|
||||
fi
|
||||
|
||||
echo " ${PACKAGE_MANAGER}: $(${PACKAGE_MANAGER} --version)"
|
||||
|
||||
# Export PM binary to BASH_ENV for downstream steps
|
||||
PM_BIN_DIR="$(dirname "$(command -v "${PACKAGE_MANAGER}")")"
|
||||
if [[ "${PM_BIN_DIR}" != "${NODE_BIN_DIR}" ]]; then
|
||||
echo "export PATH=\"${PM_BIN_DIR}:\${PATH}\"" >> "${BASH_ENV}"
|
||||
echo " BASH_ENV: exported PATH with ${PM_BIN_DIR}"
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then
|
||||
source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
fi
|
||||
munitor_header "maven_cache_key"
|
||||
|
||||
# Hash the checked-out dependency definitions, not release-version rewrites.
|
||||
# The same index travels with the workspace to downstream Maven jobs.
|
||||
git ls-files -s -- pom.xml '**/pom.xml' '.mvn/*' > .munitor-maven-cache-key
|
||||
if [[ ! -s .munitor-maven-cache-key ]]; then
|
||||
echo "ERROR: Maven cache requires tracked dependency definitions." >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then
|
||||
source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
fi
|
||||
munitor_header "maven_workspace"
|
||||
|
||||
archive="${PWD}/.munitor-maven-installed.tgz"
|
||||
repository="${HOME}/.m2/repository"
|
||||
case "${MAVEN_WORKSPACE_ACTION:-restore}" in
|
||||
save)
|
||||
# Maven marks local installs with an empty repository ID (artifact>=).
|
||||
# Carry build outputs and mutable SNAPSHOT dependencies separately from the cache.
|
||||
files=$(mktemp)
|
||||
trap 'rm -f "${files}"' EXIT
|
||||
cd "${repository}"
|
||||
while IFS= read -r -d '' tracking; do
|
||||
if [[ "${tracking}" == */*-SNAPSHOT/_remote.repositories ]] ||
|
||||
grep -qE '^[^#].*>=[[:space:]]*$' "${tracking}"; then
|
||||
directory=$(dirname "${tracking}")
|
||||
printf '%s\0' "${directory}" >> "${files}"
|
||||
while [[ "${directory}" != . ]]; do
|
||||
directory=$(dirname "${directory}")
|
||||
if [[ -f "${directory}/maven-metadata-local.xml" ]]; then
|
||||
printf '%s\0' "${directory}/maven-metadata-local.xml" >> "${files}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done < <(find . -name _remote.repositories -type f -print0)
|
||||
tar -czf "${archive}" --null -T "${files}"
|
||||
;;
|
||||
restore)
|
||||
if [[ -f "${archive}" ]]; then
|
||||
mkdir -p "${repository}"
|
||||
tar -xzf "${archive}" -C "${repository}"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Unknown Maven workspace action." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -18,6 +18,7 @@ munitor_check_tool java -version
|
||||
echo "Running Maven build..."
|
||||
|
||||
mvn clean install \
|
||||
-U \
|
||||
-DskipTests \
|
||||
--batch-mode \
|
||||
--fail-at-end \
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared helpers
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
munitor_header "node_audit"
|
||||
|
||||
echo "Running npm audit..."
|
||||
|
||||
if [[ -f .npm-audit-exceptions.json ]]; then
|
||||
# The optional, repository-reviewed exception file never changes runtime policy.
|
||||
REPORT_DIR=$(mktemp -d)
|
||||
trap 'rm -rf "${REPORT_DIR}"' EXIT
|
||||
export REPORT_DIR
|
||||
npm audit --json --audit-level=high > "${REPORT_DIR}/full.json" || AUDIT_STATUS=$?
|
||||
npm audit --json --omit=dev --audit-level=high > "${REPORT_DIR}/production.json" || PRODUCTION_STATUS=$?
|
||||
export AUDIT_STATUS="${AUDIT_STATUS:-0}" PRODUCTION_STATUS="${PRODUCTION_STATUS:-0}"
|
||||
node <<'AUDIT_JS'
|
||||
const fs = require('node:fs');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
function fail(message) { throw new Error(message); }
|
||||
function readReport(name, status) {
|
||||
const report = JSON.parse(fs.readFileSync(`${process.env.REPORT_DIR}/${name}.json`, 'utf8'));
|
||||
if (report.error || report.auditReportVersion !== 2 || !report.vulnerabilities || !report.metadata?.vulnerabilities)
|
||||
fail(`${name}: npm audit returned an error or unsupported report`);
|
||||
if (![0, 1].includes(Number(status))) fail(`${name}: npm audit failed (${status})`);
|
||||
const counts = report.metadata.vulnerabilities;
|
||||
if (Object.values(report.vulnerabilities).some(v => !v || !['info', 'low', 'moderate', 'high', 'critical'].includes(v.severity)))
|
||||
fail(`${name}: invalid vulnerability severity`);
|
||||
for (const severity of ['info', 'low', 'moderate', 'high', 'critical']) {
|
||||
if (!Number.isInteger(counts[severity]) || counts[severity] < 0) fail(`${name}: invalid severity counts`);
|
||||
if (Object.values(report.vulnerabilities).filter(v => v.severity === severity).length !== counts[severity])
|
||||
fail(`${name}: inconsistent severity counts`);
|
||||
}
|
||||
return report;
|
||||
}
|
||||
try {
|
||||
const full = readReport('full', process.env.AUDIT_STATUS);
|
||||
const production = readReport('production', process.env.PRODUCTION_STATUS);
|
||||
if (production.metadata.vulnerabilities.high || production.metadata.vulnerabilities.critical)
|
||||
fail('Production dependencies contain high/critical vulnerabilities; exceptions do not apply');
|
||||
if (full.metadata.vulnerabilities.critical) fail('Critical vulnerabilities cannot be excepted');
|
||||
const config = JSON.parse(fs.readFileSync('.npm-audit-exceptions.json', 'utf8'));
|
||||
if (config.schemaVersion !== 1 || !Array.isArray(config.exceptions) || !config.exceptions.length)
|
||||
fail('Invalid exception configuration');
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8'));
|
||||
if (!lock.packages) fail('Exceptions require a package-lock.json with package paths');
|
||||
const allowed = new Map();
|
||||
for (const entry of config.exceptions) {
|
||||
if (!/^EX-\d{4}$/.test(entry.id) || !/^GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/.test(entry.advisory)
|
||||
|| entry.scope !== 'development' || !/^\d{4}-\d{2}-\d{2}$/.test(entry.expires)
|
||||
|| !Number.isFinite(Date.parse(entry.expires))
|
||||
|| new Date(entry.expires).toISOString().slice(0, 10) !== entry.expires || today >= entry.expires
|
||||
|| typeof entry.package !== 'string' || !/^(@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(entry.package)
|
||||
|| !/^\d+\.\d+\.\d+$/.test(entry.version) || allowed.has(entry.advisory))
|
||||
fail('Invalid, duplicated or expired development exception');
|
||||
allowed.set(entry.advisory, entry);
|
||||
}
|
||||
function roots(name, seen = new Set()) {
|
||||
if (seen.has(name)) fail(`Cyclic vulnerability chain: ${name}`);
|
||||
const vulnerability = full.vulnerabilities[name];
|
||||
if (!vulnerability || !Array.isArray(vulnerability.via) || !vulnerability.via.length)
|
||||
fail(`Unresolved vulnerability chain: ${name}`);
|
||||
const next = new Set([...seen, name]);
|
||||
return vulnerability.via.flatMap(via => {
|
||||
if (typeof via === 'string') return roots(via, next);
|
||||
if (!via || typeof via.url !== 'string' || typeof via.severity !== 'string')
|
||||
fail(`Invalid advisory: ${name}`);
|
||||
return [{ advisory: via, vulnerability }];
|
||||
});
|
||||
}
|
||||
const used = new Set();
|
||||
for (const [name, vulnerability] of Object.entries(full.vulnerabilities)) {
|
||||
if (vulnerability.severity !== 'high') continue;
|
||||
for (const { advisory, vulnerability: root } of roots(name)) {
|
||||
if (!['high', 'critical'].includes(advisory.severity)) continue;
|
||||
const id = advisory.url.replace('https://github.com/advisories/', '');
|
||||
const exception = allowed.get(id);
|
||||
if (!exception || advisory.url !== `https://github.com/advisories/${id}`
|
||||
|| advisory.severity !== 'high' || advisory.name !== exception.package
|
||||
|| root.name !== exception.package || !Array.isArray(root.nodes) || !root.nodes.length)
|
||||
fail(`Unexcepted high vulnerability: ${name} (${advisory.url})`);
|
||||
for (const path of root.nodes) {
|
||||
if (!lock.packages[path] || lock.packages[path].dev !== true || lock.packages[path].version !== exception.version)
|
||||
fail(`Exception does not cover package path or version: ${path}`);
|
||||
}
|
||||
used.add(id);
|
||||
}
|
||||
if (!roots(name).some(r => ['high', 'critical'].includes(r.advisory.severity)))
|
||||
fail(`High vulnerability has no resolvable high advisory: ${name}`);
|
||||
}
|
||||
for (const id of used) {
|
||||
const entry = allowed.get(id);
|
||||
// Any newer release requires reassessment, even if npm proposes a parent downgrade.
|
||||
const latest = execFileSync('npm', ['view', entry.package, 'version', '--json'],
|
||||
{ encoding: 'utf8', timeout: 30000 });
|
||||
if (JSON.parse(latest) !== entry.version) fail(`New ${entry.package} release available; reassess ${entry.id}`);
|
||||
console.log(`Accepted ${entry.id}: ${id}, development only, expires ${entry.expires}`);
|
||||
}
|
||||
console.log('Full audit checked; production audit has no high/critical findings.');
|
||||
} catch (error) {
|
||||
console.error(`npm audit rejected: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
AUDIT_JS
|
||||
exit 0
|
||||
fi
|
||||
|
||||
npm audit --audit-level=high || {
|
||||
EXIT_CODE=$?
|
||||
echo "npm audit found high/critical vulnerabilities."
|
||||
exit "${EXIT_CODE}"
|
||||
}
|
||||
|
||||
echo "npm audit passed."
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared helpers
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
MIN="${MIN_COVERAGE:-70}"
|
||||
COVERAGE_TOOL="${MUNITOR_COVERAGE_TOOL:-jest}"
|
||||
COVERAGE_COMMAND="${MUNITOR_COVERAGE_COMMAND:-}"
|
||||
SUMMARY_PATH="${MUNITOR_COVERAGE_SUMMARY_PATH:-coverage/coverage-summary.json}"
|
||||
|
||||
munitor_header "node_coverage_check (min: ${MIN}%)"
|
||||
munitor_check_tool node --version
|
||||
|
||||
# Validate MIN_COVERAGE is numeric
|
||||
if ! [[ "${MIN}" =~ ^[0-9]+$ ]]; then
|
||||
echo "ERROR: MIN_COVERAGE must be a whole number, got '${MIN}'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Checking coverage threshold (minimum: ${MIN}%, tool: ${COVERAGE_TOOL})..."
|
||||
|
||||
# Run custom coverage report command if specified
|
||||
if [[ -n "${COVERAGE_COMMAND}" ]]; then
|
||||
echo "Running coverage report command: ${COVERAGE_COMMAND}"
|
||||
bash -c "${COVERAGE_COMMAND}"
|
||||
fi
|
||||
|
||||
# Resolve summary file(s) - supports glob patterns for monorepos
|
||||
# shellcheck disable=SC2206
|
||||
SUMMARY_FILES=( ${SUMMARY_PATH} )
|
||||
|
||||
if [[ ${#SUMMARY_FILES[@]} -eq 0 ]] || [[ ! -f "${SUMMARY_FILES[0]}" ]]; then
|
||||
echo "ERROR: Coverage summary not found at ${SUMMARY_PATH}."
|
||||
echo ""
|
||||
echo " Ensure your test runner generates coverage-summary.json."
|
||||
echo " For monorepos, set test.coverage.summary_path in .munitor.yml:"
|
||||
echo " test:"
|
||||
echo " coverage:"
|
||||
echo " summary_path: \"packages/*/coverage/coverage-summary.json\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ${#SUMMARY_FILES[@]} -eq 1 ]]; then
|
||||
# Single file - use directly
|
||||
SUMMARY_FILE="${SUMMARY_FILES[0]}"
|
||||
echo "Using coverage summary: ${SUMMARY_FILE}"
|
||||
|
||||
ACTUAL=$(node -e "
|
||||
const s = require('./${SUMMARY_FILE}');
|
||||
console.log(s.total.lines.pct);
|
||||
")
|
||||
else
|
||||
# Multiple files (monorepo) - compute weighted average
|
||||
echo "Found ${#SUMMARY_FILES[@]} coverage summaries (monorepo mode):"
|
||||
for f in "${SUMMARY_FILES[@]}"; do
|
||||
echo " - ${f}"
|
||||
done
|
||||
|
||||
ACTUAL=$(node -e "
|
||||
const files = process.argv.slice(1);
|
||||
let totalCovered = 0, totalTotal = 0;
|
||||
for (const f of files) {
|
||||
const s = require('./' + f);
|
||||
totalCovered += s.total.lines.covered;
|
||||
totalTotal += s.total.lines.total;
|
||||
}
|
||||
console.log(totalTotal > 0 ? ((totalCovered / totalTotal) * 100).toFixed(2) : '0');
|
||||
" "${SUMMARY_FILES[@]}")
|
||||
fi
|
||||
|
||||
echo "Line coverage: ${ACTUAL}%"
|
||||
|
||||
# Compare as integers (floor)
|
||||
ACTUAL_INT=${ACTUAL%.*}
|
||||
if [[ "${ACTUAL_INT}" -lt "${MIN}" ]]; then
|
||||
echo "FAIL: Coverage ${ACTUAL}% is below minimum ${MIN}%."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Coverage check passed (${ACTUAL}% >= ${MIN}%)."
|
||||
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared helpers
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
PACKAGE_MANAGER="${MUNITOR_PACKAGE_MANAGER:-npm}"
|
||||
|
||||
munitor_header "node_install_deps (${PACKAGE_MANAGER})"
|
||||
munitor_check_tool node --version
|
||||
|
||||
case "${PACKAGE_MANAGER}" in
|
||||
npm)
|
||||
munitor_check_tool npm --version
|
||||
|
||||
# Verify package-lock.json exists
|
||||
if [[ ! -f "package-lock.json" ]]; then
|
||||
echo "ERROR: package-lock.json not found in $(pwd)"
|
||||
echo " npm ci requires a lockfile. Run 'npm install' locally and commit the lockfile."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check lockfile version compatibility
|
||||
LOCKFILE_VERSION=$(node -e "console.log(require('./package-lock.json').lockfileVersion || 'unknown')" 2>/dev/null || echo "unknown")
|
||||
NPM_MAJOR=$(npm --version 2>/dev/null | cut -d. -f1)
|
||||
echo " lockfileVersion: ${LOCKFILE_VERSION}"
|
||||
|
||||
if [[ "${LOCKFILE_VERSION}" == "3" && -n "${NPM_MAJOR}" && "${NPM_MAJOR}" -lt 11 ]]; then
|
||||
echo ""
|
||||
echo "WARNING: package-lock.json uses lockfileVersion 3 (npm 11+)"
|
||||
echo " but CI is running npm ${NPM_MAJOR} ($(npm --version))."
|
||||
echo " This will likely cause 'npm ci' to fail."
|
||||
echo ""
|
||||
echo " Fix: regenerate your lockfile with the same Node version CI uses."
|
||||
echo " Add an .nvmrc to your repo to keep local and CI versions in sync."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Running npm ci..."
|
||||
npm ci
|
||||
|
||||
PKG_COUNT=$(find node_modules -maxdepth 1 -mindepth 1 -type d | wc -l | tr -d ' ')
|
||||
echo "npm ci complete. ${PKG_COUNT} packages installed."
|
||||
;;
|
||||
|
||||
pnpm)
|
||||
munitor_check_tool pnpm --version
|
||||
|
||||
# Verify pnpm-lock.yaml exists
|
||||
if [[ ! -f "pnpm-lock.yaml" ]]; then
|
||||
echo "ERROR: pnpm-lock.yaml not found in $(pwd)"
|
||||
echo " pnpm install --frozen-lockfile requires a lockfile. Run 'pnpm install' locally and commit the lockfile."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Running pnpm install --frozen-lockfile..."
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
PKG_COUNT=$(find node_modules -maxdepth 1 -mindepth 1 -type d | wc -l | tr -d ' ')
|
||||
echo "pnpm install complete. ${PKG_COUNT} packages installed."
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "ERROR: Unsupported package manager '${PACKAGE_MANAGER}'"
|
||||
echo " Supported values: npm, pnpm"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prisma auto-detection: generate client if schema files are present
|
||||
PRISMA_SCHEMAS=$(find . -path "*/prisma/schema.prisma" -not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/build/*" 2>/dev/null || true)
|
||||
|
||||
if [[ -n "${PRISMA_SCHEMAS}" ]]; then
|
||||
echo ""
|
||||
echo "=== Prisma schema(s) detected ==="
|
||||
|
||||
PRISMA_GENERATED=0
|
||||
while IFS= read -r SCHEMA_PATH; do
|
||||
# Resolve the package directory (parent of prisma/)
|
||||
PKG_DIR=$(dirname "$(dirname "${SCHEMA_PATH}")")
|
||||
|
||||
# Check if prisma CLI is available from the package directory.
|
||||
# In monorepos, prisma may only be installed in a workspace package,
|
||||
# not at the root, so we must check from the package's own context.
|
||||
PRISMA_CMD=""
|
||||
case "${PACKAGE_MANAGER}" in
|
||||
npm)
|
||||
if (cd "${PKG_DIR}" && npx prisma --version) &>/dev/null; then
|
||||
PRISMA_CMD="npx prisma"
|
||||
fi
|
||||
;;
|
||||
pnpm)
|
||||
if (cd "${PKG_DIR}" && pnpm exec prisma --version) &>/dev/null; then
|
||||
PRISMA_CMD="pnpm exec prisma"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "${PRISMA_CMD}" ]]; then
|
||||
echo " Generating Prisma client for: ${SCHEMA_PATH}"
|
||||
(cd "${PKG_DIR}" && ${PRISMA_CMD} generate --schema=prisma/schema.prisma)
|
||||
PRISMA_GENERATED=$((PRISMA_GENERATED + 1))
|
||||
else
|
||||
echo " WARNING: Found ${SCHEMA_PATH} but prisma CLI not available in ${PKG_DIR}."
|
||||
echo " Add prisma as a dev dependency in that package."
|
||||
fi
|
||||
done <<< "${PRISMA_SCHEMAS}"
|
||||
|
||||
if [[ "${PRISMA_GENERATED}" -gt 0 ]]; then
|
||||
echo "Prisma generate complete (${PRISMA_GENERATED} schema(s))."
|
||||
fi
|
||||
fi
|
||||
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
munitor_header "npm_lint"
|
||||
munitor_header "node_lint"
|
||||
|
||||
echo "Running ESLint..."
|
||||
|
||||
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
munitor_header "npm_sbom"
|
||||
munitor_header "node_sbom"
|
||||
munitor_check_tool node --version
|
||||
munitor_check_tool npx --version
|
||||
|
||||
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
munitor_header "npm_sonar"
|
||||
munitor_header "node_sonar"
|
||||
|
||||
PROJECT_KEY="${SONAR_PROJECT_KEY:?SONAR_PROJECT_KEY not set}"
|
||||
ORG="${SONAR_ORG:-KofTwentyTwo}"
|
||||
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
munitor_header "npm_test"
|
||||
munitor_header "node_test"
|
||||
|
||||
# Prevent interactive prompts from apt/dpkg when test commands install system deps
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
@@ -34,36 +34,44 @@ if [[ -n "${TEST_COMMANDS_JSON}" && "${TEST_COMMANDS_JSON}" != "[]" && "${TEST_C
|
||||
|
||||
echo "Custom test commands complete."
|
||||
else
|
||||
# Default: Jest with coverage and JUnit reporter
|
||||
if ! npx jest --version &>/dev/null; then
|
||||
echo ""
|
||||
echo "ERROR: Jest is not installed and no custom test commands are configured."
|
||||
echo ""
|
||||
echo " Munitor defaults to 'npx jest' but this project doesn't have Jest."
|
||||
echo " Add custom test commands to your .munitor.yml:"
|
||||
echo ""
|
||||
echo " test:"
|
||||
echo " commands:"
|
||||
echo " - \"npm test\""
|
||||
echo ""
|
||||
echo " Or install Jest: npm install --save-dev jest"
|
||||
echo ""
|
||||
exit 1
|
||||
PACKAGE_MANAGER="${MUNITOR_PACKAGE_MANAGER:-npm}"
|
||||
|
||||
if [[ "${PACKAGE_MANAGER}" == "pnpm" ]]; then
|
||||
echo "Running pnpm test..."
|
||||
pnpm test
|
||||
echo "pnpm test complete."
|
||||
else
|
||||
# Default: Jest with coverage and JUnit reporter
|
||||
if ! npx jest --version &>/dev/null; then
|
||||
echo ""
|
||||
echo "ERROR: Jest is not installed and no custom test commands are configured."
|
||||
echo ""
|
||||
echo " Munitor defaults to 'npx jest' but this project doesn't have Jest."
|
||||
echo " Add custom test commands to your .munitor.yml:"
|
||||
echo ""
|
||||
echo " test:"
|
||||
echo " commands:"
|
||||
echo " - \"npm test\""
|
||||
echo ""
|
||||
echo " Or install Jest: npm install --save-dev jest"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Running Jest tests..."
|
||||
|
||||
export JEST_JUNIT_OUTPUT_DIR="reports/junit"
|
||||
export JEST_JUNIT_OUTPUT_NAME="results.xml"
|
||||
|
||||
npx jest \
|
||||
--ci \
|
||||
--forceExit \
|
||||
--coverage \
|
||||
--coverageReporters=json-summary \
|
||||
--coverageReporters=lcov \
|
||||
--reporters=default \
|
||||
--reporters=jest-junit
|
||||
|
||||
echo "Jest tests complete."
|
||||
fi
|
||||
|
||||
echo "Running Jest tests..."
|
||||
|
||||
export JEST_JUNIT_OUTPUT_DIR="reports/junit"
|
||||
export JEST_JUNIT_OUTPUT_NAME="results.xml"
|
||||
|
||||
npx jest \
|
||||
--ci \
|
||||
--forceExit \
|
||||
--coverage \
|
||||
--coverageReporters=json-summary \
|
||||
--coverageReporters=lcov \
|
||||
--reporters=default \
|
||||
--reporters=jest-junit
|
||||
|
||||
echo "Jest tests complete."
|
||||
fi
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared helpers
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
munitor_header "npm_audit"
|
||||
|
||||
echo "Running npm audit..."
|
||||
|
||||
npm audit --audit-level=high || {
|
||||
EXIT_CODE=$?
|
||||
echo "npm audit found high/critical vulnerabilities."
|
||||
exit "${EXIT_CODE}"
|
||||
}
|
||||
|
||||
echo "npm audit passed."
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared helpers
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
MIN="${MIN_COVERAGE:-70}"
|
||||
COVERAGE_TOOL="${MUNITOR_COVERAGE_TOOL:-jest}"
|
||||
COVERAGE_COMMAND="${MUNITOR_COVERAGE_COMMAND:-}"
|
||||
SUMMARY_FILE="coverage/coverage-summary.json"
|
||||
|
||||
munitor_header "npm_coverage_check (min: ${MIN}%)"
|
||||
munitor_check_tool node --version
|
||||
|
||||
# Validate MIN_COVERAGE is numeric
|
||||
if ! [[ "${MIN}" =~ ^[0-9]+$ ]]; then
|
||||
echo "ERROR: MIN_COVERAGE must be a whole number, got '${MIN}'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Checking coverage threshold (minimum: ${MIN}%, tool: ${COVERAGE_TOOL})..."
|
||||
|
||||
# Run custom coverage report command if specified
|
||||
if [[ -n "${COVERAGE_COMMAND}" ]]; then
|
||||
echo "Running coverage report command: ${COVERAGE_COMMAND}"
|
||||
bash -c "${COVERAGE_COMMAND}"
|
||||
fi
|
||||
|
||||
if [[ ! -f "${SUMMARY_FILE}" ]]; then
|
||||
echo "ERROR: Coverage summary not found at ${SUMMARY_FILE}."
|
||||
echo "Ensure your test runner generates coverage/coverage-summary.json."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract total line coverage percentage from summary (same format for jest and nyc)
|
||||
ACTUAL=$(node -e "
|
||||
const s = require('./${SUMMARY_FILE}');
|
||||
console.log(s.total.lines.pct);
|
||||
")
|
||||
|
||||
echo "Line coverage: ${ACTUAL}%"
|
||||
|
||||
# Compare as integers (floor)
|
||||
ACTUAL_INT=${ACTUAL%.*}
|
||||
if [[ "${ACTUAL_INT}" -lt "${MIN}" ]]; then
|
||||
echo "FAIL: Coverage ${ACTUAL}% is below minimum ${MIN}%."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Coverage check passed (${ACTUAL}% >= ${MIN}%)."
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared helpers
|
||||
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
|
||||
# shellcheck source=munitor_helpers.sh
|
||||
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
|
||||
elif ! type munitor_header &>/dev/null; then
|
||||
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
|
||||
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
|
||||
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
|
||||
fi
|
||||
|
||||
munitor_header "npm_install"
|
||||
munitor_check_tool node --version
|
||||
munitor_check_tool npm --version
|
||||
|
||||
# Verify package-lock.json exists
|
||||
if [[ ! -f "package-lock.json" ]]; then
|
||||
echo "ERROR: package-lock.json not found in $(pwd)"
|
||||
echo " npm ci requires a lockfile. Run 'npm install' locally and commit the lockfile."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check lockfile version compatibility
|
||||
LOCKFILE_VERSION=$(node -e "console.log(require('./package-lock.json').lockfileVersion || 'unknown')" 2>/dev/null || echo "unknown")
|
||||
NPM_MAJOR=$(npm --version 2>/dev/null | cut -d. -f1)
|
||||
echo " lockfileVersion: ${LOCKFILE_VERSION}"
|
||||
|
||||
if [[ "${LOCKFILE_VERSION}" == "3" && -n "${NPM_MAJOR}" && "${NPM_MAJOR}" -lt 11 ]]; then
|
||||
echo ""
|
||||
echo "WARNING: package-lock.json uses lockfileVersion 3 (npm 11+)"
|
||||
echo " but CI is running npm ${NPM_MAJOR} ($(npm --version))."
|
||||
echo " This will likely cause 'npm ci' to fail."
|
||||
echo ""
|
||||
echo " Fix: regenerate your lockfile with the same Node version CI uses."
|
||||
echo " Add an .nvmrc to your repo to keep local and CI versions in sync."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Running npm ci..."
|
||||
npm ci
|
||||
|
||||
PKG_COUNT=$(find node_modules -maxdepth 1 -mindepth 1 -type d | wc -l | tr -d ' ')
|
||||
echo "npm ci complete. ${PKG_COUNT} packages installed."
|
||||
File diff suppressed because it is too large.
Load diff
@@ -135,6 +135,8 @@ extract_munitor_vars() {
|
||||
fi
|
||||
MUNITOR_CD_REPO=$(yq '.cd.repo // ""' "${config_file}")
|
||||
MUNITOR_CD_FORMAT=$(yq '.cd.format // "helm"' "${config_file}")
|
||||
MUNITOR_CD_PATH=$(yq '.cd.path // ""' "${config_file}")
|
||||
MUNITOR_CD_PRODUCTION_ONLY=$(yq '.cd.production_only // false' "${config_file}")
|
||||
MUNITOR_CD_ENV_DEVELOP=$(yq '.cd.env.develop // "develop"' "${config_file}")
|
||||
MUNITOR_CD_ENV_STAGING=$(yq '.cd.env.staging // "staging"' "${config_file}")
|
||||
MUNITOR_CD_ENV_PROD=$(yq '.cd.env.prod // "prod"' "${config_file}")
|
||||
@@ -193,6 +195,7 @@ extract_munitor_vars() {
|
||||
if [[ -n "${MUNITOR_TEST_COVERAGE_MIN}" ]]; then
|
||||
MUNITOR_COVERAGE_MIN="${MUNITOR_TEST_COVERAGE_MIN}"
|
||||
fi
|
||||
MUNITOR_COVERAGE_SUMMARY_PATH=$(yq '.test.coverage.summary_path // "coverage/coverage-summary.json"' "${config_file}")
|
||||
|
||||
# Terraform pipeline settings
|
||||
MUNITOR_TF_PATH=$(yq '.terraform.path // "terraform/"' "${config_file}")
|
||||
@@ -290,8 +293,22 @@ extract_munitor_vars() {
|
||||
|
||||
if [[ -n "${MUNITOR_CD_REPO}" ]]; then
|
||||
MUNITOR_CD="true"
|
||||
if [[ "${MUNITOR_CD_PRODUCTION_ONLY}" == "true" ]]; then
|
||||
MUNITOR_CD_NON_PROD="false"
|
||||
else
|
||||
MUNITOR_CD_NON_PROD="true"
|
||||
fi
|
||||
else
|
||||
MUNITOR_CD="false"
|
||||
MUNITOR_CD_NON_PROD="false"
|
||||
fi
|
||||
|
||||
# cd_path is only emitted when set, so manifests without cd.path still render
|
||||
# pipelines that validate against orb versions predating the parameter.
|
||||
if [[ -n "${MUNITOR_CD_PATH}" ]]; then
|
||||
MUNITOR_CD_EXPLICIT_PATH="true"
|
||||
else
|
||||
MUNITOR_CD_EXPLICIT_PATH="false"
|
||||
fi
|
||||
|
||||
if [[ -n "${MUNITOR_CONTEXT_NVD}" ]]; then
|
||||
@@ -302,7 +319,7 @@ extract_munitor_vars() {
|
||||
|
||||
# Export all variables
|
||||
export MUNITOR_PIPELINE MUNITOR_ORB_VERSION MUNITOR_IMAGE_NAME MUNITOR_JAVA_VERSION MUNITOR_NODE_VERSION MUNITOR_SERVER_MODULE
|
||||
export MUNITOR_SONAR_PROJECT_KEY MUNITOR_DOCKER_REGISTRY MUNITOR_CD_REPO MUNITOR_CD_FORMAT
|
||||
export MUNITOR_SONAR_PROJECT_KEY MUNITOR_DOCKER_REGISTRY MUNITOR_CD_REPO MUNITOR_CD_FORMAT MUNITOR_CD_PATH MUNITOR_CD_PRODUCTION_ONLY
|
||||
export MUNITOR_CD_ENV_DEVELOP MUNITOR_CD_ENV_STAGING MUNITOR_CD_ENV_PROD MUNITOR_CD_ENV_RELEASE
|
||||
export MUNITOR_COVERAGE_MIN MUNITOR_E2E MUNITOR_SBOM MUNITOR_OWASP
|
||||
export MUNITOR_CONTEXT_REGISTRY MUNITOR_CONTEXT_GITHUB MUNITOR_CONTEXT_SONAR MUNITOR_CONTEXT_NVD
|
||||
@@ -311,7 +328,7 @@ extract_munitor_vars() {
|
||||
export MUNITOR_SERVICES MUNITOR_SERVICES_JSON
|
||||
export MUNITOR_TEST_SETUP MUNITOR_TEST_SETUP_SCRIPT
|
||||
export MUNITOR_CUSTOM_TEST MUNITOR_TEST_COMMANDS_JSON
|
||||
export MUNITOR_COVERAGE_TOOL MUNITOR_COVERAGE_CMD MUNITOR_COVERAGE_COMMAND
|
||||
export MUNITOR_COVERAGE_TOOL MUNITOR_COVERAGE_CMD MUNITOR_COVERAGE_COMMAND MUNITOR_COVERAGE_SUMMARY_PATH
|
||||
export MUNITOR_TF_PATH MUNITOR_TF_LIVE_PATH MUNITOR_TF_ENVIRONMENTS MUNITOR_CHECKOV_SKIP MUNITOR_SAST MUNITOR_SAST_FAIL_ON_FINDINGS
|
||||
export MUNITOR_KUSTOMIZE_VERSION MUNITOR_KUSTOMIZE_BASE_PATH MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR
|
||||
export MUNITOR_KUSTOMIZE_SCAN_OVERLAY MUNITOR_KUSTOMIZE_OVERLAYS MUNITOR_KUSTOMIZE_OVERLAY_DIR
|
||||
@@ -320,13 +337,13 @@ extract_munitor_vars() {
|
||||
export MUNITOR_CI_EMAIL MUNITOR_CI_NAME MUNITOR_NPM_DEFAULT_SCOPE MUNITOR_ORB_SLUG
|
||||
export MUNITOR_HEALTH_PATH MUNITOR_HEALTH_PORT MUNITOR_HEALTH_DB MUNITOR_HEALTH_MIGRATIONS MUNITOR_HEALTH_SMOKE_PATHS
|
||||
export MUNITOR_SUPPLEMENTAL MUNITOR_SUPPLEMENTAL_IMAGES_JSON
|
||||
export MUNITOR_SONAR MUNITOR_CD MUNITOR_NVD
|
||||
export MUNITOR_SONAR MUNITOR_CD MUNITOR_CD_NON_PROD MUNITOR_NVD MUNITOR_CD_EXPLICIT_PATH
|
||||
}
|
||||
|
||||
# Build the envsubst variable list
|
||||
get_envsubst_vars() {
|
||||
# shellcheck disable=SC2016
|
||||
echo '${MUNITOR_PIPELINE} ${MUNITOR_ORB_VERSION} ${MUNITOR_IMAGE_NAME} ${MUNITOR_JAVA_VERSION} ${MUNITOR_NODE_VERSION} ${MUNITOR_SONAR_PROJECT_KEY} ${MUNITOR_DOCKER_REGISTRY} ${MUNITOR_CD_REPO} ${MUNITOR_CD_FORMAT} ${MUNITOR_CD_ENV_DEVELOP} ${MUNITOR_CD_ENV_STAGING} ${MUNITOR_CD_ENV_PROD} ${MUNITOR_CD_ENV_RELEASE} ${MUNITOR_COVERAGE_MIN} ${MUNITOR_E2E} ${MUNITOR_SBOM} ${MUNITOR_OWASP} ${MUNITOR_CONTEXT_REGISTRY} ${MUNITOR_CONTEXT_GITHUB} ${MUNITOR_CONTEXT_SONAR} ${MUNITOR_CONTEXT_NVD} ${MUNITOR_NPM_AUTH} ${MUNITOR_NPM_SCOPES} ${MUNITOR_SERVICES_JSON} ${MUNITOR_TEST_SETUP_SCRIPT} ${MUNITOR_TEST_COMMANDS_JSON} ${MUNITOR_COVERAGE_TOOL} ${MUNITOR_COVERAGE_COMMAND} ${MUNITOR_GITHUB_RELEASE} ${MUNITOR_SAST} ${MUNITOR_SAST_FAIL_ON_FINDINGS} ${MUNITOR_HEALTH_PATH} ${MUNITOR_HEALTH_PORT} ${MUNITOR_HEALTH_DB} ${MUNITOR_HEALTH_MIGRATIONS} ${MUNITOR_HEALTH_SMOKE_PATHS} ${MUNITOR_TF_PATH} ${MUNITOR_TF_LIVE_PATH} ${MUNITOR_TF_ENVIRONMENTS} ${MUNITOR_CHECKOV_SKIP} ${MUNITOR_KUSTOMIZE_VERSION} ${MUNITOR_KUSTOMIZE_BASE_PATH} ${MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR} ${MUNITOR_KUSTOMIZE_SCAN_OVERLAY} ${MUNITOR_KUSTOMIZE_OVERLAYS} ${MUNITOR_KUSTOMIZE_OVERLAY_DIR} ${MUNITOR_KUBE_LINTER_CONFIG} ${MUNITOR_YAMLLINT_PATHS} ${MUNITOR_CI_EMAIL} ${MUNITOR_CI_NAME} ${MUNITOR_NPM_DEFAULT_SCOPE} ${MUNITOR_ORB_SLUG} ${MUNITOR_PACKAGE_MANAGER} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON} ${MUNITOR_SERVER_MODULE}'
|
||||
echo '${MUNITOR_PIPELINE} ${MUNITOR_ORB_VERSION} ${MUNITOR_IMAGE_NAME} ${MUNITOR_JAVA_VERSION} ${MUNITOR_NODE_VERSION} ${MUNITOR_SONAR_PROJECT_KEY} ${MUNITOR_DOCKER_REGISTRY} ${MUNITOR_CD_REPO} ${MUNITOR_CD_FORMAT} ${MUNITOR_CD_PATH} ${MUNITOR_CD_ENV_DEVELOP} ${MUNITOR_CD_ENV_STAGING} ${MUNITOR_CD_ENV_PROD} ${MUNITOR_CD_ENV_RELEASE} ${MUNITOR_COVERAGE_MIN} ${MUNITOR_E2E} ${MUNITOR_SBOM} ${MUNITOR_OWASP} ${MUNITOR_CONTEXT_REGISTRY} ${MUNITOR_CONTEXT_GITHUB} ${MUNITOR_CONTEXT_SONAR} ${MUNITOR_CONTEXT_NVD} ${MUNITOR_NPM_AUTH} ${MUNITOR_NPM_SCOPES} ${MUNITOR_SERVICES_JSON} ${MUNITOR_TEST_SETUP_SCRIPT} ${MUNITOR_TEST_COMMANDS_JSON} ${MUNITOR_COVERAGE_TOOL} ${MUNITOR_COVERAGE_COMMAND} ${MUNITOR_GITHUB_RELEASE} ${MUNITOR_SAST} ${MUNITOR_SAST_FAIL_ON_FINDINGS} ${MUNITOR_HEALTH_PATH} ${MUNITOR_HEALTH_PORT} ${MUNITOR_HEALTH_DB} ${MUNITOR_HEALTH_MIGRATIONS} ${MUNITOR_HEALTH_SMOKE_PATHS} ${MUNITOR_TF_PATH} ${MUNITOR_TF_LIVE_PATH} ${MUNITOR_TF_ENVIRONMENTS} ${MUNITOR_CHECKOV_SKIP} ${MUNITOR_KUSTOMIZE_VERSION} ${MUNITOR_KUSTOMIZE_BASE_PATH} ${MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR} ${MUNITOR_KUSTOMIZE_SCAN_OVERLAY} ${MUNITOR_KUSTOMIZE_OVERLAYS} ${MUNITOR_KUSTOMIZE_OVERLAY_DIR} ${MUNITOR_KUBE_LINTER_CONFIG} ${MUNITOR_YAMLLINT_PATHS} ${MUNITOR_CI_EMAIL} ${MUNITOR_CI_NAME} ${MUNITOR_NPM_DEFAULT_SCOPE} ${MUNITOR_ORB_SLUG} ${MUNITOR_PACKAGE_MANAGER} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON} ${MUNITOR_SERVER_MODULE} ${MUNITOR_COVERAGE_SUMMARY_PATH}'
|
||||
}
|
||||
MUNITOR_EXTRACT_EOF
|
||||
|
||||
@@ -377,7 +394,42 @@ case "${MUNITOR_PIPELINE}" in
|
||||
nextjs)
|
||||
# Multi-stage Next.js standalone Dockerfile.
|
||||
# Assumes `output: 'standalone'` in next.config.js.
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
case "${MUNITOR_PACKAGE_MANAGER}" in
|
||||
pnpm)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
ENV STANDALONE=true
|
||||
ARG GIT_COMMIT_SHA
|
||||
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
|
||||
RUN pnpm run build
|
||||
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
|
||||
WORKDIR /app
|
||||
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
|
||||
RUN npm cache clean --force && rm -rf /usr/local/lib/node_modules /usr/local/bin/npm /usr/local/bin/npx
|
||||
RUN addgroup --system --gid 1001 nodejs \\
|
||||
&& adduser --system --uid 1001 nextjs
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
CMD ["node", "server.js"]
|
||||
DOCKERFILE
|
||||
;;
|
||||
*)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
@@ -408,11 +460,38 @@ EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
CMD ["node", "server.js"]
|
||||
DOCKERFILE
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
express)
|
||||
# Two-stage Express Dockerfile. Uses package.json "main" field via `node .`.
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
case "${MUNITOR_PACKAGE_MANAGER}" in
|
||||
pnpm)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --prod
|
||||
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
|
||||
WORKDIR /app
|
||||
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
|
||||
RUN npm cache clean --force && rm -rf /usr/local/lib/node_modules /usr/local/bin/npm /usr/local/bin/npx
|
||||
RUN addgroup --system --gid 1001 nodejs \\
|
||||
&& adduser --system --uid 1001 appuser -G nodejs
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
USER appuser
|
||||
EXPOSE ${MUNITOR_HEALTH_PORT}
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:${MUNITOR_HEALTH_PORT}${MUNITOR_HEALTH_PATH} || exit 1
|
||||
CMD ["node", "."]
|
||||
DOCKERFILE
|
||||
;;
|
||||
*)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
@@ -432,6 +511,8 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:${MUNITOR_HEALTH_PORT}${MUNITOR_HEALTH_PATH} || exit 1
|
||||
CMD ["node", "."]
|
||||
DOCKERFILE
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
*)
|
||||
@@ -532,8 +613,42 @@ DOCKERFILE
|
||||
;;
|
||||
|
||||
node-webapp)
|
||||
echo "Generating Dockerfile for node-webapp (node ${MUNITOR_NODE_VERSION})"
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
if yq -e '.docker.use_repo_dockerfile == true' "${CONFIG_FILE}" &>/dev/null; then
|
||||
echo "Using repository Dockerfile for node-webapp"
|
||||
if [[ ! -f Dockerfile ]]; then
|
||||
echo "ERROR: docker.use_repo_dockerfile requires a Dockerfile in the repository root." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Generating Dockerfile for node-webapp (node ${MUNITOR_NODE_VERSION})"
|
||||
# Single source for the runtime base. debian12 ships libssl3 3.0.18 with
|
||||
# open HIGH/CRITICAL CVEs that fail the Trivy gate; debian13 scans clean.
|
||||
NODE_WEBAPP_RUNTIME_IMAGE="gcr.io/distroless/nodejs${MUNITOR_NODE_VERSION}-debian13"
|
||||
case "${MUNITOR_PACKAGE_MANAGER}" in
|
||||
pnpm)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY . .
|
||||
ARG GIT_COMMIT_SHA
|
||||
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
|
||||
RUN pnpm run build
|
||||
|
||||
FROM ${NODE_WEBAPP_RUNTIME_IMAGE} AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/public ./public
|
||||
EXPOSE ${MUNITOR_HEALTH_PORT}
|
||||
CMD ["server.js"]
|
||||
DOCKERFILE
|
||||
;;
|
||||
*)
|
||||
cat > Dockerfile <<DOCKERFILE
|
||||
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
@@ -543,7 +658,7 @@ ARG GIT_COMMIT_SHA
|
||||
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
|
||||
RUN npm run build
|
||||
|
||||
FROM gcr.io/distroless/nodejs${MUNITOR_NODE_VERSION}-debian12 AS runner
|
||||
FROM ${NODE_WEBAPP_RUNTIME_IMAGE} AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
@@ -552,6 +667,9 @@ COPY --from=builder /app/public ./public
|
||||
EXPOSE ${MUNITOR_HEALTH_PORT}
|
||||
CMD ["server.js"]
|
||||
DOCKERFILE
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
|
||||
+34
-11
@@ -15,22 +15,45 @@ munitor_header "run_semgrep"
|
||||
|
||||
CONFIG="${SEMGREP_CONFIG:-auto}"
|
||||
FAIL_ON_FINDINGS="${SEMGREP_FAIL_ON_FINDINGS:-true}"
|
||||
REPORT="${SEMGREP_REPORT:-/tmp/semgrep-results.json}"
|
||||
|
||||
echo "Running Semgrep SAST scan (config: ${CONFIG}, fail_on_findings: ${FAIL_ON_FINDINGS})..."
|
||||
|
||||
EXIT_CODE=0
|
||||
semgrep ci \
|
||||
--config "${CONFIG}" \
|
||||
--json \
|
||||
--output /tmp/semgrep-results.json || {
|
||||
EXIT_CODE=$?
|
||||
if [[ "${FAIL_ON_FINDINGS}" == "true" ]]; then
|
||||
echo "ERROR: Semgrep found issues."
|
||||
echo "Review the report at: /tmp/semgrep-results.json"
|
||||
exit "${EXIT_CODE}"
|
||||
else
|
||||
echo "WARNING: Semgrep found issues (warn-only mode, not failing build)."
|
||||
echo "Review the report at: /tmp/semgrep-results.json"
|
||||
fi
|
||||
}
|
||||
--output "${REPORT}" || EXIT_CODE=$?
|
||||
|
||||
# Summarise the report, and fail a scan that ran no rules or scanned no files:
|
||||
# semgrep exits 0 when the downloaded rules fail to parse (an outdated semgrep),
|
||||
# which used to pass this job without scanning anything.
|
||||
python3 - "${REPORT}" <<'PY'
|
||||
import json, sys
|
||||
report = json.load(open(sys.argv[1]))
|
||||
scanned = len(report.get("paths", {}).get("scanned", []))
|
||||
results = report.get("results", [])
|
||||
errors = report.get("errors", [])
|
||||
rule_errors = [e for e in errors if "rule" in str(e.get("type", "")).lower()
|
||||
or "config" in str(e.get("message", "")).lower()]
|
||||
print(f"Semgrep: {scanned} files scanned, {len(results)} findings, {len(errors)} errors.")
|
||||
for r in results[:50]:
|
||||
print(f" {r.get('check_id')} {r.get('path')}:{r.get('start', {}).get('line')}")
|
||||
if scanned == 0 or rule_errors:
|
||||
for e in rule_errors[:5]:
|
||||
print(f" error: {str(e.get('message', e))[:300]}")
|
||||
print("ERROR: Semgrep did not run its rules (no files scanned or invalid rule configuration).")
|
||||
sys.exit(2)
|
||||
PY
|
||||
|
||||
if [[ "${EXIT_CODE}" -ne 0 ]]; then
|
||||
if [[ "${FAIL_ON_FINDINGS}" == "true" ]]; then
|
||||
echo "ERROR: Semgrep found issues."
|
||||
echo "Review the report at: ${REPORT}"
|
||||
exit "${EXIT_CODE}"
|
||||
fi
|
||||
echo "WARNING: Semgrep found issues (warn-only mode, not failing build)."
|
||||
echo "Review the report at: ${REPORT}"
|
||||
fi
|
||||
|
||||
echo "Semgrep scan completed."
|
||||
@@ -16,16 +16,17 @@ munitor_header "run_test_setup"
|
||||
SETUP_SCRIPT="${MUNITOR_TEST_SETUP_SCRIPT:-}"
|
||||
|
||||
if [[ -z "${SETUP_SCRIPT}" ]]; then
|
||||
echo "No test setup script configured."
|
||||
echo "No test setup configured."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "${SETUP_SCRIPT}" ]]; then
|
||||
echo "ERROR: Test setup script '${SETUP_SCRIPT}' not found."
|
||||
exit 1
|
||||
if [[ -f "${SETUP_SCRIPT}" ]]; then
|
||||
echo "Running test setup script: ${SETUP_SCRIPT}"
|
||||
chmod +x "${SETUP_SCRIPT}"
|
||||
"${SETUP_SCRIPT}"
|
||||
else
|
||||
echo "Running test setup command: ${SETUP_SCRIPT}"
|
||||
bash -c "${SETUP_SCRIPT}"
|
||||
fi
|
||||
|
||||
echo "Running test setup: ${SETUP_SCRIPT}"
|
||||
chmod +x "${SETUP_SCRIPT}"
|
||||
"${SETUP_SCRIPT}"
|
||||
echo "Test setup complete."
|
||||
@@ -36,13 +36,17 @@ fi
|
||||
|
||||
echo "Running Trivy filesystem scan..."
|
||||
|
||||
# --exit-code 1: without it trivy exits 0 even when it reports HIGH/CRITICAL
|
||||
# findings, so this job could never fail.
|
||||
trivy fs \
|
||||
--format json \
|
||||
--output /tmp/trivy-results.json \
|
||||
--severity HIGH,CRITICAL \
|
||||
--exit-code 1 \
|
||||
. || {
|
||||
EXIT_CODE=$?
|
||||
echo "Trivy found vulnerabilities. See /tmp/trivy-results.json"
|
||||
trivy fs --severity HIGH,CRITICAL . || true
|
||||
exit "${EXIT_CODE}"
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -223,13 +237,16 @@ workflows:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_SBOM##
|
||||
##IF_CD##
|
||||
##IF_CD_NON_PROD##
|
||||
- munitor/update_cd_repo:
|
||||
name: update-cd-repo
|
||||
cd_repo: ${MUNITOR_CD_REPO}
|
||||
environment: ${MUNITOR_CD_ENV_RELEASE}
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
@@ -243,7 +260,7 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_CD##
|
||||
##ENDIF_CD_NON_PROD##
|
||||
##IF_GITHUB_RELEASE##
|
||||
- munitor/github_release:
|
||||
name: github-release
|
||||
@@ -293,6 +310,9 @@ workflows:
|
||||
environment: ${MUNITOR_CD_ENV_PROD}
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
|
||||
@@ -8,6 +8,8 @@ workflows:
|
||||
jobs:
|
||||
- munitor/mvn_build_and_test:
|
||||
name: build-and-test
|
||||
quality_checks: true
|
||||
min_instruction: "${MUNITOR_COVERAGE_MIN}"
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
@@ -16,6 +18,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:
|
||||
@@ -31,27 +47,6 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/mvn_code_quality:
|
||||
name: code-quality
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/mvn_coverage:
|
||||
name: coverage
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
min_instruction: "${MUNITOR_COVERAGE_MIN}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/security_scan:
|
||||
name: security-scan
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
@@ -89,6 +84,8 @@ workflows:
|
||||
jobs:
|
||||
- munitor/mvn_build_and_test:
|
||||
name: build-and-test
|
||||
quality_checks: true
|
||||
min_instruction: "${MUNITOR_COVERAGE_MIN}"
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
@@ -109,25 +106,6 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/mvn_code_quality:
|
||||
name: code-quality
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/mvn_coverage:
|
||||
name: coverage
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
min_instruction: "${MUNITOR_COVERAGE_MIN}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/security_scan:
|
||||
name: security-scan
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
@@ -162,7 +140,6 @@ workflows:
|
||||
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
|
||||
requires:
|
||||
- build-and-test
|
||||
- coverage
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_SONAR}
|
||||
filters:
|
||||
@@ -183,8 +160,7 @@ workflows:
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- code-quality
|
||||
- coverage
|
||||
- build-and-test
|
||||
- secrets-scan
|
||||
- sast-scan
|
||||
- security-scan
|
||||
@@ -207,8 +183,7 @@ workflows:
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
requires:
|
||||
- code-quality
|
||||
- coverage
|
||||
- build-and-test
|
||||
- secrets-scan
|
||||
- sast-scan
|
||||
- security-scan
|
||||
@@ -223,13 +198,16 @@ workflows:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_SBOM##
|
||||
##IF_CD##
|
||||
##IF_CD_NON_PROD##
|
||||
- munitor/update_cd_repo:
|
||||
name: update-cd-repo
|
||||
cd_repo: ${MUNITOR_CD_REPO}
|
||||
environment: ${MUNITOR_CD_ENV_RELEASE}
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
@@ -243,7 +221,7 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_CD##
|
||||
##ENDIF_CD_NON_PROD##
|
||||
##IF_GITHUB_RELEASE##
|
||||
- munitor/github_release:
|
||||
name: github-release
|
||||
@@ -260,7 +238,7 @@ workflows:
|
||||
production:
|
||||
jobs:
|
||||
- munitor/mvn_build_and_test:
|
||||
name: build-and-test
|
||||
name: production-build-and-test
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
@@ -280,7 +258,7 @@ workflows:
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- build-and-test
|
||||
- production-build-and-test
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_REGISTRY}
|
||||
filters:
|
||||
@@ -293,6 +271,9 @@ workflows:
|
||||
environment: ${MUNITOR_CD_ENV_PROD}
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
|
||||
@@ -6,9 +6,10 @@ orbs:
|
||||
workflows:
|
||||
pr-checks:
|
||||
jobs:
|
||||
- munitor/npm_build_and_test:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
@@ -30,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:
|
||||
@@ -45,8 +60,10 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/npm_code_quality:
|
||||
- munitor/node_code_quality:
|
||||
name: code-quality
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -54,10 +71,13 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/npm_coverage:
|
||||
- munitor/node_coverage:
|
||||
name: coverage
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
min_coverage: "${MUNITOR_COVERAGE_MIN}"
|
||||
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
|
||||
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
|
||||
##IF_COVERAGE_CMD##
|
||||
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
|
||||
##ENDIF_COVERAGE_CMD##
|
||||
@@ -68,8 +88,10 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/npm_security_scan:
|
||||
- munitor/node_security_scan:
|
||||
name: security-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -78,8 +100,10 @@ workflows:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
##IF_E2E##
|
||||
- munitor/npm_e2e_test:
|
||||
- munitor/node_e2e_test:
|
||||
name: e2e-tests
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -94,9 +118,10 @@ workflows:
|
||||
|
||||
release-candidate:
|
||||
jobs:
|
||||
- munitor/npm_build_and_test:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
@@ -130,18 +155,23 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/npm_code_quality:
|
||||
- munitor/node_code_quality:
|
||||
name: code-quality
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/npm_coverage:
|
||||
- munitor/node_coverage:
|
||||
name: coverage
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
min_coverage: "${MUNITOR_COVERAGE_MIN}"
|
||||
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
|
||||
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
|
||||
##IF_COVERAGE_CMD##
|
||||
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
|
||||
##ENDIF_COVERAGE_CMD##
|
||||
@@ -151,8 +181,10 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/npm_security_scan:
|
||||
- munitor/node_security_scan:
|
||||
name: security-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -160,8 +192,10 @@ workflows:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##IF_E2E##
|
||||
- munitor/npm_e2e_test:
|
||||
- munitor/node_e2e_test:
|
||||
name: e2e-tests
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -170,9 +204,10 @@ workflows:
|
||||
- /release\/.*/
|
||||
##ENDIF_E2E##
|
||||
##IF_SONAR##
|
||||
- munitor/npm_sonar_scan:
|
||||
- munitor/node_sonar_scan:
|
||||
name: sonar-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
|
||||
requires:
|
||||
- build-and-test
|
||||
@@ -210,9 +245,10 @@ workflows:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##IF_SBOM##
|
||||
- munitor/npm_sbom:
|
||||
- munitor/node_sbom:
|
||||
name: sbom
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
requires:
|
||||
@@ -229,13 +265,16 @@ workflows:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_SBOM##
|
||||
##IF_CD##
|
||||
##IF_CD_NON_PROD##
|
||||
- munitor/update_cd_repo:
|
||||
name: update-cd-repo
|
||||
cd_repo: ${MUNITOR_CD_REPO}
|
||||
environment: ${MUNITOR_CD_ENV_RELEASE}
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
@@ -249,7 +288,7 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_CD##
|
||||
##ENDIF_CD_NON_PROD##
|
||||
##IF_GITHUB_RELEASE##
|
||||
- munitor/github_release:
|
||||
name: github-release
|
||||
@@ -265,9 +304,10 @@ workflows:
|
||||
|
||||
production:
|
||||
jobs:
|
||||
- munitor/npm_build_and_test:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
@@ -311,6 +351,9 @@ workflows:
|
||||
environment: ${MUNITOR_CD_ENV_PROD}
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
|
||||
@@ -6,9 +6,10 @@ orbs:
|
||||
workflows:
|
||||
pr-checks:
|
||||
jobs:
|
||||
- munitor/npm_build_and_test:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
@@ -30,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:
|
||||
@@ -45,8 +60,10 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/npm_code_quality:
|
||||
- munitor/node_code_quality:
|
||||
name: code-quality
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -54,10 +71,13 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/npm_coverage:
|
||||
- munitor/node_coverage:
|
||||
name: coverage
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
min_coverage: "${MUNITOR_COVERAGE_MIN}"
|
||||
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
|
||||
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
|
||||
##IF_COVERAGE_CMD##
|
||||
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
|
||||
##ENDIF_COVERAGE_CMD##
|
||||
@@ -68,8 +88,10 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/npm_security_scan:
|
||||
- munitor/node_security_scan:
|
||||
name: security-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -78,8 +100,10 @@ workflows:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
##IF_E2E##
|
||||
- munitor/npm_e2e_test:
|
||||
- munitor/node_e2e_test:
|
||||
name: e2e-tests
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -94,9 +118,10 @@ workflows:
|
||||
|
||||
release-candidate:
|
||||
jobs:
|
||||
- munitor/npm_build_and_test:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
@@ -130,18 +155,23 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/npm_code_quality:
|
||||
- munitor/node_code_quality:
|
||||
name: code-quality
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/npm_coverage:
|
||||
- munitor/node_coverage:
|
||||
name: coverage
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
min_coverage: "${MUNITOR_COVERAGE_MIN}"
|
||||
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
|
||||
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
|
||||
##IF_COVERAGE_CMD##
|
||||
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
|
||||
##ENDIF_COVERAGE_CMD##
|
||||
@@ -151,8 +181,10 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/npm_security_scan:
|
||||
- munitor/node_security_scan:
|
||||
name: security-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -160,8 +192,10 @@ workflows:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##IF_E2E##
|
||||
- munitor/npm_e2e_test:
|
||||
- munitor/node_e2e_test:
|
||||
name: e2e-tests
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -170,9 +204,10 @@ workflows:
|
||||
- /release\/.*/
|
||||
##ENDIF_E2E##
|
||||
##IF_SONAR##
|
||||
- munitor/npm_sonar_scan:
|
||||
- munitor/node_sonar_scan:
|
||||
name: sonar-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
|
||||
requires:
|
||||
- build-and-test
|
||||
@@ -210,9 +245,10 @@ workflows:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##IF_SBOM##
|
||||
- munitor/npm_sbom:
|
||||
- munitor/node_sbom:
|
||||
name: sbom
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
requires:
|
||||
@@ -229,13 +265,16 @@ workflows:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_SBOM##
|
||||
##IF_CD##
|
||||
##IF_CD_NON_PROD##
|
||||
- munitor/update_cd_repo:
|
||||
name: update-cd-repo
|
||||
cd_repo: ${MUNITOR_CD_REPO}
|
||||
environment: ${MUNITOR_CD_ENV_RELEASE}
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
@@ -249,7 +288,7 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_CD##
|
||||
##ENDIF_CD_NON_PROD##
|
||||
##IF_GITHUB_RELEASE##
|
||||
- munitor/github_release:
|
||||
name: github-release
|
||||
@@ -265,9 +304,10 @@ workflows:
|
||||
|
||||
production:
|
||||
jobs:
|
||||
- munitor/npm_build_and_test:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
@@ -311,6 +351,9 @@ workflows:
|
||||
environment: ${MUNITOR_CD_ENV_PROD}
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
munitor: ${MUNITOR_ORB_SLUG}@${MUNITOR_ORB_VERSION}
|
||||
|
||||
workflows:
|
||||
pr-checks:
|
||||
jobs:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
npm_default_scope: '${MUNITOR_NPM_DEFAULT_SCOPE}'
|
||||
##ENDIF_NPM_AUTH##
|
||||
##IF_SERVICES##
|
||||
services: '${MUNITOR_SERVICES_JSON}'
|
||||
##ENDIF_SERVICES##
|
||||
##IF_TEST_SETUP##
|
||||
test_setup: ${MUNITOR_TEST_SETUP_SCRIPT}
|
||||
##ENDIF_TEST_SETUP##
|
||||
##IF_CUSTOM_TEST##
|
||||
test_commands: '${MUNITOR_TEST_COMMANDS_JSON}'
|
||||
##ENDIF_CUSTOM_TEST##
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
##IF_SAST##
|
||||
- munitor/sast_scan:
|
||||
name: sast-scan
|
||||
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
##ENDIF_SAST##
|
||||
- munitor/node_code_quality:
|
||||
name: code-quality
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/node_coverage:
|
||||
name: coverage
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
min_coverage: "${MUNITOR_COVERAGE_MIN}"
|
||||
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
|
||||
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
|
||||
##IF_COVERAGE_CMD##
|
||||
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
|
||||
##ENDIF_COVERAGE_CMD##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/node_security_scan:
|
||||
name: security-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
##IF_E2E##
|
||||
- munitor/node_e2e_test:
|
||||
name: e2e-tests
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
##ENDIF_E2E##
|
||||
|
||||
develop:
|
||||
jobs:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
npm_default_scope: '${MUNITOR_NPM_DEFAULT_SCOPE}'
|
||||
##ENDIF_NPM_AUTH##
|
||||
##IF_SERVICES##
|
||||
services: '${MUNITOR_SERVICES_JSON}'
|
||||
##ENDIF_SERVICES##
|
||||
##IF_TEST_SETUP##
|
||||
test_setup: ${MUNITOR_TEST_SETUP_SCRIPT}
|
||||
##ENDIF_TEST_SETUP##
|
||||
##IF_CUSTOM_TEST##
|
||||
test_commands: '${MUNITOR_TEST_COMMANDS_JSON}'
|
||||
##ENDIF_CUSTOM_TEST##
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
filters:
|
||||
branches:
|
||||
only: develop
|
||||
|
||||
release-candidate:
|
||||
jobs:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
npm_default_scope: '${MUNITOR_NPM_DEFAULT_SCOPE}'
|
||||
##ENDIF_NPM_AUTH##
|
||||
##IF_SERVICES##
|
||||
services: '${MUNITOR_SERVICES_JSON}'
|
||||
##ENDIF_SERVICES##
|
||||
##IF_TEST_SETUP##
|
||||
test_setup: ${MUNITOR_TEST_SETUP_SCRIPT}
|
||||
##ENDIF_TEST_SETUP##
|
||||
##IF_CUSTOM_TEST##
|
||||
test_commands: '${MUNITOR_TEST_COMMANDS_JSON}'
|
||||
##ENDIF_CUSTOM_TEST##
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##IF_SAST##
|
||||
- munitor/sast_scan:
|
||||
name: sast-scan
|
||||
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_SAST##
|
||||
- munitor/node_code_quality:
|
||||
name: code-quality
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/node_coverage:
|
||||
name: coverage
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
min_coverage: "${MUNITOR_COVERAGE_MIN}"
|
||||
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
|
||||
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
|
||||
##IF_COVERAGE_CMD##
|
||||
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
|
||||
##ENDIF_COVERAGE_CMD##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
- munitor/node_security_scan:
|
||||
name: security-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##IF_E2E##
|
||||
- munitor/node_e2e_test:
|
||||
name: e2e-tests
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_E2E##
|
||||
##IF_SONAR##
|
||||
- munitor/node_sonar_scan:
|
||||
name: sonar-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
|
||||
requires:
|
||||
- build-and-test
|
||||
- coverage
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_SONAR}
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_SONAR##
|
||||
##IF_SBOM##
|
||||
- munitor/node_sbom:
|
||||
name: sbom
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
requires:
|
||||
- code-quality
|
||||
- coverage
|
||||
- secrets-scan
|
||||
##IF_SAST##
|
||||
- sast-scan
|
||||
##ENDIF_SAST##
|
||||
- security-scan
|
||||
##IF_E2E##
|
||||
- e2e-tests
|
||||
##ENDIF_E2E##
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
##ENDIF_SBOM##
|
||||
- munitor/github_release:
|
||||
name: github-release
|
||||
release_artifacts: "main.js manifest.json styles.css"
|
||||
requires:
|
||||
- code-quality
|
||||
- coverage
|
||||
- secrets-scan
|
||||
##IF_SAST##
|
||||
- sast-scan
|
||||
##ENDIF_SAST##
|
||||
- security-scan
|
||||
##IF_E2E##
|
||||
- e2e-tests
|
||||
##ENDIF_E2E##
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
|
||||
production:
|
||||
jobs:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
npm_default_scope: '${MUNITOR_NPM_DEFAULT_SCOPE}'
|
||||
##ENDIF_NPM_AUTH##
|
||||
##IF_SERVICES##
|
||||
services: '${MUNITOR_SERVICES_JSON}'
|
||||
##ENDIF_SERVICES##
|
||||
##IF_TEST_SETUP##
|
||||
test_setup: ${MUNITOR_TEST_SETUP_SCRIPT}
|
||||
##ENDIF_TEST_SETUP##
|
||||
##IF_CUSTOM_TEST##
|
||||
test_commands: '${MUNITOR_TEST_COMMANDS_JSON}'
|
||||
##ENDIF_CUSTOM_TEST##
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
filters:
|
||||
branches:
|
||||
only: main
|
||||
- munitor/github_release:
|
||||
name: github-release
|
||||
release_artifacts: "main.js manifest.json styles.css"
|
||||
requires:
|
||||
- build-and-test
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
filters:
|
||||
branches:
|
||||
only: main
|
||||
@@ -124,13 +124,16 @@
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_SBOM##
|
||||
##IF_CD##
|
||||
##IF_CD_NON_PROD##
|
||||
- munitor/update_cd_repo:
|
||||
name: update-cd-repo
|
||||
cd_repo: ${MUNITOR_CD_REPO}
|
||||
environment: __CD_ENVIRONMENT__
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
@@ -143,4 +146,4 @@
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_CD##
|
||||
##ENDIF_CD_NON_PROD##
|
||||
@@ -2,6 +2,8 @@
|
||||
jobs:
|
||||
- munitor/mvn_build_and_test:
|
||||
name: build-and-test
|
||||
quality_checks: true
|
||||
min_instruction: "${MUNITOR_COVERAGE_MIN}"
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
@@ -19,23 +21,6 @@
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
- munitor/mvn_code_quality:
|
||||
name: code-quality
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
- munitor/mvn_coverage:
|
||||
name: coverage
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
min_instruction: "${MUNITOR_COVERAGE_MIN}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
- munitor/security_scan:
|
||||
name: security-scan
|
||||
java_version: "${MUNITOR_JAVA_VERSION}"
|
||||
@@ -68,7 +53,6 @@
|
||||
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
|
||||
requires:
|
||||
- build-and-test
|
||||
- coverage
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_SONAR}
|
||||
filters:
|
||||
@@ -86,8 +70,7 @@
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- code-quality
|
||||
- coverage
|
||||
- build-and-test
|
||||
- secrets-scan
|
||||
- sast-scan
|
||||
- security-scan
|
||||
@@ -109,8 +92,7 @@
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
requires:
|
||||
- code-quality
|
||||
- coverage
|
||||
- build-and-test
|
||||
- secrets-scan
|
||||
- sast-scan
|
||||
- security-scan
|
||||
@@ -124,13 +106,16 @@
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_SBOM##
|
||||
##IF_CD##
|
||||
##IF_CD_NON_PROD##
|
||||
- munitor/update_cd_repo:
|
||||
name: update-cd-repo
|
||||
cd_repo: ${MUNITOR_CD_REPO}
|
||||
environment: __CD_ENVIRONMENT__
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
@@ -143,4 +128,4 @@
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_CD##
|
||||
##ENDIF_CD_NON_PROD##
|
||||
@@ -1,8 +1,9 @@
|
||||
__WORKFLOW_NAME__:
|
||||
jobs:
|
||||
- munitor/npm_build_and_test:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
@@ -33,17 +34,22 @@
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
- munitor/npm_code_quality:
|
||||
- munitor/node_code_quality:
|
||||
name: code-quality
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
- munitor/npm_coverage:
|
||||
- munitor/node_coverage:
|
||||
name: coverage
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
min_coverage: "${MUNITOR_COVERAGE_MIN}"
|
||||
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
|
||||
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
|
||||
##IF_COVERAGE_CMD##
|
||||
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
|
||||
##ENDIF_COVERAGE_CMD##
|
||||
@@ -52,16 +58,20 @@
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
- munitor/npm_security_scan:
|
||||
- munitor/node_security_scan:
|
||||
name: security-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##IF_E2E##
|
||||
- munitor/npm_e2e_test:
|
||||
- munitor/node_e2e_test:
|
||||
name: e2e-tests
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -69,9 +79,10 @@
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_E2E##
|
||||
##IF_SONAR##
|
||||
- munitor/npm_sonar_scan:
|
||||
- munitor/node_sonar_scan:
|
||||
name: sonar-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
|
||||
requires:
|
||||
- build-and-test
|
||||
@@ -107,9 +118,10 @@
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##IF_SBOM##
|
||||
- munitor/npm_sbom:
|
||||
- munitor/node_sbom:
|
||||
name: sbom
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
requires:
|
||||
@@ -125,13 +137,16 @@
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_SBOM##
|
||||
##IF_CD##
|
||||
##IF_CD_NON_PROD##
|
||||
- munitor/update_cd_repo:
|
||||
name: update-cd-repo
|
||||
cd_repo: ${MUNITOR_CD_REPO}
|
||||
environment: __CD_ENVIRONMENT__
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
@@ -144,4 +159,4 @@
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_CD##
|
||||
##ENDIF_CD_NON_PROD##
|
||||
@@ -1,8 +1,9 @@
|
||||
__WORKFLOW_NAME__:
|
||||
jobs:
|
||||
- munitor/npm_build_and_test:
|
||||
- munitor/node_build_and_test:
|
||||
name: build-and-test
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
##IF_NPM_AUTH##
|
||||
npm_auth: true
|
||||
npm_scopes: '${MUNITOR_NPM_SCOPES}'
|
||||
@@ -33,17 +34,22 @@
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
- munitor/npm_code_quality:
|
||||
- munitor/node_code_quality:
|
||||
name: code-quality
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
- munitor/npm_coverage:
|
||||
- munitor/node_coverage:
|
||||
name: coverage
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
min_coverage: "${MUNITOR_COVERAGE_MIN}"
|
||||
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
|
||||
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
|
||||
##IF_COVERAGE_CMD##
|
||||
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
|
||||
##ENDIF_COVERAGE_CMD##
|
||||
@@ -52,16 +58,20 @@
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
- munitor/npm_security_scan:
|
||||
- munitor/node_security_scan:
|
||||
name: security-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##IF_E2E##
|
||||
- munitor/npm_e2e_test:
|
||||
- munitor/node_e2e_test:
|
||||
name: e2e-tests
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
@@ -69,9 +79,10 @@
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_E2E##
|
||||
##IF_SONAR##
|
||||
- munitor/npm_sonar_scan:
|
||||
- munitor/node_sonar_scan:
|
||||
name: sonar-scan
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
|
||||
requires:
|
||||
- build-and-test
|
||||
@@ -107,9 +118,10 @@
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##IF_SBOM##
|
||||
- munitor/npm_sbom:
|
||||
- munitor/node_sbom:
|
||||
name: sbom
|
||||
node_version: "${MUNITOR_NODE_VERSION}"
|
||||
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
|
||||
context:
|
||||
- ${MUNITOR_CONTEXT_GITHUB}
|
||||
requires:
|
||||
@@ -125,13 +137,16 @@
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_SBOM##
|
||||
##IF_CD##
|
||||
##IF_CD_NON_PROD##
|
||||
- munitor/update_cd_repo:
|
||||
name: update-cd-repo
|
||||
cd_repo: ${MUNITOR_CD_REPO}
|
||||
environment: __CD_ENVIRONMENT__
|
||||
cd_format: ${MUNITOR_CD_FORMAT}
|
||||
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
|
||||
##IF_CD_EXPLICIT_PATH##
|
||||
cd_path: ${MUNITOR_CD_PATH}
|
||||
##ENDIF_CD_EXPLICIT_PATH##
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
@@ -144,4 +159,4 @@
|
||||
filters:
|
||||
branches:
|
||||
only: __BRANCH_FILTER__
|
||||
##ENDIF_CD##
|
||||
##ENDIF_CD_NON_PROD##
|
||||
+172
-89
@@ -18,6 +18,7 @@ VERSION="${PROJECT_VERSION:?PROJECT_VERSION not set}"
|
||||
ENVIRONMENT="${CD_ENVIRONMENT:-}"
|
||||
FORMAT="${CD_FORMAT:-helm}"
|
||||
IMAGE_NAME="${CD_IMAGE_NAME:-}"
|
||||
CONFIGURED_PATH="${CD_PATH:-}"
|
||||
VALUES="${VALUES_FILE:-values.yaml}"
|
||||
KEY="${IMAGE_KEY:-image.tag}"
|
||||
|
||||
@@ -34,6 +35,31 @@ if [[ -z "${GITHUB_TOKEN:-}" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Release versions are owned by exactly one commit (allocate_version reserves the
|
||||
# v<version> tag atomically). Refuse to point the GitOps repo at a version that
|
||||
# belongs to a different commit: the image tag would not contain this build.
|
||||
if [[ "${CIRCLE_BRANCH:-}" == "main" && "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ && -z "${MUNITOR_SKIP_TAG_OWNERSHIP_CHECK:-}" ]]; then
|
||||
SOURCE_REPO="${MUNITOR_SOURCE_REPO:-${CIRCLE_PROJECT_USERNAME:-}/${CIRCLE_PROJECT_REPONAME:-}}"
|
||||
SOURCE_SHA="${CIRCLE_SHA1:?CIRCLE_SHA1 not set}"
|
||||
github_get() {
|
||||
curl -fsS -H "Authorization: Bearer ${GITHUB_TOKEN}" -H "Accept: application/vnd.github+json" "https://api.github.com/repos/${SOURCE_REPO}/$1"
|
||||
}
|
||||
if ! TAG_REF=$(github_get "git/ref/tags/v${VERSION}"); then
|
||||
echo "ERROR: Release tag v${VERSION} does not exist in ${SOURCE_REPO}; the version was never reserved."
|
||||
exit 1
|
||||
fi
|
||||
TAG_SHA=$(echo "${TAG_REF}" | jq -r '.object.sha')
|
||||
if [[ "$(echo "${TAG_REF}" | jq -r '.object.type')" == "tag" ]]; then
|
||||
TAG_SHA=$(github_get "git/tags/${TAG_SHA}" | jq -r '.object.sha')
|
||||
fi
|
||||
if [[ "${TAG_SHA}" != "${SOURCE_SHA}" ]]; then
|
||||
echo "ERROR: Version ${VERSION} belongs to commit ${TAG_SHA}, not ${SOURCE_SHA}."
|
||||
echo "Refusing to bump the GitOps repo: the image tag ${VERSION} was not built from this commit."
|
||||
exit 1
|
||||
fi
|
||||
echo "Verified v${VERSION} -> ${SOURCE_SHA}."
|
||||
fi
|
||||
|
||||
echo "Updating CD repo: ${REPO} (format=${FORMAT}, version=${VERSION})"
|
||||
|
||||
# Clone the CD repo using explicit token auth
|
||||
@@ -42,102 +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
|
||||
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
|
||||
|
||||
# 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}"
|
||||
|
||||
# Find kustomization.yaml in overlays/<env>/
|
||||
if [[ -n "${ENVIRONMENT}" ]]; then
|
||||
KUST_FILE="overlays/${ENVIRONMENT}/kustomization.yaml"
|
||||
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."
|
||||
echo "Available overlays:"
|
||||
ls overlays/ 2>/dev/null || echo " (no overlays directory)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Updating ${KUST_FILE}: ${IMAGE_NAME} -> ${VERSION}"
|
||||
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 tag to ${VERSION}"
|
||||
git push origin HEAD
|
||||
# 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)..."
|
||||
|
||||
echo "CD repo updated successfully."
|
||||
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}")"
|
||||
}
|
||||
|
||||
for attempt in 1 2 3; do
|
||||
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 commit the GitOps update after ${attempt} attempts."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Commit raced with another GitOps update; refreshing before retry ${attempt}/3."
|
||||
git fetch -q origin "${BRANCH}"
|
||||
git reset -q --hard "origin/${BRANCH}"
|
||||
done
|
||||
@@ -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 ]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
pipeline: node-webapp
|
||||
orb_version: dev:snapshot
|
||||
image_name: investinginchester
|
||||
node_version: "22"
|
||||
|
||||
docker:
|
||||
registry: ghcr.io/koftwentytwo
|
||||
use_repo_dockerfile: true
|
||||
|
||||
cd:
|
||||
repo: KofTwentyTwo/cluster-gitops
|
||||
format: kustomize
|
||||
production_only: true
|
||||
path: tenants/investinginchester/website/kustomization.yaml
|
||||
|
||||
contexts:
|
||||
registry: ghcr
|
||||
github: github
|
||||
Loaded 100 of 124 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user