Compare commits

...
15 Commits
Author SHA1 Message Date
james.maes 4fbae520ee fix: add -U flag to force Maven SNAPSHOT updates
Without -U, Maven uses cached SNAPSHOTs from the CircleCI cache
without checking for newer versions. This caused builds to use stale
SNAPSHOT dependencies even after new SNAPSHOTs were published.
2026-03-27 15:50:40 -05:00
james.maes 1e558c18af fix: correct step order — docker login must precede supplemental push
The previous fix moved build_push_supplemental before docker_push_ghcr,
but docker_push_ghcr contains the docker login to GHCR. Without it,
supplemental pushes fail with 'unauthorized'.

Correct order: docker_push_ghcr (login + push main) first, then
build_push_supplemental (build + push supplementals), then health_check
(supplementals now in local Docker cache).

Main image is pushed before health check, but Trivy has already scanned
it and a health check failure still prevents the CD update step.
2026-03-27 10:32:34 -05:00
james.maes e262339fa7 fix: build supplemental images before health check
Supplemental images (e.g. migrations) must be built locally before the
health check runs. Previously, build_push_supplemental ran AFTER
health_check, so on a first-release tag like 0.1.8 the health check
tried to pull a tag that didn't exist yet in GHCR and failed with
'unauthorized'.

New step order:
  docker_build → trivy → build_push_supplemental → health_check → docker_push_ghcr

