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/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..b5878c5 --- /dev/null +++ b/src/commands/gradle_test.yml @@ -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 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..cb51d22 --- /dev/null +++ b/src/jobs/gradle_build_and_test.yml @@ -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: <> + - 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/collect_gradle_test_results.sh b/src/scripts/collect_gradle_test_results.sh new file mode 100755 index 0000000..3b6cc0b --- /dev/null +++ b/src/scripts/collect_gradle_test_results.sh @@ -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}/" 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..f28a751 --- /dev/null +++ b/src/scripts/gradle_jacoco.sh @@ -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}%)." 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..914a1a5 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,330 @@ 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: + - ${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/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## @@ -1921,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: @@ -2298,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") ;; @@ -2322,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 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 <