Files
Munitor/CONTRIBUTING.md
james.maesandClaude Opus 5.5 b09ced056f feat: scan container images in PR pipelines
New munitor/docker_scan job (image-scan) in the pr-checks workflow of
java-webapp, gradle-webapp, node-api and node-webapp: builds the app
image and every supplemental image, runs the same Trivy 0.69.3
HIGH/CRITICAL --exit-code 1 scan as the release pipeline (same
.trivyignore), stores the JSON results, and never pushes.
build_push_supplemental gains push (default true, unchanged behavior).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:18:41 -05:00

343 lines
13 KiB
Markdown

# 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.
### Current Modules
| Module | `.munitor.yml` name | Stack | Deploys Docker | Environments | Repos Using It |
|--------|-------------------|-------|---------------|--------------|----------------|
| **Node.js API** | `node-api` | Node.js, Next.js, TypeScript | Yes (auto-generated Dockerfile) | dev, staging, prod | me-health-website, me-health-dashboard-api, fulfillment APIs |
| **Node.js Webapp** | `node-webapp` | Node.js, TypeScript | Yes (user-provided Dockerfile) | dev, staging, prod | Website-Frontend |
| **Java Webapp** | `java-webapp` | Java, Maven | Yes (auto-generated Dockerfile) | dev, staging, prod | Website-Backend |
| **Terraform** | `terraform` | HCL, Terragrunt, OpenTofu | No | N/A | me-health-portal-infrastructure, terraform-sandbox, aft-* |
| **SDK Distribution** | `sdk-distribution` | Any (binary artifacts) | No | N/A | GGBluetoothSDK, ggHealthKitPackage |
| **CD Repo Validation** | `validate-cd-repo` | Kustomize, K8s manifests, ArgoCD | No | N/A | Website-CD, me-health-portal-cd |
| **ArgoCD App-of-Apps** | `argocd-apps` | Kustomize (envs/ layout), ArgoCD | No | N/A | k8s-app-of-apps |
### Planned Modules
| Module | Proposed name | Stack | Priority | Target Repos |
|--------|--------------|-------|----------|--------------|
| **Python App** | `python-app` | Python, pip/poetry, pytest | Medium | sdlc-monitor, me-health-portal-utils |
| **iOS App** | `ios-app` | Swift, Xcode, fastlane | Low | SageiOSApp, perfectPortions3-iOS, GGBluetoothSDK-RPM |
| **Android App** | `android-app` | Kotlin, Gradle | Low | SageAndroidApp, vico |
| **Static Site** | `static-site` | HTML/JS, no Docker | Low | ggSite, balanceWebsite |
## Module Architecture
Each module consists of these components:
```
src/scripts/templates/
{module-name}.yml.tpl # Main template (required)
partials/{module-name}-deploy.yml.tpl # Deploy partial (if multi-env)
src/scripts/
{tool}_{action}.sh # Backing scripts for commands
src/commands/
{tool}_{action}.yml # Orb commands (reusable steps)
src/jobs/
{module-prefix}_build_and_test.yml # Orb jobs (executor + steps)
{module-prefix}_code_quality.yml
{module-prefix}_coverage.yml
...
tests/fixtures/
{module-name}.munitor.yml # Full-featured test fixture
{module-name}-minimal.munitor.yml # Minimal test fixture
```
### How a module executes
```
1. Consumer repo has .munitor.yml with: pipeline: {module-name}
2. generate_config.sh:
a. Reads .munitor.yml
b. extract_munitor_vars.sh maps fields to MUNITOR_* shell variables
c. Selects templates/{module-name}.yml.tpl
d. Expands ##INCLUDE_DEPLOY## markers with deploy partials
e. Runs envsubst for ${MUNITOR_*} variable substitution
f. Processes ##IF_FLAG## / ##ENDIF_FLAG## conditional blocks
g. Validates output YAML
3. CircleCI continuation runs the generated pipeline, which references
munitor orb jobs/commands by name (e.g., munitor/node_build_and_test)
```
## Adding a New Module
### Step 1: Create the pipeline template
**File:** `src/scripts/templates/{module-name}.yml.tpl`
This is the CircleCI config that gets generated. Use `${MUNITOR_*}` variables and conditional blocks:
```yaml
version: 2.1
orbs:
munitor: kof22/munitor@${MUNITOR_ORB_VERSION}
workflows:
pr-checks:
jobs:
- munitor/{module}_build_and_test:
name: build-and-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
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
##ENDIF_SAST##
```
Available conditional flags: `E2E`, `SBOM`, `SONAR`, `NPM_AUTH`, `SERVICES`, `TEST_SETUP`, `CUSTOM_TEST`, `COVERAGE_CMD`, `GITHUB_RELEASE`, `SAST`, `CD`, `NVD`.
Templates use a split workflow pattern for release and production:
- **`release-candidate:`** (release/* branches) -- Full quality pipeline with all gates
- **`production:`** (main branch) -- Fast-path: build + deploy only (quality was enforced on the release branch)
To add a new flag, set it in `extract_munitor_vars.sh`, export it, and add it to the processing loop in `generate_config.sh` (line ~201). **Important:** any optional context (like `nvd`) that may be empty must be wrapped in a conditional to avoid generating empty YAML list items, which silently break CircleCI continuation.
### Step 2: Create deploy partials (if multi-environment)
**File:** `src/scripts/templates/partials/{module-name}-deploy.yml.tpl`
Uses placeholders that get replaced per environment:
- `__WORKFLOW_NAME__` -- workflow name (e.g., `develop`, `staging`)
- `__BRANCH_FILTER__` -- branch pattern (e.g., `develop`, `staging`)
- `__CD_ENVIRONMENT__` -- CD target env (e.g., `develop`, `staging`)
Reference from the main template:
```
##INCLUDE_DEPLOY {module-name}-deploy develop develop develop##
##INCLUDE_DEPLOY {module-name}-deploy staging staging staging##
```
### Step 3: Add variable extraction
**File:** `src/scripts/extract_munitor_vars.sh` (edit existing)
Add module-specific fields to `extract_munitor_vars()`:
```bash
# Python-app settings
MUNITOR_PYTHON_VERSION=$(yq '.python_version // "3.12"' "${config_file}")
```
Then add to the exports:
```bash
export MUNITOR_PYTHON_VERSION
```
And add to `get_envsubst_vars()`:
```bash
echo '... ${MUNITOR_PYTHON_VERSION}'
```
### Step 4: Register the module in generate_config.sh
**File:** `src/scripts/generate_config.sh` (edit existing)
**Required fields validation** (~line 69):
```bash
case "${pipeline}" in
...
python-app)
# Add any required fields for this module
[[ -z "$(yq '.image_name // ""' "${config}")" ]] && missing+=("image_name")
;;
esac
```
**Known keys** (~line 91) -- add any new top-level `.munitor.yml` keys:
```bash
KNOWN_KEYS="... python_version"
```
### Step 5: Create orb jobs and commands
Jobs go in `src/jobs/`, commands in `src/commands/`, scripts in `src/scripts/`.
**Job pattern** (`src/jobs/{module}_build_and_test.yml`):
```yaml
description: >
Build and test Python application.
parameters:
python_version:
type: string
default: "3.12"
description: Python version to install.
resource_class:
type: enum
enum: [medium, large]
default: medium
description: Machine resource class.
executor:
name: machine
resource_class: << parameters.resource_class >>
steps:
- checkout
- install_python:
version: << parameters.python_version >>
- pip_install
- python_test
```
**Command pattern** (`src/commands/pip_install.yml`):
```yaml
description: >
Install Python dependencies via pip.
steps:
- run:
name: pip install
command: <<include(scripts/pip_install.sh)>>
```
**Script pattern** (`src/scripts/pip_install.sh`):
```bash
#!/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 "pip_install"
munitor_check_tool pip3 --version
pip3 install -r requirements.txt
```
### Step 6: Register in pack script
**File:** `scripts/pack_generate_config.sh` (edit existing)
Add template to the packing loop (~line 42):
```bash
for tpl in java-webapp node-api node-webapp terraform sdk-distribution validate-cd-repo python-app; do
```
If you added deploy partials, add those too (~line 50):
```bash
for partial in java-webapp-deploy node-api-deploy node-webapp-deploy python-app-deploy; do
```
Regenerate: `bash scripts/pack_generate_config.sh`
### Step 7: Add tests
**Test fixtures** (`tests/fixtures/`):
Create at least two:
- `{module-name}.munitor.yml` -- all features enabled
- `{module-name}-minimal.munitor.yml` -- bare minimum config
**Template rendering tests** (`tests/test_generate_config.sh`):
```bash
echo ""
echo "=== python-app Template Tests ==="
run_test "python-app: uses correct orb" \
"${FIXTURES_DIR}/python-app.munitor.yml" \
"kof22/munitor@"
run_test "python-app: renders python_version" \
"${FIXTURES_DIR}/python-app.munitor.yml" \
'python_version: "3.12"'
run_negative_test "python-app-minimal: no sast block" \
"${FIXTURES_DIR}/python-app-minimal.munitor.yml" \
"sast-scan"
```
**Variable extraction tests** (`tests/test_extract_munitor_vars.sh`):
```bash
echo ""
echo "=== python-app Variable Tests ==="
extract_munitor_vars "${FIXTURES_DIR}/python-app.munitor.yml"
assert_eq "MUNITOR_PIPELINE" "python-app" "${MUNITOR_PIPELINE}"
assert_eq "MUNITOR_PYTHON_VERSION" "3.12" "${MUNITOR_PYTHON_VERSION}"
```
### Step 8: Validate and publish
```bash
make all # lint + validate + all tests
bash scripts/pack_generate_config.sh # regenerate packed script
circleci orb pack src > orb.yml # pack the orb
circleci orb validate orb.yml # validate
git push origin develop # triggers dev:snapshot publish
```
## Naming Conventions
| Component | Convention | Example |
|-----------|-----------|---------|
| Module name | `lowercase-with-hyphens` | `python-app` |
| Template file | `{module}.yml.tpl` | `python-app.yml.tpl` |
| Deploy partial | `{module}-deploy.yml.tpl` | `python-app-deploy.yml.tpl` |
| Orb command | `snake_case` | `pip_install` |
| Orb job | `snake_case` | `python_build_and_test` |
| Shell script | `snake_case.sh` | `pip_install.sh` |
| MUNITOR variable | `MUNITOR_UPPER_SNAKE` | `MUNITOR_PYTHON_VERSION` |
| Conditional flag | `UPPER_SNAKE` | `##IF_SAST##` |
| Test fixture | `{module}[-variant].munitor.yml` | `python-app-minimal.munitor.yml` |
## Shared Infrastructure
These components are shared across all modules -- you don't need to recreate them:
| Component | What It Does |
|-----------|-------------|
| `munitor_helpers.sh` | Standard header, tool checks, download retry |
| `validate_branch.sh` | GitFlow branch name enforcement |
| `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`) |
| `sbom` job | Software Bill of Materials |
| `sonar_scan` job | SonarCloud analysis |
Reuse these in your template rather than creating new ones. Only create new jobs/commands when existing ones don't fit your stack.