The migrations image is now in the local Docker cache when health_check
runs, so no GHCR pull is needed. Main image is still only pushed after
the health check passes.
2026-03-27 10:13:34 -05:00
james.maes af150158ab feat: add gradle-webapp pipeline type (#2)
* feat: add gradle-webapp pipeline type

Add full Gradle build support mirroring the existing Maven pipeline.
New commands, scripts, jobs, and templates for Gradle projects with
multi-module JaCoCo aggregation, graceful code quality plugin detection,
and shadow JAR discovery via configurable server_module field.

* fix: extract inline test collection to included script

Move the multi-line mkdir/find command into
collect_gradle_test_results.sh to satisfy RC009 orb review
(commands >64 chars must use include syntax).

* fix: embed gradle templates in packed orb scripts

Add gradle-webapp.yml.tpl and gradle-webapp-deploy.yml.tpl to the
pack_generate_config.sh template/partial embed loops so they are
available at runtime when the orb executes on CircleCI.

* fix: improve gradle_jacoco.sh diagnostics and error messaging

Add directory listing of JaCoCo report output before CSV search to
help diagnose missing reports. Improve error message with exact
build.gradle.kts snippet needed to enable CSV report format.
2026-03-22 18:36:53 -05:00
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
54 changed files with 2528 additions and 52 deletions

No files matched your search

+2 -2
View File
@@ -39,7 +39,7 @@ echo "Packing generate_config.sh..."
echo ''
# Embed each template
for tpl in java-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; do
echo "cat > /tmp/munitor/templates/${tpl}.yml.tpl << 'MUNITOR_TPL_EOF'"
cat "${SRC}/templates/${tpl}.yml.tpl"
echo 'MUNITOR_TPL_EOF'
@@ -47,7 +47,7 @@ echo "Packing generate_config.sh..."
done
# Embed partials
for partial in java-webapp-deploy node-api-deploy node-webapp-deploy; do
for partial in java-webapp-deploy gradle-webapp-deploy node-api-deploy node-webapp-deploy; do
echo "cat > /tmp/munitor/templates/partials/${partial}.yml.tpl << 'MUNITOR_PARTIAL_EOF'"
cat "${SRC}/templates/partials/${partial}.yml.tpl"
echo 'MUNITOR_PARTIAL_EOF'
+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) >>
+6
View File
@@ -0,0 +1,6 @@
description: >
Run Gradle build (compile + package, skip tests).
steps:
- run:
name: Gradle build
command: << include(scripts/gradle_build.sh) >>
+6
View File
@@ -0,0 +1,6 @@
description: >
Run Gradle Checkstyle analysis (skips gracefully if plugin not applied).
steps:
- run:
name: Gradle Checkstyle
command: << include(scripts/gradle_checkstyle.sh) >>
+13
View File
@@ -0,0 +1,13 @@
description: >
Generate JaCoCo coverage report and enforce minimum thresholds.
parameters:
min_instruction:
type: string
default: "70"
description: Minimum instruction coverage percentage.
steps:
- run:
name: Gradle JaCoCo coverage
environment:
MIN_INSTRUCTION: << parameters.min_instruction >>
command: << include(scripts/gradle_jacoco.sh) >>
+6
View File
@@ -0,0 +1,6 @@
description: >
Run Gradle PMD static analysis (skips gracefully if plugin not applied).
steps:
- run:
name: Gradle PMD
command: << include(scripts/gradle_pmd.sh) >>
+6
View File
@@ -0,0 +1,6 @@
description: >
Run Gradle SpotBugs static analysis (skips gracefully if plugin not applied).
steps:
- run:
name: Gradle SpotBugs
command: << include(scripts/gradle_spotbugs.sh) >>
+15
View File
@@ -0,0 +1,15 @@
description: >
Run Gradle tests and store JUnit XML results.
steps:
- run:
name: Gradle test
command: << include(scripts/gradle_test.sh) >>
- run:
name: Collect test results
command: << include(scripts/collect_gradle_test_results.sh) >>
when: always
- store_test_results:
path: /tmp/test-results
- store_artifacts:
path: /tmp/test-results
destination: test-results
+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) >>
+7
View File
@@ -0,0 +1,7 @@
description: >
Restore the Gradle caches based on wrapper and settings checksums.
steps:
- restore_cache:
keys:
- gradle-v1-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "settings.gradle.kts" }}
- gradle-v1-
+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:
+8
View File
@@ -0,0 +1,8 @@
description: >
Save the Gradle caches for future builds.
steps:
- save_cache:
key: gradle-v1-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "settings.gradle.kts" }}
paths:
- ~/.gradle/caches
- ~/.gradle/wrapper
+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) >>
+22 -4
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 >>
@@ -45,12 +57,18 @@ steps:
- 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 >>
- 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 >>
- 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 >>
+43
View File
@@ -0,0 +1,43 @@
description: >
Full Gradle build and test job. Installs Java, restores cache, calculates
version, builds, tests, saves cache, and persists the workspace for
downstream jobs.
parameters:
java_version:
type: string
default: "21"
description: Java major version to install.
resource_class:
type: enum
enum:
- medium
- large
- xlarge
default: large
description: Machine resource class.
executor:
name: machine
resource_class: << parameters.resource_class >>
steps:
- checkout
- install_java:
version: << parameters.java_version >>
- restore_gradle_cache
- calculate_version
- export_version_vars
- gradle_build
- gradle_test
- run:
name: Collect test results
command: << include(scripts/collect_gradle_test_results.sh) >>
when: always
- store_test_results:
path: /tmp/test-results
- run:
name: Persist version metadata
command: <<include(scripts/persist_version_metadata.sh)>>
- save_gradle_cache
- persist_to_workspace:
root: .
paths:
- "."
+27
View File
@@ -0,0 +1,27 @@
description: >
Run code quality checks: Checkstyle, SpotBugs, and PMD.
Requires workspace from gradle_build_and_test.
Skips individual tools gracefully if their Gradle plugin is not applied.
parameters:
java_version:
type: string
default: "21"
description: Java major version to install.
resource_class:
type: enum
enum:
- medium
- large
default: large
description: Docker resource class.
executor:
name: java
java_version: << parameters.java_version >>
resource_class: << parameters.resource_class >>
steps:
- attach_workspace:
at: .
- restore_gradle_cache
- gradle_checkstyle
- gradle_spotbugs
- gradle_pmd
+29
View File
@@ -0,0 +1,29 @@
description: >
Generate coverage report and enforce minimum thresholds.
Requires workspace from gradle_build_and_test.
parameters:
java_version:
type: string
default: "21"
description: Java major version to install.
min_instruction:
type: string
default: "70"
description: Minimum instruction coverage percentage.
resource_class:
type: enum
enum:
- medium
- large
default: large
description: Docker resource class.
executor:
name: java
java_version: << parameters.java_version >>
resource_class: << parameters.resource_class >>
steps:
- attach_workspace:
at: .
- restore_gradle_cache
- gradle_jacoco:
min_instruction: << parameters.min_instruction >>
+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."
+22
View File
@@ -0,0 +1,22 @@
#!/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 "collect_gradle_test_results"
DEST="/tmp/test-results"
mkdir -p "${DEST}"
find . -path "*/build/test-results/test/*.xml" -exec cp {} "${DEST}/" \;
COUNT=$(find "${DEST}" -name "*.xml" 2>/dev/null | wc -l | tr -d ' ')
echo "Collected ${COUNT} test result file(s) to ${DEST}/"
+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[@]}"
+29 -9
View File
@@ -12,18 +12,24 @@ 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
ORG_DOCKER_REGISTRY=$(yq '.org.docker_registry // "ghcr.io/KofTwentyTwo"' "${config_file}")
local ORG_NPM_SCOPE
ORG_NPM_SCOPE=$(yq '.org.npm_scope // "@koftwentytwo"' "${config_file}")
local ORG_CI_EMAIL
ORG_CI_EMAIL=$(yq '.org.ci_email // "munitor-ci@koftwentytwo.com"' "${config_file}")
local ORG_CI_NAME
ORG_CI_NAME=$(yq '.org.ci_name // "Munitor CI"' "${config_file}")
local ORG_ORB_SLUG
ORG_ORB_SLUG=$(yq '.org.orb_slug // "kof22/munitor"' "${config_file}")
# Core pipeline settings
MUNITOR_PIPELINE=$(yq '.pipeline' "${config_file}")
MUNITOR_ORB_VERSION=$(yq '.orb_version // ""' "${config_file}")
MUNITOR_IMAGE_NAME=$(yq '.image_name // ""' "${config_file}")
MUNITOR_JAVA_VERSION=$(yq '.java_version // "21"' "${config_file}")
MUNITOR_SERVER_MODULE=$(yq '.server_module // ""' "${config_file}")
MUNITOR_NODE_VERSION=$(yq '.node_version // "20"' "${config_file}")
MUNITOR_SONAR_PROJECT_KEY=$(yq '.sonar.project_key // ""' "${config_file}")
MUNITOR_DOCKER_REGISTRY=$(yq '.docker.registry // ""' "${config_file}")
@@ -164,6 +170,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
@@ -185,7 +204,7 @@ extract_munitor_vars() {
fi
# Export all variables
export MUNITOR_PIPELINE MUNITOR_ORB_VERSION MUNITOR_IMAGE_NAME MUNITOR_JAVA_VERSION MUNITOR_NODE_VERSION
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_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
@@ -202,12 +221,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} ${MUNITOR_SERVER_MODULE}'
}
+3 -3
View File
@@ -70,7 +70,7 @@ validate_required_fields() {
[[ -z "$(yq '.orb_version // ""' "${config}")" ]] && missing+=("orb_version")
case "${pipeline}" in
java-webapp|node-api|node-webapp)
java-webapp|gradle-webapp|node-api|node-webapp)
[[ -z "$(yq '.image_name // ""' "${config}")" ]] && missing+=("image_name")
[[ -z "$(yq '.docker.registry // ""' "${config}")" ]] && missing+=("docker.registry")
;;
@@ -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 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 +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
+52
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
@@ -139,6 +141,54 @@ ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+ExitOnOut
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
ENTRYPOINT ["sh", "-c", "java \$JAVA_OPTS -jar app.jar"]
DOCKERFILE
;;
gradle-webapp)
echo "Generating Dockerfile for gradle-webapp (java ${MUNITOR_JAVA_VERSION})"
# Find the shadow JAR in the server module's build output
SEARCH_DIR="build/libs"
if [[ -n "${MUNITOR_SERVER_MODULE:-}" ]]; then
SEARCH_DIR="${MUNITOR_SERVER_MODULE}/build/libs"
fi
APP_JAR=$(find "${SEARCH_DIR}" -maxdepth 1 -name "*.jar" \
! -name "*-plain.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" \
2>/dev/null | head -1) || true
if [[ -z "${APP_JAR}" ]]; then
echo "ERROR: No application JAR found in ${SEARCH_DIR}/" >&2
echo "Ensure shadowJar or bootJar is configured in build.gradle.kts" >&2
exit 1
fi
echo "Using JAR: ${APP_JAR}"
cat > Dockerfile <<DOCKERFILE
FROM eclipse-temurin:${MUNITOR_JAVA_VERSION}-jre-alpine
RUN apk update && apk upgrade --no-cache
RUN addgroup -g 1001 -S appgroup && \\
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
COPY ${APP_JAR} app.jar
RUN chown -R appuser:appgroup /app
USER appuser
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+ExitOnOutOfMemoryError"
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
@@ -154,6 +204,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
+30
View File
@@ -0,0 +1,30 @@
#!/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 "gradle_build"
if [[ ! -f "./gradlew" ]]; then
echo "ERROR: ./gradlew not found. Ensure the Gradle wrapper is present."
exit 1
fi
if [[ ! -x "./gradlew" ]]; then
chmod +x ./gradlew
fi
munitor_check_tool java -version
echo "Running Gradle build..."
./gradlew build -x test --no-daemon --console=plain
echo "Gradle build complete."
+34
View File
@@ -0,0 +1,34 @@
#!/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 "gradle_checkstyle"
if [[ ! -f "./gradlew" ]]; then
echo "ERROR: ./gradlew not found. Ensure the Gradle wrapper is present."
exit 1
fi
if [[ ! -x "./gradlew" ]]; then
chmod +x ./gradlew
fi
# Detect if checkstyle plugin is applied
if ! ./gradlew tasks --all 2>/dev/null | grep -q "checkstyleMain"; then
echo "Checkstyle plugin not applied, skipping."
exit 0
fi
echo "Running Checkstyle..."
./gradlew checkstyleMain checkstyleTest --no-daemon --console=plain
echo "Checkstyle passed."
+91
View File
@@ -0,0 +1,91 @@
#!/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_INSTRUCTION:-70}"
munitor_header "gradle_jacoco (min: ${MIN}%)"
# Validate MIN_INSTRUCTION is numeric
if ! [[ "${MIN}" =~ ^[0-9]+$ ]]; then
echo "ERROR: MIN_INSTRUCTION must be a whole number, got '${MIN}'."
exit 1
fi
if [[ ! -f "./gradlew" ]]; then
echo "ERROR: ./gradlew not found. Ensure the Gradle wrapper is present."
exit 1
fi
if [[ ! -x "./gradlew" ]]; then
chmod +x ./gradlew
fi
echo "Generating JaCoCo report..."
./gradlew jacocoTestReport --no-daemon --console=plain
# Show what JaCoCo produced for diagnostics
echo ""
echo "Searching for JaCoCo reports..."
find . -path "*/build/reports/jacoco" -type d 2>/dev/null | while read -r dir; do
echo " Found: ${dir}/"
for f in "${dir}"/test/*; do
[[ -e "${f}" ]] && echo " $(basename "${f}")"
done
done
# Find ALL CSV report files across all subprojects
CSV_FILES=$(find . -path "*/build/reports/jacoco/test/jacocoTestReport.csv" -type f)
if [[ -z "${CSV_FILES}" ]]; then
echo ""
echo "ERROR: No JaCoCo CSV reports found."
echo ""
echo "Gradle does not generate CSV reports by default. Add this to your"
echo "build.gradle.kts (in each module or via allprojects/subprojects):"
echo ""
echo ' tasks.jacocoTestReport {'
echo ' dependsOn(tasks.test)'
echo ' reports {'
echo ' csv.required.set(true)'
echo ' }'
echo ' }'
exit 1
fi
echo ""
echo "Found CSV reports:"
echo "${CSV_FILES}" | sed 's/^/ /'
# Parse CSV: sum INSTRUCTION_MISSED (col 4) and INSTRUCTION_COVERED (col 5) across ALL files
# CSV header: GROUP,PACKAGE,CLASS,INSTRUCTION_MISSED,INSTRUCTION_COVERED,...
read -r MISSED COVERED <<< "$(awk -F',' 'NR>1 { m+=$4; c+=$5 } END { print m, c }' ${CSV_FILES})"
TOTAL=$((MISSED + COVERED))
if [[ "${TOTAL}" -eq 0 ]]; then
echo "WARNING: No instruction data found in JaCoCo report (0 instructions total)."
echo "JaCoCo coverage: N/A (no code to measure)"
exit 0
fi
# Calculate percentage (integer arithmetic, truncated)
PCT=$((COVERED * 100 / TOTAL))
echo "JaCoCo coverage: ${PCT}% (${COVERED}/${TOTAL} instructions covered, minimum: ${MIN}%)"
if [[ "${PCT}" -lt "${MIN}" ]]; then
echo "FAILED: Coverage ${PCT}% is below minimum threshold of ${MIN}%."
exit 1
fi
echo "JaCoCo coverage passed (>= ${MIN}%)."
+34
View File
@@ -0,0 +1,34 @@
#!/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 "gradle_pmd"
if [[ ! -f "./gradlew" ]]; then
echo "ERROR: ./gradlew not found. Ensure the Gradle wrapper is present."
exit 1
fi
if [[ ! -x "./gradlew" ]]; then
chmod +x ./gradlew
fi
# Detect if pmd plugin is applied
if ! ./gradlew tasks --all 2>/dev/null | grep -q "pmdMain"; then
echo "PMD plugin not applied, skipping."
exit 0
fi
echo "Running PMD..."
./gradlew pmdMain --no-daemon --console=plain
echo "PMD passed."
+34
View File
@@ -0,0 +1,34 @@
#!/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 "gradle_spotbugs"
if [[ ! -f "./gradlew" ]]; then
echo "ERROR: ./gradlew not found. Ensure the Gradle wrapper is present."
exit 1
fi
if [[ ! -x "./gradlew" ]]; then
chmod +x ./gradlew
fi
# Detect if spotbugs plugin is applied
if ! ./gradlew tasks --all 2>/dev/null | grep -q "spotbugsMain"; then
echo "SpotBugs plugin not applied, skipping."
exit 0
fi
echo "Running SpotBugs..."
./gradlew spotbugsMain --no-daemon --console=plain
echo "SpotBugs passed."
+28
View File
@@ -0,0 +1,28 @@
#!/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 "gradle_test"
if [[ ! -f "./gradlew" ]]; then
echo "ERROR: ./gradlew not found. Ensure the Gradle wrapper is present."
exit 1
fi
if [[ ! -x "./gradlew" ]]; then
chmod +x ./gradlew
fi
echo "Running Gradle tests..."
./gradlew test --no-daemon --console=plain
echo "Gradle tests complete."
+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}"
+1
View File
@@ -18,6 +18,7 @@ munitor_check_tool java -version
echo "Running Maven build..."
mvn clean install \
-U \
-DskipTests \
--batch-mode \
--fail-at-end \
+561 -12
View File
@@ -109,18 +109,24 @@ 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
ORG_DOCKER_REGISTRY=$(yq '.org.docker_registry // "ghcr.io/KofTwentyTwo"' "${config_file}")
local ORG_NPM_SCOPE
ORG_NPM_SCOPE=$(yq '.org.npm_scope // "@koftwentytwo"' "${config_file}")
local ORG_CI_EMAIL
ORG_CI_EMAIL=$(yq '.org.ci_email // "munitor-ci@koftwentytwo.com"' "${config_file}")
local ORG_CI_NAME
ORG_CI_NAME=$(yq '.org.ci_name // "Munitor CI"' "${config_file}")
local ORG_ORB_SLUG
ORG_ORB_SLUG=$(yq '.org.orb_slug // "kof22/munitor"' "${config_file}")
# Core pipeline settings
MUNITOR_PIPELINE=$(yq '.pipeline' "${config_file}")
MUNITOR_ORB_VERSION=$(yq '.orb_version // ""' "${config_file}")
MUNITOR_IMAGE_NAME=$(yq '.image_name // ""' "${config_file}")
MUNITOR_JAVA_VERSION=$(yq '.java_version // "21"' "${config_file}")
MUNITOR_SERVER_MODULE=$(yq '.server_module // ""' "${config_file}")
MUNITOR_NODE_VERSION=$(yq '.node_version // "20"' "${config_file}")
MUNITOR_SONAR_PROJECT_KEY=$(yq '.sonar.project_key // ""' "${config_file}")
MUNITOR_DOCKER_REGISTRY=$(yq '.docker.registry // ""' "${config_file}")
@@ -261,6 +267,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
@@ -282,7 +301,7 @@ extract_munitor_vars() {
fi
# Export all variables
export MUNITOR_PIPELINE MUNITOR_ORB_VERSION MUNITOR_IMAGE_NAME MUNITOR_JAVA_VERSION MUNITOR_NODE_VERSION
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_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
@@ -299,14 +318,15 @@ 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} ${MUNITOR_SERVER_MODULE}'
}
MUNITOR_EXTRACT_EOF
@@ -490,6 +510,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
@@ -538,6 +563,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 +607,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:
@@ -593,6 +626,331 @@ 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:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: main
##ENDIF_CD##
##IF_GITHUB_RELEASE##
- munitor/github_release:
name: github-release
requires:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: main
##ENDIF_GITHUB_RELEASE##
MUNITOR_TPL_EOF
cat > /tmp/munitor/templates/gradle-webapp.yml.tpl << 'MUNITOR_TPL_EOF'
version: 2.1
orbs:
munitor: ${MUNITOR_ORB_SLUG}@${MUNITOR_ORB_VERSION}
workflows:
pr-checks:
jobs:
- munitor/gradle_build_and_test:
name: build-and-test
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/sast_scan:
name: sast-scan
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/gradle_code_quality:
name: code-quality
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/gradle_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}"
owasp: ${MUNITOR_OWASP}
##IF_OWASP##
##IF_NVD##
context:
- ${MUNITOR_CONTEXT_NVD}
##ENDIF_NVD##
##ENDIF_OWASP##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
##IF_E2E##
- munitor/mvn_e2e_test:
name: e2e-tests
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
##ENDIF_E2E##
##INCLUDE_DEPLOY gradle-webapp-deploy develop develop ${MUNITOR_CD_ENV_DEVELOP}##
##INCLUDE_DEPLOY gradle-webapp-deploy staging staging ${MUNITOR_CD_ENV_STAGING}##
release-candidate:
jobs:
- munitor/gradle_build_and_test:
name: build-and-test
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /release\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
branches:
only:
- /release\/.*/
- munitor/sast_scan:
name: sast-scan
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
filters:
branches:
only:
- /release\/.*/
- munitor/gradle_code_quality:
name: code-quality
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
- munitor/gradle_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}"
owasp: ${MUNITOR_OWASP}
##IF_OWASP##
##IF_NVD##
context:
- ${MUNITOR_CONTEXT_NVD}
##ENDIF_NVD##
##ENDIF_OWASP##
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
##IF_E2E##
- munitor/mvn_e2e_test:
name: e2e-tests
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
##ENDIF_E2E##
##IF_SONAR##
- munitor/sonar_scan:
name: sonar-scan
java_version: "${MUNITOR_JAVA_VERSION}"
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
requires:
- build-and-test
- coverage
context:
- ${MUNITOR_CONTEXT_SONAR}
filters:
branches:
only:
- /release\/.*/
##ENDIF_SONAR##
- munitor/docker_build_push:
name: docker-build-push
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
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
- secrets-scan
- sast-scan
- security-scan
##IF_SONAR##
- sonar-scan
##ENDIF_SONAR##
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
context:
- ${MUNITOR_CONTEXT_REGISTRY}
filters:
branches:
only:
- /release\/.*/
##IF_SBOM##
- munitor/sbom:
name: sbom
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
requires:
- code-quality
- coverage
- secrets-scan
- sast-scan
- security-scan
##IF_SONAR##
- sonar-scan
##ENDIF_SONAR##
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
filters:
branches:
only:
- /release\/.*/
##ENDIF_SBOM##
##IF_CD##
- 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_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /release\/.*/
##ENDIF_CD##
##IF_GITHUB_RELEASE##
- munitor/github_release:
name: github-release
requires:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /release\/.*/
##ENDIF_GITHUB_RELEASE##
production:
jobs:
- munitor/gradle_build_and_test:
name: build-and-test
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: main
- munitor/docker_build_push:
name: docker-build-push
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
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:
- ${MUNITOR_CONTEXT_REGISTRY}
filters:
branches:
only: main
##IF_CD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
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 +1168,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 +1213,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 +1271,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 +1288,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 +1508,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 +1553,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 +1611,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 +1628,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 +2193,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 +2242,158 @@ 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:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_CD##
MUNITOR_PARTIAL_EOF
cat > /tmp/munitor/templates/partials/gradle-webapp-deploy.yml.tpl << 'MUNITOR_PARTIAL_EOF'
__WORKFLOW_NAME__:
jobs:
- munitor/gradle_build_and_test:
name: build-and-test
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: __BRANCH_FILTER__
- munitor/secrets_scan:
name: secrets-scan
filters:
branches:
only: __BRANCH_FILTER__
- munitor/sast_scan:
name: sast-scan
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
filters:
branches:
only: __BRANCH_FILTER__
- munitor/gradle_code_quality:
name: code-quality
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
- munitor/gradle_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}"
owasp: ${MUNITOR_OWASP}
##IF_OWASP##
##IF_NVD##
context:
- ${MUNITOR_CONTEXT_NVD}
##ENDIF_NVD##
##ENDIF_OWASP##
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
##IF_E2E##
- munitor/mvn_e2e_test:
name: e2e-tests
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_E2E##
##IF_SONAR##
- munitor/sonar_scan:
name: sonar-scan
java_version: "${MUNITOR_JAVA_VERSION}"
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
requires:
- build-and-test
- coverage
context:
- ${MUNITOR_CONTEXT_SONAR}
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_SONAR##
- munitor/docker_build_push:
name: docker-build-push
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
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
- secrets-scan
- sast-scan
- security-scan
##IF_SONAR##
- sonar-scan
##ENDIF_SONAR##
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
context:
- ${MUNITOR_CONTEXT_REGISTRY}
filters:
branches:
only: __BRANCH_FILTER__
##IF_SBOM##
- munitor/sbom:
name: sbom
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
requires:
- code-quality
- coverage
- secrets-scan
- sast-scan
- security-scan
##IF_SONAR##
- sonar-scan
##ENDIF_SONAR##
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_SBOM##
##IF_CD##
- 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_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
@@ -1961,6 +2498,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 +2541,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 +2648,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 +2691,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:
@@ -2234,7 +2783,7 @@ validate_required_fields() {
[[ -z "$(yq '.orb_version // ""' "${config}")" ]] && missing+=("orb_version")
case "${pipeline}" in
java-webapp|node-api|node-webapp)
java-webapp|gradle-webapp|node-api|node-webapp)
[[ -z "$(yq '.image_name // ""' "${config}")" ]] && missing+=("image_name")
[[ -z "$(yq '.docker.registry // ""' "${config}")" ]] && missing+=("docker.registry")
;;
@@ -2258,7 +2807,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 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
@@ -2362,7 +2911,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
+81 -9
View File
@@ -109,18 +109,24 @@ 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
ORG_DOCKER_REGISTRY=$(yq '.org.docker_registry // "ghcr.io/KofTwentyTwo"' "${config_file}")
local ORG_NPM_SCOPE
ORG_NPM_SCOPE=$(yq '.org.npm_scope // "@koftwentytwo"' "${config_file}")
local ORG_CI_EMAIL
ORG_CI_EMAIL=$(yq '.org.ci_email // "munitor-ci@koftwentytwo.com"' "${config_file}")
local ORG_CI_NAME
ORG_CI_NAME=$(yq '.org.ci_name // "Munitor CI"' "${config_file}")
local ORG_ORB_SLUG
ORG_ORB_SLUG=$(yq '.org.orb_slug // "kof22/munitor"' "${config_file}")
# Core pipeline settings
MUNITOR_PIPELINE=$(yq '.pipeline' "${config_file}")
MUNITOR_ORB_VERSION=$(yq '.orb_version // ""' "${config_file}")
MUNITOR_IMAGE_NAME=$(yq '.image_name // ""' "${config_file}")
MUNITOR_JAVA_VERSION=$(yq '.java_version // "21"' "${config_file}")
MUNITOR_SERVER_MODULE=$(yq '.server_module // ""' "${config_file}")
MUNITOR_NODE_VERSION=$(yq '.node_version // "20"' "${config_file}")
MUNITOR_SONAR_PROJECT_KEY=$(yq '.sonar.project_key // ""' "${config_file}")
MUNITOR_DOCKER_REGISTRY=$(yq '.docker.registry // ""' "${config_file}")
@@ -261,6 +267,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
@@ -282,7 +301,7 @@ extract_munitor_vars() {
fi
# Export all variables
export MUNITOR_PIPELINE MUNITOR_ORB_VERSION MUNITOR_IMAGE_NAME MUNITOR_JAVA_VERSION MUNITOR_NODE_VERSION
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_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
@@ -299,14 +318,15 @@ 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} ${MUNITOR_SERVER_MODULE}'
}
MUNITOR_EXTRACT_EOF
@@ -368,6 +388,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
@@ -454,6 +476,54 @@ ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+ExitOnOut
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
ENTRYPOINT ["sh", "-c", "java \$JAVA_OPTS -jar app.jar"]
DOCKERFILE
;;
gradle-webapp)
echo "Generating Dockerfile for gradle-webapp (java ${MUNITOR_JAVA_VERSION})"
# Find the shadow JAR in the server module's build output
SEARCH_DIR="build/libs"
if [[ -n "${MUNITOR_SERVER_MODULE:-}" ]]; then
SEARCH_DIR="${MUNITOR_SERVER_MODULE}/build/libs"
fi
APP_JAR=$(find "${SEARCH_DIR}" -maxdepth 1 -name "*.jar" \
! -name "*-plain.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" \
2>/dev/null | head -1) || true
if [[ -z "${APP_JAR}" ]]; then
echo "ERROR: No application JAR found in ${SEARCH_DIR}/" >&2
echo "Ensure shadowJar or bootJar is configured in build.gradle.kts" >&2
exit 1
fi
echo "Using JAR: ${APP_JAR}"
cat > Dockerfile <<DOCKERFILE
FROM eclipse-temurin:${MUNITOR_JAVA_VERSION}-jre-alpine
RUN apk update && apk upgrade --no-cache
RUN addgroup -g 1001 -S appgroup && \\
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
COPY ${APP_JAR} app.jar
RUN chown -R appuser:appgroup /app
USER appuser
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+ExitOnOutOfMemoryError"
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
@@ -469,6 +539,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}"
+319
View File
@@ -0,0 +1,319 @@
version: 2.1
orbs:
munitor: ${MUNITOR_ORB_SLUG}@${MUNITOR_ORB_VERSION}
workflows:
pr-checks:
jobs:
- munitor/gradle_build_and_test:
name: build-and-test
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/sast_scan:
name: sast-scan
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/gradle_code_quality:
name: code-quality
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/gradle_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}"
owasp: ${MUNITOR_OWASP}
##IF_OWASP##
##IF_NVD##
context:
- ${MUNITOR_CONTEXT_NVD}
##ENDIF_NVD##
##ENDIF_OWASP##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
##IF_E2E##
- munitor/mvn_e2e_test:
name: e2e-tests
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
##ENDIF_E2E##
##INCLUDE_DEPLOY gradle-webapp-deploy develop develop ${MUNITOR_CD_ENV_DEVELOP}##
##INCLUDE_DEPLOY gradle-webapp-deploy staging staging ${MUNITOR_CD_ENV_STAGING}##
release-candidate:
jobs:
- munitor/gradle_build_and_test:
name: build-and-test
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /release\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
branches:
only:
- /release\/.*/
- munitor/sast_scan:
name: sast-scan
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
filters:
branches:
only:
- /release\/.*/
- munitor/gradle_code_quality:
name: code-quality
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
- munitor/gradle_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}"
owasp: ${MUNITOR_OWASP}
##IF_OWASP##
##IF_NVD##
context:
- ${MUNITOR_CONTEXT_NVD}
##ENDIF_NVD##
##ENDIF_OWASP##
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
##IF_E2E##
- munitor/mvn_e2e_test:
name: e2e-tests
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
##ENDIF_E2E##
##IF_SONAR##
- munitor/sonar_scan:
name: sonar-scan
java_version: "${MUNITOR_JAVA_VERSION}"
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
requires:
- build-and-test
- coverage
context:
- ${MUNITOR_CONTEXT_SONAR}
filters:
branches:
only:
- /release\/.*/
##ENDIF_SONAR##
- munitor/docker_build_push:
name: docker-build-push
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
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
- secrets-scan
- sast-scan
- security-scan
##IF_SONAR##
- sonar-scan
##ENDIF_SONAR##
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
context:
- ${MUNITOR_CONTEXT_REGISTRY}
filters:
branches:
only:
- /release\/.*/
##IF_SBOM##
- munitor/sbom:
name: sbom
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
requires:
- code-quality
- coverage
- secrets-scan
- sast-scan
- security-scan
##IF_SONAR##
- sonar-scan
##ENDIF_SONAR##
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
filters:
branches:
only:
- /release\/.*/
##ENDIF_SBOM##
##IF_CD##
- 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_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /release\/.*/
##ENDIF_CD##
##IF_GITHUB_RELEASE##
- munitor/github_release:
name: github-release
requires:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /release\/.*/
##ENDIF_GITHUB_RELEASE##
production:
jobs:
- munitor/gradle_build_and_test:
name: build-and-test
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: main
- munitor/docker_build_push:
name: docker-build-push
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
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:
- ${MUNITOR_CONTEXT_REGISTRY}
filters:
branches:
only: main
##IF_CD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
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:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: main
##ENDIF_CD##
##IF_GITHUB_RELEASE##
- munitor/github_release:
name: github-release
requires:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: main
##ENDIF_GITHUB_RELEASE##
+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:
@@ -0,0 +1,146 @@
__WORKFLOW_NAME__:
jobs:
- munitor/gradle_build_and_test:
name: build-and-test
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: __BRANCH_FILTER__
- munitor/secrets_scan:
name: secrets-scan
filters:
branches:
only: __BRANCH_FILTER__
- munitor/sast_scan:
name: sast-scan
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
filters:
branches:
only: __BRANCH_FILTER__
- munitor/gradle_code_quality:
name: code-quality
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
- munitor/gradle_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}"
owasp: ${MUNITOR_OWASP}
##IF_OWASP##
##IF_NVD##
context:
- ${MUNITOR_CONTEXT_NVD}
##ENDIF_NVD##
##ENDIF_OWASP##
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
##IF_E2E##
- munitor/mvn_e2e_test:
name: e2e-tests
java_version: "${MUNITOR_JAVA_VERSION}"
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_E2E##
##IF_SONAR##
- munitor/sonar_scan:
name: sonar-scan
java_version: "${MUNITOR_JAVA_VERSION}"
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
requires:
- build-and-test
- coverage
context:
- ${MUNITOR_CONTEXT_SONAR}
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_SONAR##
- munitor/docker_build_push:
name: docker-build-push
image_name: ${MUNITOR_IMAGE_NAME}
registry: ${MUNITOR_DOCKER_REGISTRY}
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
- secrets-scan
- sast-scan
- security-scan
##IF_SONAR##
- sonar-scan
##ENDIF_SONAR##
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
context:
- ${MUNITOR_CONTEXT_REGISTRY}
filters:
branches:
only: __BRANCH_FILTER__
##IF_SBOM##
- munitor/sbom:
name: sbom
java_version: "${MUNITOR_JAVA_VERSION}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
requires:
- code-quality
- coverage
- secrets-scan
- sast-scan
- security-scan
##IF_SONAR##
- sonar-scan
##ENDIF_SONAR##
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_SBOM##
##IF_CD##
- 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_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
ci_git_email: '${MUNITOR_CI_EMAIL}'
ci_git_name: '${MUNITOR_CI_NAME}'
requires:
- docker-build-push
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_CD##
@@ -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)."
+32
View File
@@ -0,0 +1,32 @@
pipeline: gradle-webapp
orb_version: "0.1"
image_name: concilium
java_version: "21"
server_module: "concilium-server"
docker:
registry: ghcr.io/koftwentytwo
cd:
repo: KofTwentyTwo/Concilium-CD
format: kustomize
env:
develop: dev
coverage:
min_instruction: "50"
sast:
fail_on_findings: true
sbom: true
owasp: false
health:
path: /api/health
port: "8000"
contexts:
registry: ghcr
github: github
+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
+81
View File
@@ -263,6 +263,78 @@ 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}"
# --------------------------------------------------------------------------
# gradle-webapp fixture
# --------------------------------------------------------------------------
echo ""
echo "=== extract_munitor_vars: gradle-webapp fixture ==="
extract_munitor_vars "${FIXTURES_DIR}/gradle-webapp.munitor.yml"
assert_eq "pipeline type" "gradle-webapp" "${MUNITOR_PIPELINE}"
assert_eq "image_name" "concilium" "${MUNITOR_IMAGE_NAME}"
assert_eq "java_version" "21" "${MUNITOR_JAVA_VERSION}"
assert_eq "server_module" "concilium-server" "${MUNITOR_SERVER_MODULE}"
assert_eq "docker registry" "ghcr.io/koftwentytwo" "${MUNITOR_DOCKER_REGISTRY}"
assert_eq "cd repo" "KofTwentyTwo/Concilium-CD" "${MUNITOR_CD_REPO}"
assert_eq "cd flag" "true" "${MUNITOR_CD}"
assert_eq "cd format" "kustomize" "${MUNITOR_CD_FORMAT}"
assert_eq "cd env develop" "dev" "${MUNITOR_CD_ENV_DEVELOP}"
assert_eq "coverage min" "50" "${MUNITOR_COVERAGE_MIN}"
assert_eq "sbom" "true" "${MUNITOR_SBOM}"
assert_eq "owasp" "false" "${MUNITOR_OWASP}"
assert_eq "sast" "true" "${MUNITOR_SAST}"
assert_eq "sast fail_on_findings" "true" "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
assert_eq "health_path" "/api/health" "${MUNITOR_HEALTH_PATH}"
assert_eq "health_port" "8000" "${MUNITOR_HEALTH_PORT}"
assert_eq "context registry" "ghcr" "${MUNITOR_CONTEXT_REGISTRY}"
assert_eq "context github" "github" "${MUNITOR_CONTEXT_GITHUB}"
assert_eq "github_release flag" "true" "${MUNITOR_GITHUB_RELEASE}"
# --------------------------------------------------------------------------
# server_module defaults (empty when not set)
# --------------------------------------------------------------------------
echo ""
echo "=== extract_munitor_vars: server_module default ==="
extract_munitor_vars "${FIXTURES_DIR}/java-webapp.munitor.yml"
assert_eq "server_module default (empty)" "" "${MUNITOR_SERVER_MODULE}"
# --------------------------------------------------------------------------
# get_envsubst_vars includes MUNITOR_SERVER_MODULE
# --------------------------------------------------------------------------
echo -n " TEST: get_envsubst_vars includes MUNITOR_SERVER_MODULE... "
VARS_CHECK=$(get_envsubst_vars)
if echo "${VARS_CHECK}" | grep -q 'MUNITOR_SERVER_MODULE'; then
echo "PASS"
PASS=$((PASS + 1))
else
echo "FAIL"
FAIL=$((FAIL + 1))
fi
# --------------------------------------------------------------------------
# Missing config file
# --------------------------------------------------------------------------
@@ -326,6 +398,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 ==="
+133
View File
@@ -1080,6 +1080,139 @@ 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:"
# ==========================================================================
# gradle-webapp template rendering
# ==========================================================================
echo ""
echo "=== gradle-webapp Template Rendering Tests ==="
run_test "gradle-webapp: renders image_name" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"concilium"
run_test "gradle-webapp: renders java_version" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"java_version: \"21\""
run_test "gradle-webapp: uses gradle_build_and_test job" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"munitor/gradle_build_and_test"
run_test "gradle-webapp: uses gradle_code_quality job" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"munitor/gradle_code_quality"
run_test "gradle-webapp: uses gradle_coverage job" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"munitor/gradle_coverage"
run_negative_test "gradle-webapp: does not use mvn_build_and_test" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"munitor/mvn_build_and_test"
run_negative_test "gradle-webapp: does not use mvn_code_quality" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"munitor/mvn_code_quality"
run_negative_test "gradle-webapp: does not use mvn_coverage" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"munitor/mvn_coverage"
run_test "gradle-webapp: includes sast-scan" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"sast-scan"
run_test "gradle-webapp: includes sbom when enabled" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"sbom"
run_test "gradle-webapp: owasp disabled when false" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"owasp: false"
run_test "gradle-webapp: has cd repo update" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"update-cd-repo"
run_test "gradle-webapp: has release-candidate workflow" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"release-candidate:"
run_test "gradle-webapp: has production workflow" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"production:"
run_test "gradle-webapp: has pr-checks workflow" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"pr-checks:"
run_test "gradle-webapp: coverage min is 50" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
'min_instruction: "50"'
run_test "gradle-webapp: health_port is 8000" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
'health_port: "8000"'
run_negative_test "gradle-webapp: no e2e block (e2e not enabled)" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"e2e-tests"
run_negative_test "gradle-webapp: no sonar block (sonar not configured)" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"sonar-scan"
echo ""
echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
+283
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,154 @@ 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
# =============================================================================
# Test: Gradle scripts - structural tests
# =============================================================================
echo ""
echo "=== Gradle Scripts Tests ==="
for GRADLE_SCRIPT in gradle_build gradle_test gradle_jacoco gradle_checkstyle gradle_spotbugs gradle_pmd; do
SCRIPT_FILE="${SRC_SCRIPTS}/${GRADLE_SCRIPT}.sh"
echo -n " TEST: ${GRADLE_SCRIPT}.sh exists... "
if [[ -f "${SCRIPT_FILE}" ]]; then
pass
else
fail "file not found"
continue
fi
echo -n " TEST: ${GRADLE_SCRIPT}.sh is executable... "
if [[ -x "${SCRIPT_FILE}" ]]; then
pass
else
fail "not executable"
fi
echo -n " TEST: ${GRADLE_SCRIPT}.sh sources munitor_helpers.sh... "
if grep -q 'munitor_helpers.sh' "${SCRIPT_FILE}"; then
pass
else
fail "should source munitor_helpers.sh"
fi
echo -n " TEST: ${GRADLE_SCRIPT}.sh uses set -euo pipefail... "
if grep -q 'set -euo pipefail' "${SCRIPT_FILE}"; then
pass
else
fail "should use set -euo pipefail"
fi
echo -n " TEST: ${GRADLE_SCRIPT}.sh uses gradlew... "
if grep -q 'gradlew' "${SCRIPT_FILE}"; then
pass
else
fail "should use ./gradlew"
fi
done
echo -n " TEST: gradle_build.sh uses --no-daemon... "
if grep -q '\-\-no-daemon' "${SRC_SCRIPTS}/gradle_build.sh"; then
pass
else
fail "should use --no-daemon"
fi
echo -n " TEST: gradle_build.sh skips tests (-x test)... "
if grep -q '\-x test' "${SRC_SCRIPTS}/gradle_build.sh"; then
pass
else
fail "should skip tests with -x test"
fi
echo -n " TEST: gradle_jacoco.sh validates MIN_INSTRUCTION... "
if grep -q 'MIN_INSTRUCTION' "${SRC_SCRIPTS}/gradle_jacoco.sh"; then
pass
else
fail "should use MIN_INSTRUCTION"
fi
echo -n " TEST: gradle_jacoco.sh aggregates multi-module CSVs... "
if grep -q 'find' "${SRC_SCRIPTS}/gradle_jacoco.sh" && grep -q 'jacocoTestReport.csv' "${SRC_SCRIPTS}/gradle_jacoco.sh"; then
pass
else
fail "should find jacocoTestReport.csv files"
fi
echo -n " TEST: gradle_checkstyle.sh detects plugin availability... "
if grep -q 'tasks --all' "${SRC_SCRIPTS}/gradle_checkstyle.sh" && grep -q 'checkstyleMain' "${SRC_SCRIPTS}/gradle_checkstyle.sh"; then
pass
else
fail "should detect checkstyleMain task"
fi
echo -n " TEST: gradle_checkstyle.sh skips gracefully... "
if grep -q 'skipping\|Skipping\|skip\|not applied' "${SRC_SCRIPTS}/gradle_checkstyle.sh"; then
pass
else
fail "should skip gracefully when plugin not applied"
fi
echo -n " TEST: gradle_spotbugs.sh detects plugin availability... "
if grep -q 'tasks --all' "${SRC_SCRIPTS}/gradle_spotbugs.sh" && grep -q 'spotbugsMain' "${SRC_SCRIPTS}/gradle_spotbugs.sh"; then
pass
else
fail "should detect spotbugsMain task"
fi
echo -n " TEST: gradle_pmd.sh detects plugin availability... "
if grep -q 'tasks --all' "${SRC_SCRIPTS}/gradle_pmd.sh" && grep -q 'pmdMain' "${SRC_SCRIPTS}/gradle_pmd.sh"; then
pass
else
fail "should detect pmdMain task"
fi
# =============================================================================
# Summary
# =============================================================================