From 4a98d2b63fc58a6843d0b172fe306451ce03901e Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 22 Mar 2026 11:08:06 -0500 Subject: [PATCH 1/4] 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. --- src/commands/gradle_build.yml | 6 + src/commands/gradle_checkstyle.yml | 6 + src/commands/gradle_jacoco.yml | 13 + src/commands/gradle_pmd.yml | 6 + src/commands/gradle_spotbugs.yml | 6 + src/commands/gradle_test.yml | 17 + src/commands/restore_gradle_cache.yml | 7 + src/commands/save_gradle_cache.yml | 8 + src/jobs/gradle_build_and_test.yml | 45 +++ src/jobs/gradle_code_quality.yml | 27 ++ src/jobs/gradle_coverage.yml | 29 ++ src/scripts/extract_munitor_vars.sh | 20 +- src/scripts/generate_config.sh | 4 +- src/scripts/generate_dockerfile.sh | 48 +++ src/scripts/gradle_build.sh | 30 ++ src/scripts/gradle_checkstyle.sh | 34 ++ src/scripts/gradle_jacoco.sh | 67 ++++ src/scripts/gradle_pmd.sh | 34 ++ src/scripts/gradle_spotbugs.sh | 34 ++ src/scripts/gradle_test.sh | 28 ++ src/scripts/packed_generate_config.sh | 36 +- src/scripts/packed_generate_dockerfile.sh | 76 ++++- src/scripts/templates/gradle-webapp.yml.tpl | 319 ++++++++++++++++++ .../partials/gradle-webapp-deploy.yml.tpl | 146 ++++++++ tests/fixtures/gradle-webapp.munitor.yml | 32 ++ tests/test_extract_munitor_vars.sh | 48 +++ tests/test_generate_config.sh | 82 +++++ tests/test_scripts.sh | 102 ++++++ 28 files changed, 1281 insertions(+), 29 deletions(-) create mode 100644 src/commands/gradle_build.yml create mode 100644 src/commands/gradle_checkstyle.yml create mode 100644 src/commands/gradle_jacoco.yml create mode 100644 src/commands/gradle_pmd.yml create mode 100644 src/commands/gradle_spotbugs.yml create mode 100644 src/commands/gradle_test.yml create mode 100644 src/commands/restore_gradle_cache.yml create mode 100644 src/commands/save_gradle_cache.yml create mode 100644 src/jobs/gradle_build_and_test.yml create mode 100644 src/jobs/gradle_code_quality.yml create mode 100644 src/jobs/gradle_coverage.yml create mode 100755 src/scripts/gradle_build.sh create mode 100755 src/scripts/gradle_checkstyle.sh create mode 100755 src/scripts/gradle_jacoco.sh create mode 100755 src/scripts/gradle_pmd.sh create mode 100755 src/scripts/gradle_spotbugs.sh create mode 100755 src/scripts/gradle_test.sh create mode 100644 src/scripts/templates/gradle-webapp.yml.tpl create mode 100644 src/scripts/templates/partials/gradle-webapp-deploy.yml.tpl create mode 100644 tests/fixtures/gradle-webapp.munitor.yml diff --git a/src/commands/gradle_build.yml b/src/commands/gradle_build.yml new file mode 100644 index 0000000..393eca0 --- /dev/null +++ b/src/commands/gradle_build.yml @@ -0,0 +1,6 @@ +description: > + Run Gradle build (compile + package, skip tests). +steps: + - run: + name: Gradle build + command: << include(scripts/gradle_build.sh) >> diff --git a/src/commands/gradle_checkstyle.yml b/src/commands/gradle_checkstyle.yml new file mode 100644 index 0000000..8d894f8 --- /dev/null +++ b/src/commands/gradle_checkstyle.yml @@ -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) >> diff --git a/src/commands/gradle_jacoco.yml b/src/commands/gradle_jacoco.yml new file mode 100644 index 0000000..3682494 --- /dev/null +++ b/src/commands/gradle_jacoco.yml @@ -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) >> diff --git a/src/commands/gradle_pmd.yml b/src/commands/gradle_pmd.yml new file mode 100644 index 0000000..6cb14ae --- /dev/null +++ b/src/commands/gradle_pmd.yml @@ -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) >> diff --git a/src/commands/gradle_spotbugs.yml b/src/commands/gradle_spotbugs.yml new file mode 100644 index 0000000..1a842b9 --- /dev/null +++ b/src/commands/gradle_spotbugs.yml @@ -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) >> diff --git a/src/commands/gradle_test.yml b/src/commands/gradle_test.yml new file mode 100644 index 0000000..d93d1f1 --- /dev/null +++ b/src/commands/gradle_test.yml @@ -0,0 +1,17 @@ +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: | + mkdir -p /tmp/test-results + find . -path "*/build/test-results/test/*.xml" -exec cp {} /tmp/test-results/ \; + when: always + - store_test_results: + path: /tmp/test-results + - store_artifacts: + path: /tmp/test-results + destination: test-results diff --git a/src/commands/restore_gradle_cache.yml b/src/commands/restore_gradle_cache.yml new file mode 100644 index 0000000..66a52da --- /dev/null +++ b/src/commands/restore_gradle_cache.yml @@ -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- diff --git a/src/commands/save_gradle_cache.yml b/src/commands/save_gradle_cache.yml new file mode 100644 index 0000000..6b77c5a --- /dev/null +++ b/src/commands/save_gradle_cache.yml @@ -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 diff --git a/src/jobs/gradle_build_and_test.yml b/src/jobs/gradle_build_and_test.yml new file mode 100644 index 0000000..4dd2a27 --- /dev/null +++ b/src/jobs/gradle_build_and_test.yml @@ -0,0 +1,45 @@ +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: | + mkdir -p /tmp/test-results + find . -path "*/build/test-results/test/*.xml" -exec cp {} /tmp/test-results/ \; + when: always + - store_test_results: + path: /tmp/test-results + - run: + name: Persist version metadata + command: <> + - save_gradle_cache + - persist_to_workspace: + root: . + paths: + - "." diff --git a/src/jobs/gradle_code_quality.yml b/src/jobs/gradle_code_quality.yml new file mode 100644 index 0000000..e966824 --- /dev/null +++ b/src/jobs/gradle_code_quality.yml @@ -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 diff --git a/src/jobs/gradle_coverage.yml b/src/jobs/gradle_coverage.yml new file mode 100644 index 0000000..8b50aa0 --- /dev/null +++ b/src/jobs/gradle_coverage.yml @@ -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 >> diff --git a/src/scripts/extract_munitor_vars.sh b/src/scripts/extract_munitor_vars.sh index 673e22c..5d0e19c 100644 --- a/src/scripts/extract_munitor_vars.sh +++ b/src/scripts/extract_munitor_vars.sh @@ -13,17 +13,23 @@ extract_munitor_vars() { fi # --- Org defaults (overridable via .munitor.yml org section) --- - local ORG_DOCKER_REGISTRY=$(yq '.org.docker_registry // "ghcr.io/KofTwentyTwo"' "${config_file}") - local ORG_NPM_SCOPE=$(yq '.org.npm_scope // "@koftwentytwo"' "${config_file}") - local ORG_CI_EMAIL=$(yq '.org.ci_email // "munitor-ci@koftwentytwo.com"' "${config_file}") - local ORG_CI_NAME=$(yq '.org.ci_name // "Munitor CI"' "${config_file}") - local ORG_ORB_SLUG=$(yq '.org.orb_slug // "kof22/munitor"' "${config_file}") + 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}") @@ -198,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 @@ -223,5 +229,5 @@ extract_munitor_vars() { # Build the envsubst variable list get_envsubst_vars() { # shellcheck disable=SC2016 - echo '${MUNITOR_PIPELINE} ${MUNITOR_ORB_VERSION} ${MUNITOR_IMAGE_NAME} ${MUNITOR_JAVA_VERSION} ${MUNITOR_NODE_VERSION} ${MUNITOR_SONAR_PROJECT_KEY} ${MUNITOR_DOCKER_REGISTRY} ${MUNITOR_CD_REPO} ${MUNITOR_CD_FORMAT} ${MUNITOR_CD_ENV_DEVELOP} ${MUNITOR_CD_ENV_STAGING} ${MUNITOR_CD_ENV_PROD} ${MUNITOR_CD_ENV_RELEASE} ${MUNITOR_COVERAGE_MIN} ${MUNITOR_E2E} ${MUNITOR_SBOM} ${MUNITOR_OWASP} ${MUNITOR_CONTEXT_REGISTRY} ${MUNITOR_CONTEXT_GITHUB} ${MUNITOR_CONTEXT_SONAR} ${MUNITOR_CONTEXT_NVD} ${MUNITOR_NPM_AUTH} ${MUNITOR_NPM_SCOPES} ${MUNITOR_SERVICES_JSON} ${MUNITOR_TEST_SETUP_SCRIPT} ${MUNITOR_TEST_COMMANDS_JSON} ${MUNITOR_COVERAGE_TOOL} ${MUNITOR_COVERAGE_COMMAND} ${MUNITOR_GITHUB_RELEASE} ${MUNITOR_SAST} ${MUNITOR_SAST_FAIL_ON_FINDINGS} ${MUNITOR_HEALTH_PATH} ${MUNITOR_HEALTH_PORT} ${MUNITOR_HEALTH_DB} ${MUNITOR_HEALTH_MIGRATIONS} ${MUNITOR_HEALTH_SMOKE_PATHS} ${MUNITOR_TF_PATH} ${MUNITOR_TF_LIVE_PATH} ${MUNITOR_TF_ENVIRONMENTS} ${MUNITOR_CHECKOV_SKIP} ${MUNITOR_KUSTOMIZE_VERSION} ${MUNITOR_KUSTOMIZE_BASE_PATH} ${MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR} ${MUNITOR_KUSTOMIZE_SCAN_OVERLAY} ${MUNITOR_KUSTOMIZE_OVERLAYS} ${MUNITOR_KUSTOMIZE_OVERLAY_DIR} ${MUNITOR_KUBE_LINTER_CONFIG} ${MUNITOR_YAMLLINT_PATHS} ${MUNITOR_CI_EMAIL} ${MUNITOR_CI_NAME} ${MUNITOR_NPM_DEFAULT_SCOPE} ${MUNITOR_ORB_SLUG} ${MUNITOR_PACKAGE_MANAGER} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}' + 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}' } diff --git a/src/scripts/generate_config.sh b/src/scripts/generate_config.sh index 2d2b5ee..6991086 100755 --- a/src/scripts/generate_config.sh +++ b/src/scripts/generate_config.sh @@ -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 supplemental_images" +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 diff --git a/src/scripts/generate_dockerfile.sh b/src/scripts/generate_dockerfile.sh index cd75f53..14dcae5 100755 --- a/src/scripts/generate_dockerfile.sh +++ b/src/scripts/generate_dockerfile.sh @@ -141,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 </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." diff --git a/src/scripts/gradle_checkstyle.sh b/src/scripts/gradle_checkstyle.sh new file mode 100755 index 0000000..60a546e --- /dev/null +++ b/src/scripts/gradle_checkstyle.sh @@ -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." diff --git a/src/scripts/gradle_jacoco.sh b/src/scripts/gradle_jacoco.sh new file mode 100755 index 0000000..8281d4e --- /dev/null +++ b/src/scripts/gradle_jacoco.sh @@ -0,0 +1,67 @@ +#!/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 + +# 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 "ERROR: No JaCoCo CSV reports found." + echo "Ensure the jacoco plugin is configured in build.gradle with csv report enabled." + exit 1 +fi + +# 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}%)." diff --git a/src/scripts/gradle_pmd.sh b/src/scripts/gradle_pmd.sh new file mode 100755 index 0000000..bf5804e --- /dev/null +++ b/src/scripts/gradle_pmd.sh @@ -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." diff --git a/src/scripts/gradle_spotbugs.sh b/src/scripts/gradle_spotbugs.sh new file mode 100755 index 0000000..4b73dd2 --- /dev/null +++ b/src/scripts/gradle_spotbugs.sh @@ -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." diff --git a/src/scripts/gradle_test.sh b/src/scripts/gradle_test.sh new file mode 100755 index 0000000..ff800d5 --- /dev/null +++ b/src/scripts/gradle_test.sh @@ -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." diff --git a/src/scripts/packed_generate_config.sh b/src/scripts/packed_generate_config.sh index 902d2b1..074e4f6 100755 --- a/src/scripts/packed_generate_config.sh +++ b/src/scripts/packed_generate_config.sh @@ -110,17 +110,23 @@ extract_munitor_vars() { fi # --- Org defaults (overridable via .munitor.yml org section) --- - local ORG_DOCKER_REGISTRY=$(yq '.org.docker_registry // "ghcr.io/KofTwentyTwo"' "${config_file}") - local ORG_NPM_SCOPE=$(yq '.org.npm_scope // "@koftwentytwo"' "${config_file}") - local ORG_CI_EMAIL=$(yq '.org.ci_email // "munitor-ci@koftwentytwo.com"' "${config_file}") - local ORG_CI_NAME=$(yq '.org.ci_name // "Munitor CI"' "${config_file}") - local ORG_ORB_SLUG=$(yq '.org.orb_slug // "kof22/munitor"' "${config_file}") + 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,9 +267,13 @@ 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 '.supplemental_images // [] | tojson' "${config_file}") + 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="[]" @@ -291,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 @@ -308,7 +318,7 @@ 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 } @@ -316,7 +326,7 @@ extract_munitor_vars() { # 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} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}' + 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 @@ -500,6 +510,8 @@ 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## @@ -595,6 +607,8 @@ 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## @@ -2298,7 +2312,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") ;; @@ -2322,7 +2336,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 supplemental_images" +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 diff --git a/src/scripts/packed_generate_dockerfile.sh b/src/scripts/packed_generate_dockerfile.sh index d468367..39312ab 100755 --- a/src/scripts/packed_generate_dockerfile.sh +++ b/src/scripts/packed_generate_dockerfile.sh @@ -110,17 +110,23 @@ extract_munitor_vars() { fi # --- Org defaults (overridable via .munitor.yml org section) --- - local ORG_DOCKER_REGISTRY=$(yq '.org.docker_registry // "ghcr.io/KofTwentyTwo"' "${config_file}") - local ORG_NPM_SCOPE=$(yq '.org.npm_scope // "@koftwentytwo"' "${config_file}") - local ORG_CI_EMAIL=$(yq '.org.ci_email // "munitor-ci@koftwentytwo.com"' "${config_file}") - local ORG_CI_NAME=$(yq '.org.ci_name // "Munitor CI"' "${config_file}") - local ORG_ORB_SLUG=$(yq '.org.orb_slug // "kof22/munitor"' "${config_file}") + 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,9 +267,13 @@ 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 '.supplemental_images // [] | tojson' "${config_file}") + 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="[]" @@ -291,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 @@ -308,7 +318,7 @@ 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 } @@ -316,7 +326,7 @@ extract_munitor_vars() { # 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} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}' + 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 @@ -466,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 < Date: Sun, 22 Mar 2026 11:11:08 -0500 Subject: [PATCH 2/4] 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). --- src/commands/gradle_test.yml | 4 +--- src/jobs/gradle_build_and_test.yml | 4 +--- src/scripts/collect_gradle_test_results.sh | 13 +++++++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) create mode 100755 src/scripts/collect_gradle_test_results.sh diff --git a/src/commands/gradle_test.yml b/src/commands/gradle_test.yml index d93d1f1..b5878c5 100644 --- a/src/commands/gradle_test.yml +++ b/src/commands/gradle_test.yml @@ -6,9 +6,7 @@ steps: command: << include(scripts/gradle_test.sh) >> - run: name: Collect test results - command: | - mkdir -p /tmp/test-results - find . -path "*/build/test-results/test/*.xml" -exec cp {} /tmp/test-results/ \; + command: << include(scripts/collect_gradle_test_results.sh) >> when: always - store_test_results: path: /tmp/test-results diff --git a/src/jobs/gradle_build_and_test.yml b/src/jobs/gradle_build_and_test.yml index 4dd2a27..cb51d22 100644 --- a/src/jobs/gradle_build_and_test.yml +++ b/src/jobs/gradle_build_and_test.yml @@ -29,9 +29,7 @@ steps: - gradle_test - run: name: Collect test results - command: | - mkdir -p /tmp/test-results - find . -path "*/build/test-results/test/*.xml" -exec cp {} /tmp/test-results/ \; + command: << include(scripts/collect_gradle_test_results.sh) >> when: always - store_test_results: path: /tmp/test-results diff --git a/src/scripts/collect_gradle_test_results.sh b/src/scripts/collect_gradle_test_results.sh new file mode 100755 index 0000000..57e8d1d --- /dev/null +++ b/src/scripts/collect_gradle_test_results.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Collect Gradle multi-module JUnit XML test results into a single directory +# for CircleCI's store_test_results step. + +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}/" -- 2.54.0 From 4cac900995e990e8c9924ced930a2561c72d698f Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 22 Mar 2026 11:35:40 -0500 Subject: [PATCH 3/4] 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. --- scripts/pack_generate_config.sh | 4 +- src/scripts/collect_gradle_test_results.sh | 13 +- src/scripts/packed_generate_config.sh | 471 +++++++++++++++++++++ 3 files changed, 484 insertions(+), 4 deletions(-) diff --git a/scripts/pack_generate_config.sh b/scripts/pack_generate_config.sh index 0f44057..e76415f 100755 --- a/scripts/pack_generate_config.sh +++ b/scripts/pack_generate_config.sh @@ -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' diff --git a/src/scripts/collect_gradle_test_results.sh b/src/scripts/collect_gradle_test_results.sh index 57e8d1d..3b6cc0b 100755 --- a/src/scripts/collect_gradle_test_results.sh +++ b/src/scripts/collect_gradle_test_results.sh @@ -1,8 +1,17 @@ #!/usr/bin/env bash set -euo pipefail -# Collect Gradle multi-module JUnit XML test results into a single directory -# for CircleCI's store_test_results step. +# 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}" diff --git a/src/scripts/packed_generate_config.sh b/src/scripts/packed_generate_config.sh index 074e4f6..914a1a5 100755 --- a/src/scripts/packed_generate_config.sh +++ b/src/scripts/packed_generate_config.sh @@ -652,6 +652,328 @@ workflows: ##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: + - 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/node-api.yml.tpl << 'MUNITOR_TPL_EOF' version: 2.1 @@ -1935,6 +2257,155 @@ cat > /tmp/munitor/templates/partials/java-webapp-deploy.yml.tpl << 'MUNITOR_PAR ##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: + - docker-build-push + context: + - ${MUNITOR_CONTEXT_GITHUB} + filters: + branches: + only: __BRANCH_FILTER__ + ##ENDIF_CD## +MUNITOR_PARTIAL_EOF + cat > /tmp/munitor/templates/partials/node-api-deploy.yml.tpl << 'MUNITOR_PARTIAL_EOF' __WORKFLOW_NAME__: jobs: -- 2.54.0 From 30ae8c43c434d7d211ed91b05849b4703b7fae2d Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 22 Mar 2026 16:04:48 -0500 Subject: [PATCH 4/4] 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. --- src/scripts/gradle_jacoco.sh | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/scripts/gradle_jacoco.sh b/src/scripts/gradle_jacoco.sh index 8281d4e..f28a751 100755 --- a/src/scripts/gradle_jacoco.sh +++ b/src/scripts/gradle_jacoco.sh @@ -33,15 +33,39 @@ 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 "Ensure the jacoco plugin is configured in build.gradle with csv report enabled." + 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})" -- 2.54.0