Compare commits

..
11 Commits
Author SHA1 Message Date
james.maes d766747246 feat: make org defaults overridable via .munitor.yml org section (#1) 2026-03-18 12:40:13 -05:00
james.maes 5f708237a7 test: validate all external download URLs are reachable
Adds curl HEAD checks for every pinned binary version (Trivy, gh,
gitleaks, kube-linter, terragrunt, yq, kustomize, kubesec, OpenTofu,
sonar-scanner). Also bumps install_tfsec.sh Trivy to 0.69.3.
2026-03-15 15:21:03 -05:00
james.maes d9735fb3ce fix: bump Trivy from 0.58.2 to 0.69.3
v0.58.2 was removed from GitHub releases, breaking security-scan in all
downstream pipelines.
2026-03-15 15:18:16 -05:00
james.maesandClaude Opus 4.6 cb26668ae7 feat: add migrations runner and API smoke tests to health check
Run a migrations image against the DB sidecar before starting the app
container, and validate API endpoints return HTTP 200 after the health
check passes. Adds health_migrations and health_smoke_paths parameters
throughout the orb pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 13:57:21 -05:00
james.maes f9070fdad0 fix: pass image_name and registry to build_push_supplemental command
The script requires IMAGE_NAME and REGISTRY env vars, but they were
only set as step-level vars in docker_build and not available to later
steps. Added them as explicit command parameters passed from the job.
2026-03-08 16:06:43 -05:00
james.maes 9bc3bcefbd fix: pass supplemental_images param through update_cd_repo job
The job definition was missing the supplemental_images parameter,
so CircleCI silently dropped it even though the template and command
both had it. Added parameter declaration and forwarding, plus
functional upsert tests.
2026-03-08 09:11:03 -05:00
james.maes 8466d2daa3 fix: upsert supplemental image entries in kustomization.yaml
The yq select silently no-ops when the image entry doesn't exist.
Now creates the entry if missing, then updates if it already exists.
2026-03-08 08:16:25 -05:00
james.maes becc94fa1d test: assert supplemental_images JSON is single-line
Multiline JSON breaks when injected into YAML template parameters.
Add assertion to prevent regression.
2026-03-07 16:42:36 -06:00
james.maes 8dcdfc1f43 fix: compact supplemental_images JSON to single line for YAML injection
yq tojson produces pretty-printed multiline JSON which breaks when
envsubst injects it into a single-line YAML parameter value. Use
yq -o=json -I=0 for compact single-line output.
2026-03-07 16:41:11 -06:00
james.maesandClaude Opus 4.6 1f829edc81 feat: add supplemental Docker image support for sidecar containers
Build, Trivy-scan, and push N additional images (e.g. Liquibase migrations)
alongside the main app image via supplemental_images config in .munitor.yml.
Supports auto-generated Dockerfiles from base+copy or user-provided Dockerfiles,
and updates CD repos with all image tags atomically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:49:16 -06:00
james.maesandClaude Opus 4.6 94e3ded47d feat: pass GIT_COMMIT_SHA build arg to Docker builds
Add ARG/ENV GIT_COMMIT_SHA to node-webapp and node-api/nextjs Dockerfile
templates so Next.js can inline the commit SHA at build time. Pass
CIRCLE_SHA1 as the build arg value in docker_build.sh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:17:15 -06:00
31 changed files with 749 additions and 39 deletions

No files matched your search

+5 -4
View File
@@ -1,10 +1,11 @@
version: 2.1
description: >
Munitor (Latin: fortifier, protector) -- KofTwentyTwo's unified CircleCI orb
for building, testing, and deploying all repositories. Provides standardized
CI/CD pipelines via dynamic configuration so each repo needs only a tiny
.munitor.yml to define its pipeline.
Munitor (Latin: fortifier, protector) -- a unified CircleCI orb for building,
testing, and deploying repositories. Provides standardized CI/CD pipelines via
dynamic configuration so each repo needs only a tiny .munitor.yml to define
its pipeline. Org-agnostic: override defaults via the optional org section in
.munitor.yml.
display:
home_url: https://github.com/KofTwentyTwo/Munitor
+24
View File
@@ -0,0 +1,24 @@
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).
parameters:
supplemental_images:
type: string
default: "[]"
description: JSON array of supplemental image definitions.
image_name:
type: string
description: Docker image name (without registry prefix).
registry:
type: string
default: ghcr.io/KofTwentyTwo
description: Container registry URL.
steps:
- run:
name: Build and push supplemental images
environment:
SUPPLEMENTAL_IMAGES_JSON: << parameters.supplemental_images >>
IMAGE_NAME: << parameters.image_name >>
REGISTRY: << parameters.registry >>
command: << include(scripts/build_push_supplemental.sh) >>
+10
View File
@@ -15,6 +15,14 @@ parameters:
type: string
default: "false"
description: Start a PostgreSQL sidecar for apps that require a database to boot.
health_migrations:
type: string
default: ""
description: Name of supplemental image to run as migrations before the app (e.g. "migrations").
health_smoke_paths:
type: string
default: ""
description: Comma-separated API paths to smoke test after health check passes (e.g. "/api/v1/site/query").
steps:
- run:
name: Health check
@@ -22,4 +30,6 @@ steps:
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 >>
command: << include(scripts/health_check.sh) >>
+1 -1
View File
@@ -4,7 +4,7 @@ description: >
steps:
- run:
name: Write Trivy version file
command: echo "${TRIVY_VERSION:-0.58.2}" > .trivy-version
command: echo "${TRIVY_VERSION:-0.69.3}" > .trivy-version
- restore_cache:
keys:
- trivy-bin-v1-{{ checksum ".trivy-version" }}
+1 -1
View File
@@ -6,7 +6,7 @@ parameters:
description: Full image tag to scan (registry/name:tag).
trivy_version:
type: string
default: "0.58.2"
default: "0.69.3"
description: Trivy version to install.
steps:
- run:
+1 -1
View File
@@ -3,7 +3,7 @@ description: >
steps:
- run:
name: Write Trivy version file
command: echo "${TRIVY_VERSION:-0.58.2}" > .trivy-version
command: echo "${TRIVY_VERSION:-0.69.3}" > .trivy-version
- save_cache:
key: trivy-bin-v1-{{ checksum ".trivy-version" }}
paths:
+7
View File
@@ -38,6 +38,12 @@ parameters:
type: string
default: ""
description: Git author name for CD repo commits.
supplemental_images:
type: string
default: "[]"
description: >
JSON array of supplemental images. Each entry may contain cd.image_key (Helm)
or cd.image_name (Kustomize) for updating the CD repo with supplemental image tags.
steps:
- run:
name: Update CD repo
@@ -50,4 +56,5 @@ steps:
CD_IMAGE_NAME: << parameters.cd_image_name >>
CI_GIT_EMAIL: << parameters.ci_git_email >>
CI_GIT_NAME: << parameters.ci_git_name >>
SUPPLEMENTAL_IMAGES_JSON: << parameters.supplemental_images >>
command: << include(scripts/update_cd_repo.sh) >>
+18
View File
@@ -20,6 +20,14 @@ parameters:
type: string
default: "false"
description: Start a PostgreSQL sidecar for apps that require a database to boot.
health_migrations:
type: string
default: ""
description: Name of supplemental image to run as migrations before the app (e.g. "migrations").
health_smoke_paths:
type: string
default: ""
description: Comma-separated API paths to smoke test after health check passes.
resource_class:
type: enum
enum:
@@ -31,6 +39,10 @@ parameters:
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, scan, and push alongside the main image.
executor:
name: machine
resource_class: << parameters.resource_class >>
@@ -49,8 +61,14 @@ steps:
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}
- save_trivy_cache
- docker_push_ghcr
- build_push_supplemental:
supplemental_images: << parameters.supplemental_images >>
image_name: << parameters.image_name >>
registry: << parameters.registry >>
+7
View File
@@ -32,6 +32,12 @@ parameters:
type: string
default: ""
description: Git author name for CD repo commits.
supplemental_images:
type: string
default: "[]"
description: >
JSON array of supplemental images. Each entry may contain cd.image_key (Helm)
or cd.image_name (Kustomize) for updating the CD repo with supplemental image tags.
executor: base
steps:
- attach_workspace:
@@ -48,3 +54,4 @@ steps:
cd_image_name: << parameters.cd_image_name >>
ci_git_email: << parameters.ci_git_email >>
ci_git_name: << parameters.ci_git_name >>
supplemental_images: << parameters.supplemental_images >>
+97
View File
@@ -0,0 +1,97 @@
#!/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 "build_push_supplemental"
munitor_check_tool jq --version
munitor_check_tool docker --version
IMAGES_JSON="${SUPPLEMENTAL_IMAGES_JSON:-[]}"
# Skip if no supplemental images defined
if [[ "${IMAGES_JSON}" == "[]" || "${IMAGES_JSON}" == "null" || -z "${IMAGES_JSON}" ]]; then
echo "No supplemental images defined, skipping."
exit 0
fi
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:-}"
COUNT=$(echo "${IMAGES_JSON}" | jq 'length')
echo "Found ${COUNT} supplemental image(s) to build."
for i in $(seq 0 $((COUNT - 1))); do
ENTRY=$(echo "${IMAGES_JSON}" | jq -c ".[$i]")
NAME=$(echo "${ENTRY}" | jq -r '.name')
BASE=$(echo "${ENTRY}" | jq -r '.base // empty')
DOCKERFILE=$(echo "${ENTRY}" | jq -r '.dockerfile // empty')
DERIVED_NAME="${IMAGE}-${NAME}"
FULL_TAG="${REGISTRY}/${DERIVED_NAME}:${VERSION}"
echo ""
echo "--- Supplemental image: ${DERIVED_NAME} ---"
if [[ -n "${DOCKERFILE}" ]]; then
echo "Using provided Dockerfile: ${DOCKERFILE}"
elif [[ -n "${BASE}" ]]; then
# Auto-generate Dockerfile
DOCKERFILE="Dockerfile.${NAME}"
echo "Auto-generating ${DOCKERFILE} from base: ${BASE}"
{
echo "FROM ${BASE}"
COPY_COUNT=$(echo "${ENTRY}" | jq '.copy // [] | length')
for j in $(seq 0 $((COPY_COUNT - 1))); do
SRC=$(echo "${ENTRY}" | jq -r ".copy[$j].src")
DEST=$(echo "${ENTRY}" | jq -r ".copy[$j].dest")
echo "COPY ${SRC} ${DEST}"
done
} > "${DOCKERFILE}"
echo "Generated ${DOCKERFILE}:"
cat "${DOCKERFILE}"
else
echo "ERROR: Supplemental image '${NAME}' must specify either 'dockerfile' or 'base'."
exit 1
fi
# Build
echo "Building ${FULL_TAG} ..."
docker build -f "${DOCKERFILE}" -t "${FULL_TAG}" .
if [[ -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}"
# Push
echo "Pushing ${FULL_TAG} ..."
docker push "${FULL_TAG}"
if [[ -n "${ENV_TAG}" ]]; then
echo "Pushing ${ENV_FULL_TAG} ..."
docker push "${ENV_FULL_TAG}"
fi
echo "--- Done: ${DERIVED_NAME} ---"
done
echo ""
echo "All supplemental images built, scanned, and pushed."
+1 -1
View File
@@ -27,7 +27,7 @@ BUILD_CMD=(docker build -f "${DOCKERFILE}" -t "${FULL_IMAGE}:${VERSION}")
if [[ -n "${ENV_TAG}" ]]; then
BUILD_CMD+=(-t "${FULL_IMAGE}:${ENV_TAG}")
fi
BUILD_CMD+=(--build-arg "VERSION=${VERSION}" .)
BUILD_CMD+=(--build-arg "VERSION=${VERSION}" --build-arg "GIT_COMMIT_SHA=${CIRCLE_SHA1:-}" .)
"${BUILD_CMD[@]}"
+22 -8
View File
@@ -12,12 +12,12 @@ extract_munitor_vars() {
return 1
fi
# --- Org defaults (change this block when forking for another org) ---
local ORG_DOCKER_REGISTRY="ghcr.io/KofTwentyTwo"
local ORG_NPM_SCOPE="@koftwentytwo"
local ORG_CI_EMAIL="munitor-ci@koftwentytwo.com"
local ORG_CI_NAME="Munitor CI"
local ORG_ORB_SLUG="kof22/munitor"
# --- Org defaults (overridable via .munitor.yml org section) ---
local ORG_DOCKER_REGISTRY=$(yq '.org.docker_registry // "ghcr.io/KofTwentyTwo"' "${config_file}")
local ORG_NPM_SCOPE=$(yq '.org.npm_scope // "@koftwentytwo"' "${config_file}")
local ORG_CI_EMAIL=$(yq '.org.ci_email // "munitor-ci@koftwentytwo.com"' "${config_file}")
local ORG_CI_NAME=$(yq '.org.ci_name // "Munitor CI"' "${config_file}")
local ORG_ORB_SLUG=$(yq '.org.orb_slug // "kof22/munitor"' "${config_file}")
# Core pipeline settings
MUNITOR_PIPELINE=$(yq '.pipeline' "${config_file}")
@@ -164,6 +164,19 @@ extract_munitor_vars() {
esac
fi
MUNITOR_HEALTH_DB=$(yq '.health.db // false' "${config_file}")
MUNITOR_HEALTH_MIGRATIONS=$(yq '.health.migrations // ""' "${config_file}")
if [[ "${MUNITOR_HEALTH_MIGRATIONS}" == "null" ]]; then MUNITOR_HEALTH_MIGRATIONS=""; fi
MUNITOR_HEALTH_SMOKE_PATHS=$(yq '.health.smoke_paths // [] | join(",")' "${config_file}")
if [[ "${MUNITOR_HEALTH_SMOKE_PATHS}" == "null" ]]; then MUNITOR_HEALTH_SMOKE_PATHS=""; fi
# Supplemental images (e.g., migrations, db-backup)
MUNITOR_SUPPLEMENTAL_IMAGES_JSON=$(yq -o=json -I=0 '.supplemental_images // []' "${config_file}")
if [[ "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" == "[]" || "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" == "null" ]]; then
MUNITOR_SUPPLEMENTAL="false"
MUNITOR_SUPPLEMENTAL_IMAGES_JSON="[]"
else
MUNITOR_SUPPLEMENTAL="true"
fi
# Derived flags
if [[ -n "${MUNITOR_SONAR_PROJECT_KEY}" ]]; then
@@ -202,12 +215,13 @@ extract_munitor_vars() {
export MUNITOR_KUBE_LINTER_CONFIG MUNITOR_YAMLLINT_PATHS
export MUNITOR_NODE_FRAMEWORK MUNITOR_PACKAGE_MANAGER
export MUNITOR_CI_EMAIL MUNITOR_CI_NAME MUNITOR_NPM_DEFAULT_SCOPE MUNITOR_ORB_SLUG
export MUNITOR_HEALTH_PATH MUNITOR_HEALTH_PORT MUNITOR_HEALTH_DB
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
}
# 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_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}'
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}'
}
+2 -2
View File
@@ -94,7 +94,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 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"
KNOWN_KEYS="pipeline orb_version image_name 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 +198,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; do
for flag in E2E SBOM SONAR NPM_AUTH SERVICES TEST_SETUP CUSTOM_TEST COVERAGE_CMD GITHUB_RELEASE SAST CD OWASP NVD SUPPLEMENTAL; do
local var_name="MUNITOR_${flag}"
local value="${!var_name:-false}"
local flag_file
+4
View File
@@ -53,6 +53,8 @@ WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV STANDALONE=true
ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
RUN npm run build
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
@@ -154,6 +156,8 @@ WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
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
+56
View File
@@ -14,6 +14,8 @@ fi
HEALTH_PATH="${HEALTH_PATH:-/api/health}"
HEALTH_PORT="${HEALTH_PORT:-3000}"
HEALTH_DB="${HEALTH_DB:-false}"
HEALTH_MIGRATIONS="${HEALTH_MIGRATIONS:-}"
HEALTH_SMOKE_PATHS="${HEALTH_SMOKE_PATHS:-}"
# DOCKER_IMAGE and DOCKER_TAG are exported to BASH_ENV by docker_build.sh
IMAGE="${DOCKER_IMAGE:?DOCKER_IMAGE not set -- run docker_build first}"
@@ -64,6 +66,23 @@ if [[ "${HEALTH_DB}" == "true" ]]; then
sleep 1
done
# Run migrations if a migrations image is specified
if [[ -n "${HEALTH_MIGRATIONS}" ]]; then
MIGRATIONS_IMAGE="${DOCKER_IMAGE}-${HEALTH_MIGRATIONS}:${TAG}"
echo "Running migrations image: ${MIGRATIONS_IMAGE}"
docker run --rm \
--name "munitor-health-migrations-$$" \
--network "${NETWORK_NAME}" \
-e RDBMS_HOSTNAME="${DB_CONTAINER_NAME}" \
-e RDBMS_PORT=5432 \
-e RDBMS_DATABASE_NAME=postgres \
-e RDBMS_USERNAME=postgres \
-e RDBMS_PASSWORD=postgres \
-e LB_CONTEXTS=dev \
"${MIGRATIONS_IMAGE}"
echo "Migrations completed."
fi
# Start app container on the same network with DB env vars
docker run -d \
--name "${CONTAINER_NAME}" \
@@ -129,3 +148,40 @@ if [[ "${HEALTHY}" != "true" ]]; then
echo "--- End container logs ---"
exit 1
fi
# --- API smoke tests ---
if [[ -n "${HEALTH_SMOKE_PATHS}" ]]; then
echo ""
echo "Running API smoke tests..."
SMOKE_FAILED=false
IFS=',' read -ra SMOKE_ENDPOINTS <<< "${HEALTH_SMOKE_PATHS}"
for endpoint in "${SMOKE_ENDPOINTS[@]}"; do
endpoint=$(echo "${endpoint}" | xargs) # trim whitespace
SMOKE_URL="http://localhost:${HEALTH_PORT}${endpoint}"
echo " Smoke test: GET ${endpoint}"
SMOKE_CODE=$(curl -s -o /tmp/smoke_response.txt -w "%{http_code}" "${SMOKE_URL}" 2>/dev/null || echo "000")
if [[ "${SMOKE_CODE}" == "200" ]]; then
echo " PASSED (HTTP ${SMOKE_CODE})"
else
BODY=$(cat /tmp/smoke_response.txt 2>/dev/null || echo "")
echo " FAILED (HTTP ${SMOKE_CODE})"
echo " Response: ${BODY:0:500}"
SMOKE_FAILED=true
fi
done
if [[ "${SMOKE_FAILED}" == "true" ]]; then
echo ""
echo "ERROR: API smoke tests FAILED"
echo ""
echo "--- Container logs (last 50 lines) ---"
docker logs --tail 50 "${CONTAINER_NAME}" 2>&1 || true
echo "--- End container logs ---"
exit 1
fi
echo "All smoke tests PASSED."
fi
+1 -1
View File
@@ -12,7 +12,7 @@ elif ! type munitor_header &>/dev/null; then
fi
# tfsec is deprecated/archived -- use trivy config instead
TRIVY_VERSION="${TRIVY_VERSION:-0.58.2}"
TRIVY_VERSION="${TRIVY_VERSION:-0.69.3}"
INSTALL_DIR="${HOME}/bin"
export PATH="${INSTALL_DIR}:${PATH}"
+73 -9
View File
@@ -109,12 +109,12 @@ extract_munitor_vars() {
return 1
fi
# --- Org defaults (change this block when forking for another org) ---
local ORG_DOCKER_REGISTRY="ghcr.io/KofTwentyTwo"
local ORG_NPM_SCOPE="@koftwentytwo"
local ORG_CI_EMAIL="munitor-ci@koftwentytwo.com"
local ORG_CI_NAME="Munitor CI"
local ORG_ORB_SLUG="kof22/munitor"
# --- Org defaults (overridable via .munitor.yml org section) ---
local ORG_DOCKER_REGISTRY=$(yq '.org.docker_registry // "ghcr.io/KofTwentyTwo"' "${config_file}")
local ORG_NPM_SCOPE=$(yq '.org.npm_scope // "@koftwentytwo"' "${config_file}")
local ORG_CI_EMAIL=$(yq '.org.ci_email // "munitor-ci@koftwentytwo.com"' "${config_file}")
local ORG_CI_NAME=$(yq '.org.ci_name // "Munitor CI"' "${config_file}")
local ORG_ORB_SLUG=$(yq '.org.orb_slug // "kof22/munitor"' "${config_file}")
# Core pipeline settings
MUNITOR_PIPELINE=$(yq '.pipeline' "${config_file}")
@@ -262,6 +262,15 @@ extract_munitor_vars() {
fi
MUNITOR_HEALTH_DB=$(yq '.health.db // false' "${config_file}")
# Supplemental images (e.g., migrations, db-backup)
MUNITOR_SUPPLEMENTAL_IMAGES_JSON=$(yq '.supplemental_images // [] | tojson' "${config_file}")
if [[ "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" == "[]" || "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" == "null" ]]; then
MUNITOR_SUPPLEMENTAL="false"
MUNITOR_SUPPLEMENTAL_IMAGES_JSON="[]"
else
MUNITOR_SUPPLEMENTAL="true"
fi
# Derived flags
if [[ -n "${MUNITOR_SONAR_PROJECT_KEY}" ]]; then
MUNITOR_SONAR="true"
@@ -300,13 +309,14 @@ extract_munitor_vars() {
export MUNITOR_NODE_FRAMEWORK MUNITOR_PACKAGE_MANAGER
export MUNITOR_CI_EMAIL MUNITOR_CI_NAME MUNITOR_NPM_DEFAULT_SCOPE MUNITOR_ORB_SLUG
export MUNITOR_HEALTH_PATH MUNITOR_HEALTH_PORT MUNITOR_HEALTH_DB
export MUNITOR_SUPPLEMENTAL MUNITOR_SUPPLEMENTAL_IMAGES_JSON
export MUNITOR_SONAR MUNITOR_CD MUNITOR_NVD
}
# 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_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}'
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_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_EXTRACT_EOF
@@ -490,6 +500,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -538,6 +551,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -579,6 +595,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
context:
@@ -593,6 +612,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -810,6 +832,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -852,6 +877,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -907,6 +935,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
context:
@@ -921,6 +952,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -1138,6 +1172,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -1180,6 +1217,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -1235,6 +1275,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
context:
@@ -1249,6 +1292,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -1811,6 +1857,9 @@ cat > /tmp/munitor/templates/partials/java-webapp-deploy.yml.tpl << 'MUNITOR_PAR
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -1857,6 +1906,9 @@ cat > /tmp/munitor/templates/partials/java-webapp-deploy.yml.tpl << 'MUNITOR_PAR
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -1961,6 +2013,9 @@ cat > /tmp/munitor/templates/partials/node-api-deploy.yml.tpl << 'MUNITOR_PARTIA
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -2001,6 +2056,9 @@ cat > /tmp/munitor/templates/partials/node-api-deploy.yml.tpl << 'MUNITOR_PARTIA
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -2105,6 +2163,9 @@ cat > /tmp/munitor/templates/partials/node-webapp-deploy.yml.tpl << 'MUNITOR_PAR
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -2145,6 +2206,9 @@ cat > /tmp/munitor/templates/partials/node-webapp-deploy.yml.tpl << 'MUNITOR_PAR
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -2258,7 +2322,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 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"
KNOWN_KEYS="pipeline orb_version image_name 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
@@ -2362,7 +2426,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; do
for flag in E2E SBOM SONAR NPM_AUTH SERVICES TEST_SETUP CUSTOM_TEST COVERAGE_CMD GITHUB_RELEASE SAST CD OWASP NVD SUPPLEMENTAL; do
local var_name="MUNITOR_${flag}"
local value="${!var_name:-false}"
local flag_file
+21 -7
View File
@@ -109,12 +109,12 @@ extract_munitor_vars() {
return 1
fi
# --- Org defaults (change this block when forking for another org) ---
local ORG_DOCKER_REGISTRY="ghcr.io/KofTwentyTwo"
local ORG_NPM_SCOPE="@koftwentytwo"
local ORG_CI_EMAIL="munitor-ci@koftwentytwo.com"
local ORG_CI_NAME="Munitor CI"
local ORG_ORB_SLUG="kof22/munitor"
# --- Org defaults (overridable via .munitor.yml org section) ---
local ORG_DOCKER_REGISTRY=$(yq '.org.docker_registry // "ghcr.io/KofTwentyTwo"' "${config_file}")
local ORG_NPM_SCOPE=$(yq '.org.npm_scope // "@koftwentytwo"' "${config_file}")
local ORG_CI_EMAIL=$(yq '.org.ci_email // "munitor-ci@koftwentytwo.com"' "${config_file}")
local ORG_CI_NAME=$(yq '.org.ci_name // "Munitor CI"' "${config_file}")
local ORG_ORB_SLUG=$(yq '.org.orb_slug // "kof22/munitor"' "${config_file}")
# Core pipeline settings
MUNITOR_PIPELINE=$(yq '.pipeline' "${config_file}")
@@ -262,6 +262,15 @@ extract_munitor_vars() {
fi
MUNITOR_HEALTH_DB=$(yq '.health.db // false' "${config_file}")
# Supplemental images (e.g., migrations, db-backup)
MUNITOR_SUPPLEMENTAL_IMAGES_JSON=$(yq '.supplemental_images // [] | tojson' "${config_file}")
if [[ "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" == "[]" || "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" == "null" ]]; then
MUNITOR_SUPPLEMENTAL="false"
MUNITOR_SUPPLEMENTAL_IMAGES_JSON="[]"
else
MUNITOR_SUPPLEMENTAL="true"
fi
# Derived flags
if [[ -n "${MUNITOR_SONAR_PROJECT_KEY}" ]]; then
MUNITOR_SONAR="true"
@@ -300,13 +309,14 @@ extract_munitor_vars() {
export MUNITOR_NODE_FRAMEWORK MUNITOR_PACKAGE_MANAGER
export MUNITOR_CI_EMAIL MUNITOR_CI_NAME MUNITOR_NPM_DEFAULT_SCOPE MUNITOR_ORB_SLUG
export MUNITOR_HEALTH_PATH MUNITOR_HEALTH_PORT MUNITOR_HEALTH_DB
export MUNITOR_SUPPLEMENTAL MUNITOR_SUPPLEMENTAL_IMAGES_JSON
export MUNITOR_SONAR MUNITOR_CD MUNITOR_NVD
}
# 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_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}'
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_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_EXTRACT_EOF
@@ -368,6 +378,8 @@ WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV STANDALONE=true
ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
RUN npm run build
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
@@ -469,6 +481,8 @@ WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
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
+1 -1
View File
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
TRIVY_VERSION="${TRIVY_VERSION:-0.58.2}"
TRIVY_VERSION="${TRIVY_VERSION:-0.69.3}"
INSTALL_DIR="${HOME}/bin"
mkdir -p "${INSTALL_DIR}"
export PATH="${INSTALL_DIR}:${PATH}"
+1 -1
View File
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
TRIVY_VERSION="${TRIVY_VERSION:-0.58.2}"
TRIVY_VERSION="${TRIVY_VERSION:-0.69.3}"
INSTALL_DIR="${HOME}/bin"
mkdir -p "${INSTALL_DIR}"
export PATH="${INSTALL_DIR}:${PATH}"
+16
View File
@@ -177,6 +177,11 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
health_migrations: "${MUNITOR_HEALTH_MIGRATIONS}"
health_smoke_paths: "${MUNITOR_HEALTH_SMOKE_PATHS}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -225,6 +230,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -266,6 +274,11 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
health_migrations: "${MUNITOR_HEALTH_MIGRATIONS}"
health_smoke_paths: "${MUNITOR_HEALTH_SMOKE_PATHS}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
context:
@@ -280,6 +293,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
+12
View File
@@ -191,6 +191,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -233,6 +236,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -288,6 +294,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
context:
@@ -302,6 +311,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
+12
View File
@@ -191,6 +191,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -233,6 +236,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -288,6 +294,9 @@ workflows:
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- build-and-test
context:
@@ -302,6 +311,9 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -82,6 +82,9 @@
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -128,6 +131,9 @@
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -89,6 +89,9 @@
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -129,6 +132,9 @@
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -89,6 +89,9 @@
health_path: ${MUNITOR_HEALTH_PATH}
health_port: "${MUNITOR_HEALTH_PORT}"
health_db: "${MUNITOR_HEALTH_DB}"
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
requires:
- code-quality
- coverage
@@ -129,6 +132,9 @@
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
+38 -2
View File
@@ -92,9 +92,45 @@ else
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}"
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 and push
git config user.email "${CI_GIT_EMAIL:-munitor-ci@koftwentytwo.com}"
git config user.name "${CI_GIT_NAME:-Munitor CI}"
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 git diff --cached --quiet; then
echo "No changes to commit (version already up to date)."
+35
View File
@@ -0,0 +1,35 @@
pipeline: java-webapp
orb_version: dev:snapshot
image_name: me-health-portal
java_version: "21"
sonar:
project_key: dmdbrands_me-health-portal
docker:
registry: ghcr.io/dmdbrands
cd:
repo: dmdbrands/me-health-portal-cd-pipeline
coverage:
min_instruction: 80
e2e: true
sbom: true
contexts:
registry: ghcr
github: github
sonar: sonarcloud
nvd: nvd
supplemental_images:
- name: migrations
base: liquibase/liquibase:4.31
copy:
- src: db/changelog/
dest: /liquibase/changelog/
cd:
image_key: migrations.image.tag
+33
View File
@@ -263,6 +263,30 @@ assert_eq "health_port default 3000" "3000" "${MUNITOR_HEALTH_PORT}"
assert_eq "coverage min default" "70" "${MUNITOR_COVERAGE_MIN}"
assert_eq "package_manager default" "npm" "${MUNITOR_PACKAGE_MANAGER}"
# --------------------------------------------------------------------------
# supplemental_images (enabled)
# --------------------------------------------------------------------------
echo ""
echo "=== extract_munitor_vars: supplemental_images (enabled) ==="
extract_munitor_vars "${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml"
assert_eq "supplemental flag" "true" "${MUNITOR_SUPPLEMENTAL}"
assert_eq "supplemental JSON is non-empty" "true" "$([[ "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" != "[]" ]] && echo true || echo false)"
assert_eq "supplemental JSON is single-line" "1" "$(echo "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" | wc -l | tr -d ' ')"
assert_eq "supplemental JSON has migrations" "true" "$(echo "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" | jq -r '.[0].name' 2>/dev/null | grep -q migrations && echo true || echo false)"
assert_eq "supplemental migrations base" "liquibase/liquibase:4.31" "$(echo "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" | jq -r '.[0].base')"
assert_eq "supplemental migrations cd.image_key" "migrations.image.tag" "$(echo "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}" | jq -r '.[0].cd.image_key')"
# --------------------------------------------------------------------------
# supplemental_images (default: disabled)
# --------------------------------------------------------------------------
echo ""
echo "=== extract_munitor_vars: supplemental_images (default disabled) ==="
extract_munitor_vars "${FIXTURES_DIR}/java-webapp.munitor.yml"
assert_eq "supplemental flag default" "false" "${MUNITOR_SUPPLEMENTAL}"
assert_eq "supplemental JSON default empty" "[]" "${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}"
# --------------------------------------------------------------------------
# Missing config file
# --------------------------------------------------------------------------
@@ -326,6 +350,15 @@ else
FAIL=$((FAIL + 1))
fi
echo -n " TEST: get_envsubst_vars includes MUNITOR_SUPPLEMENTAL_IMAGES_JSON... "
if echo "${VARS}" | grep -q 'MUNITOR_SUPPLEMENTAL_IMAGES_JSON'; then
echo "PASS"
PASS=$((PASS + 1))
else
echo "FAIL"
FAIL=$((FAIL + 1))
fi
# --------------------------------------------------------------------------
echo ""
echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
+51
View File
@@ -1080,6 +1080,57 @@ run_negative_test "validate-cd-repo: no overlay_dir param (regression)" \
"${FIXTURES_DIR}/validate-cd-repo.munitor.yml" \
"overlay_dir:"
# --------------------------------------------------------------------------
# Supplemental Images Tests
# --------------------------------------------------------------------------
echo ""
echo "=== Supplemental Images Template Tests ==="
# java-webapp-supplemental: supplemental_images parameter appears in docker-build-push
run_context_test "java-webapp-supplemental: docker-build-push has supplemental_images param" \
"${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml" \
"name: docker-build-push" \
"context:" \
"supplemental_images:"
# java-webapp-supplemental: supplemental_images parameter appears in update-cd-repo
run_context_test "java-webapp-supplemental: update-cd-repo has supplemental_images param" \
"${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml" \
"name: update-cd-repo" \
"context:" \
"supplemental_images:"
# java-webapp (no supplemental): supplemental_images should NOT appear
run_negative_test "java-webapp: no supplemental_images param when not configured" \
"${FIXTURES_DIR}/java-webapp.munitor.yml" \
"supplemental_images:"
# java-webapp-minimal: supplemental_images should NOT appear
run_negative_test "java-webapp-minimal: no supplemental_images param" \
"${FIXTURES_DIR}/java-webapp-minimal.munitor.yml" \
"supplemental_images:"
# java-webapp-supplemental: still has all normal features (regression)
run_test "java-webapp-supplemental: still has e2e" \
"${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml" \
"e2e-tests"
run_test "java-webapp-supplemental: still has sonar" \
"${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml" \
"sonar-scan"
run_test "java-webapp-supplemental: still has update-cd-repo" \
"${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml" \
"update-cd-repo"
run_test "java-webapp-supplemental: has release-candidate workflow" \
"${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml" \
"release-candidate:"
run_test "java-webapp-supplemental: has production workflow" \
"${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml" \
"production:"
echo ""
echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
+181
View File
@@ -327,6 +327,58 @@ else
fail "should delete digest field to prevent kustomize from ignoring newTag"
fi
echo -n " TEST: upserts supplemental images (creates new entry)... "
if grep -q '.images += \[{' "${SRC_SCRIPTS}/update_cd_repo.sh"; then
pass
else
fail "should create new image entries for supplemental images"
fi
echo -n " TEST: checks for existing entry before upsert... "
if grep -q 'EXISTING=' "${SRC_SCRIPTS}/update_cd_repo.sh" && grep -q 'if \[\[ -z "${EXISTING}"' "${SRC_SCRIPTS}/update_cd_repo.sh"; then
pass
else
fail "should check if image entry exists before deciding to create or update"
fi
# Functional test: verify upsert creates entries in a real kustomization file
echo -n " TEST: upsert creates new image entry in kustomization (functional)... "
UPSERT_TMPDIR=$(mktemp -d)
cat > "${UPSERT_TMPDIR}/kustomization.yaml" <<'KUSTYAML'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: ghcr.io/koftwentytwo/website-backend
newTag: "0.1.0"
KUSTYAML
SUPP_CD_IMAGE="ghcr.io/koftwentytwo/website-backend-migrations"
VERSION_TEST="0.2.0-SNAPSHOT.abc1234"
EXISTING=$(yq ".images[] | select(.name == \"${SUPP_CD_IMAGE}\") | .name" "${UPSERT_TMPDIR}/kustomization.yaml")
if [[ -z "${EXISTING}" ]]; then
yq -i ".images += [{\"name\": \"${SUPP_CD_IMAGE}\", \"newTag\": \"${VERSION_TEST}\"}]" "${UPSERT_TMPDIR}/kustomization.yaml"
fi
RESULT_TAG=$(yq ".images[] | select(.name == \"${SUPP_CD_IMAGE}\") | .newTag" "${UPSERT_TMPDIR}/kustomization.yaml")
if [[ "${RESULT_TAG}" == "${VERSION_TEST}" ]]; then
pass
else
fail "expected newTag=${VERSION_TEST}, got ${RESULT_TAG}"
fi
echo -n " TEST: upsert updates existing image entry in kustomization (functional)... "
VERSION_TEST2="0.3.0-SNAPSHOT.def5678"
EXISTING=$(yq ".images[] | select(.name == \"${SUPP_CD_IMAGE}\") | .name" "${UPSERT_TMPDIR}/kustomization.yaml")
if [[ -n "${EXISTING}" ]]; then
yq -i "(.images[] | select(.name == \"${SUPP_CD_IMAGE}\")).newTag = \"${VERSION_TEST2}\"" "${UPSERT_TMPDIR}/kustomization.yaml"
fi
RESULT_TAG2=$(yq ".images[] | select(.name == \"${SUPP_CD_IMAGE}\") | .newTag" "${UPSERT_TMPDIR}/kustomization.yaml")
ENTRY_COUNT=$(yq "[.images[] | select(.name == \"${SUPP_CD_IMAGE}\")] | length" "${UPSERT_TMPDIR}/kustomization.yaml")
if [[ "${RESULT_TAG2}" == "${VERSION_TEST2}" && "${ENTRY_COUNT}" == "1" ]]; then
pass
else
fail "expected newTag=${VERSION_TEST2} with 1 entry, got tag=${RESULT_TAG2} count=${ENTRY_COUNT}"
fi
rm -rf "${UPSERT_TMPDIR}"
# =============================================================================
# Test: docker_build.sh - DOCKER_ENV_TAG support
# =============================================================================
@@ -638,6 +690,89 @@ else
fail "should skip missing directories"
fi
# =============================================================================
# Test: build_push_supplemental.sh - Supplemental image build
# =============================================================================
echo ""
echo "=== build_push_supplemental.sh Tests ==="
echo -n " TEST: sources munitor_helpers... "
if grep -q 'MUNITOR_HELPERS' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should source munitor_helpers"
fi
echo -n " TEST: calls munitor_header... "
if grep -q 'munitor_header' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should call munitor_header"
fi
echo -n " TEST: reads SUPPLEMENTAL_IMAGES_JSON... "
if grep -q 'SUPPLEMENTAL_IMAGES_JSON' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should read SUPPLEMENTAL_IMAGES_JSON"
fi
echo -n " TEST: skips when JSON is empty... "
if grep -q 'exit 0' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should exit 0 when no supplemental images"
fi
echo -n " TEST: uses jq to parse JSON... "
if grep -q 'jq' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should use jq"
fi
echo -n " TEST: builds Docker images... "
if grep -q 'docker build' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should run docker build"
fi
echo -n " TEST: scans with Trivy... "
if grep -q 'trivy image' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should scan with trivy"
fi
echo -n " TEST: pushes images... "
if grep -q 'docker push' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should push images"
fi
echo -n " TEST: supports custom Dockerfile... "
if grep -q 'dockerfile' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should support custom dockerfile"
fi
echo -n " TEST: auto-generates Dockerfile from base+copy... "
if grep -q 'Dockerfile\.' "${SRC_SCRIPTS}/build_push_supplemental.sh" && grep -q 'FROM' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should auto-generate Dockerfile"
fi
echo -n " TEST: uses set -euo pipefail... "
if grep -q 'set -euo pipefail' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "should use strict mode"
fi
# =============================================================================
# Test: Scripts have proper error handling
# =============================================================================
@@ -736,6 +871,52 @@ else
fail "missing munitor_header:${MISSING_HEADER}"
fi
# =============================================================================
# Test: External download URLs are reachable
# =============================================================================
echo ""
echo "=== External Download URL Tests ==="
# Extract pinned versions from scripts (single source of truth)
extract_version() { sed -n "s/.*${1}:-\([v0-9.]*\).*/\1/p" "$2" | head -1; }
TRIVY_VER=$(extract_version TRIVY_VERSION "${SRC_SCRIPTS}/run_trivy.sh")
GH_VER=$(extract_version GH_VERSION "${SRC_SCRIPTS}/install_gh.sh")
GITLEAKS_VER=$(extract_version GITLEAKS_VERSION "${SRC_SCRIPTS}/install_gitleaks.sh")
KUBE_LINTER_VER=$(extract_version KUBE_LINTER_VERSION "${SRC_SCRIPTS}/install_kube_linter.sh")
TERRAGRUNT_VER=$(extract_version TERRAGRUNT_VERSION "${SRC_SCRIPTS}/install_terragrunt.sh")
YQ_VER=$(extract_version YQ_VERSION "${SRC_SCRIPTS}/install_yq.sh")
KUSTOMIZE_VER=$(extract_version KUSTOMIZE_VERSION "${SRC_SCRIPTS}/install_kustomize.sh")
KUBESEC_VER=$(extract_version KUBESEC_VERSION "${SRC_SCRIPTS}/install_kubesec.sh")
TFSEC_TRIVY_VER=$(extract_version TRIVY_VERSION "${SRC_SCRIPTS}/install_tfsec.sh")
SONAR_VER=$(extract_version SONAR_SCANNER_VERSION "${SRC_SCRIPTS}/npm_sonar.sh")
# All URLs target linux/amd64 -- matches the CI executor
URLS=(
"Trivy ${TRIVY_VER}|https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VER}/trivy_${TRIVY_VER}_Linux-64bit.tar.gz"
"Trivy/tfsec ${TFSEC_TRIVY_VER}|https://github.com/aquasecurity/trivy/releases/download/v${TFSEC_TRIVY_VER}/trivy_${TFSEC_TRIVY_VER}_Linux-64bit.tar.gz"
"gh ${GH_VER}|https://github.com/cli/cli/releases/download/v${GH_VER}/gh_${GH_VER}_linux_amd64.tar.gz"
"gitleaks ${GITLEAKS_VER}|https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VER}/gitleaks_${GITLEAKS_VER}_linux_x64.tar.gz"
"kube-linter ${KUBE_LINTER_VER}|https://github.com/stackrox/kube-linter/releases/download/v${KUBE_LINTER_VER}/kube-linter-linux.tar.gz"
"terragrunt ${TERRAGRUNT_VER}|https://github.com/gruntwork-io/terragrunt/releases/download/v${TERRAGRUNT_VER}/terragrunt_linux_amd64"
"yq ${YQ_VER}|https://github.com/mikefarah/yq/releases/download/${YQ_VER}/yq_linux_amd64"
"kustomize ${KUSTOMIZE_VER}|https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VER}/kustomize_v${KUSTOMIZE_VER}_linux_amd64.tar.gz"
"kubesec ${KUBESEC_VER}|https://github.com/controlplaneio/kubesec/releases/download/v${KUBESEC_VER}/kubesec_linux_amd64.tar.gz"
"OpenTofu installer|https://get.opentofu.org/install-opentofu.sh"
"sonar-scanner ${SONAR_VER}|https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_VER}-linux-x64.zip"
)
for entry in "${URLS[@]}"; do
LABEL="${entry%%|*}"
URL="${entry##*|}"
echo -n " TEST: ${LABEL} download URL is reachable... "
HTTP_CODE=$(curl -sI -o /dev/null -w "%{http_code}" -L --retry 2 "${URL}" 2>/dev/null || echo "000")
if [[ "${HTTP_CODE}" == "200" ]]; then
pass
else
fail "HTTP ${HTTP_CODE} for ${URL}"
fi
done
# =============================================================================
# Summary
# =============================================================================