Jenkins Learn
06. Tích hợp công cụ

Maven & Gradle

Thiết kế Jenkins build Java tái lập bằng Maven hoặc Gradle: wrapper, cache có ranh giới, report, artifact và credential tối thiểu.

Phạm vi và giả định runtime

Hướng dẫn này dùng Jenkins LTS, Pipeline: Declarative, Pipeline: Basic Steps, JUnit plugin, Git và agent Linux có JDK đã được phê duyệt. junit do JUnit plugin cung cấp; archiveArtifacts là Pipeline step. Maven/Gradle version, JDK, wrapper distribution, mirror, agent label và plugin cần được xác minh bằng controller sandbox cùng ma trận hỗ trợ của đội; snippet không khẳng định chúng đã chạy ở runtime này.

Một build Java tin cậy phải xác định được JDK nào, build tool nào, dependency nào và bytes artifact nào đã được dùng. Maven Wrapper hoặc Gradle Wrapper đưa tool version vào source; Jenkins điều phối checkout, agent, report, artifact và ranh giới credential. Cache chỉ là tối ưu hiệu năng, không phải nguồn chuẩn của dependency hay artifact phát hành.

Mục lục

Mục tiêu và luồng build

Sau bài này, bạn có thể chọn wrapper/toolchain phù hợp, tách cache của PR khỏi release, thu report đúng đường dẫn và archive artifact có checksum. Bạn cũng có thể giải thích vì sao test/scan phải hoàn tất trước khi credential publish được bind, và vì sao Jenkins fingerprint không thay SHA-256.

direction: down
A: "SCM revision đã chọn"
B: "Agent có JDK đã pin"
C: "Maven Wrapper hoặc Gradle\nWrapper"
D: "Cache/mirror theo trust tier"
E: "Compile, test và scan"
F: "JUnit XML"
G: "Artifact + SHA-256 + metadata"
H: "Jenkins junit"
I: "archiveArtifacts + fingerprint"
J: "Trusted branch và policy pass?" {
  shape: diamond
}
K: "Publish credential scope hẹp"
L: "Giữ evidence, không publish"

A -> B
B -> C
C -> D
D -> E
E -> F
E -> G
F -> H
G -> I
I -> J
J -> K: "Có"
J -> L: "Không"
SCM revision đã chọnAgent có JDK đã pinMaven Wrapper hoặc GradleWrapperCache/mirror theo trust tierCompile, test và scanJUnit XMLArtifact + SHA-256 + metadataJenkins junitarchiveArtifacts + fingerprintTrusted branch và policy pass?Publish credential scope hẹpGiữ evidence, không publish CóKhông
SCM revision đã chọnAgent có JDK đã pinMaven Wrapper hoặc GradleWrapperCache/mirror theo trust tierCompile, test và scanJUnit XMLArtifact + SHA-256 + metadataJenkins junitarchiveArtifacts + fingerprintTrusted branch và policy pass?Publish credential scope hẹpGiữ evidence, không publish CóKhông
Mục tiêu và luồng build

Pipeline không nên rebuild dependency hay artifact ở stage publish. Build once có nghĩa là compile/test/package một lần trên input đã pin; publish/promotion chỉ dùng output đã verify. Nếu build cần dùng plugin/agent/runtime khác, ghi chúng vào metadata/evidence thay vì đoán từ console log.

Cài đặt và pin toolchain

Wrapper và Jenkins Global Tool Configuration

Cách chọn toolƯu điểmRủi ro/cần kiểm soátKhi dùng
Maven Wrapper ./mvnwVersion Maven và URL distribution nằm trong SCM; developer/CI cùng entry point.Wrapper JAR/properties là supply-chain input; distribution vẫn cần checksum, TLS/mirror và review.Mặc định cho repository Maven.
Gradle Wrapper ./gradlewVersion Gradle được pin trong gradle/wrapper/gradle-wrapper.properties; cùng command local/CI.Không chạy wrapper không review; distribution URL/checksum và executable bit cần được kiểm.Mặc định cho repository Gradle.
Jenkins Global Tool ConfigurationPlatform có thể quản trị JDK/Maven/Groovy tool install chung, auto-install hoặc image agent.Tool name là config runtime; auto-install có thể tải network và làm drift nếu không pin/mirror.Tool chung đã được platform pin và test trên agent.

Wrapper không tự pin JDK. Pin JDK bằng image agent digest, Jenkins tool installation version hoặc đường dẫn tool được quản trị; dùng cùng major/vendor cho developer và CI khi project yêu cầu. Không dựa vào java mặc định của host hay để agent tự tải distribution ngoài policy trong mỗi build.

Với wrapper, kiểm executable bit (test -x ./mvnw hoặc test -x ./gradlew) trước build. Đừng sửa bằng chmod +x trong Pipeline production vì điều đó che nguồn checkout/permission sai. Sửa mode trong SCM và review diff. Jenkins Global Tool Configuration có thể là lựa chọn hợp lệ, nhưng phải ghi rõ tên tool/version, source/mirror, owner, cache location và rollback version; nó không thay wrapper lock trong source.

JDK, tool và dependency tái lập

Tái lập không đồng nghĩa bytes luôn giống tuyệt đối trên mọi OS/timezone/toolchain, nhưng bắt đầu từ input kiểm soát được. Giữ các thông tin sau trong source hoặc release metadata:

  • JDK vendor, major version và architecture của agent; compiler target/source/release của project;
  • Maven Wrapper hoặc Gradle Wrapper version cùng distribution URL/checksum theo cơ chế tool;
  • Maven pom.xml, dependency/plugin version và lock/checksum policy khi tổ chức dùng; Gradle version catalog/lock file, dependency verification metadata và plugin version;
  • repository mirror, CA/truststore và profile không nhạy cảm cần để resolve; không ghi credential vào pom.xml, build.gradle hay wrapper properties;
  • revision SCM, command build, artifact coordinate/version, SHA-256 và SBOM/provenance theo policy.

-B làm Maven chạy batch mode; -ntp giảm transfer progress log, không phải security control. --no-daemon khiến Gradle CI không giữ daemon sau build; nó không pin Gradle/JDK hoặc tách cache. Đặt timeout ở Pipeline và để exit code non-zero làm stage fail; không bọc test bắt buộc bằng retry để tạo build xanh giả.

Dependency cache có ranh giới

Cache Maven và Gradle

Maven mặc định dùng local repository ~/.m2/repository; Gradle mặc định dùng GRADLE_USER_HOME. Hai location đó trên agent dùng chung có thể làm branch/PR khác ghi dependency, metadata hoặc plugin vào cùng cache. Dùng cache per-job/per-branch, hoặc cache read-only đã được trusted build tạo và kiểm checksum/lock trước khi dùng.

ToolCache phân vùng đơn giảnLưu ý
Maven-Dmaven.repo.local="$WORKSPACE/.ci-cache/m2"Workspace cache mất khi agent ephemeral; không share write giữa PR và release.
GradleGRADLE_USER_HOME="$WORKSPACE/.ci-cache/gradle" ./gradlew --no-daemon ...Bao gồm cache dependency/plugin; dùng path cố định trong workspace, không dùng home chung của OS.
Cache trung tâm read-onlyMirror/cache service do platform quản trị, ACL và checksum rõConsumer không có quyền ghi; repository/proxy vẫn cần integrity policy và audit.

Cache per-workspace dễ tái lập nhưng làm download lại khi workspace bị dọn. Cache shared có thể nhanh hơn nhưng chỉ phù hợp khi key gồm tool/JDK/OS/lockfile và writer đã trusted; PR không tin cậy không được ghi vào cache release. Không cache settings.xml có secret, token, file credential, private key, .env, report chứa secret hoặc artifact release như thể đó là dependency.

Mirror, credential và verification

Dependency mirror giảm egress và tăng availability khi được vận hành như supply-chain service: TLS/CA hợp lệ, namespace allowlist, audit, retention, quarantine và owner. Maven có thể nhận mirror/server credential qua settings.xml; Gradle có repository credential/configuration tương đương. Credential đọc mirror tách khỏi credential publish và chỉ có quyền read namespace cần thiết.

Khi mirror cần secret, binding chỉ bao command resolve/build ở trusted lane phù hợp. Không truyền password qua -D command-line, URL, --password, console hay Gradle properties committed. PR/fork nên dùng public/mirror read-only lane không chứa capability nội bộ nhạy cảm; nếu không thể đáp ứng, không chạy PR với credential đó.

Integrity không đến từ cache hit. Maven checksum policy, dependency/plugin pinning và repository checksum cần được owner chọn; Gradle dependency locking và dependency verification có thể phát hiện thay đổi bất ngờ khi project đã cấu hình chúng. Cơ chế nào cũng cần update workflow reviewable: cập nhật lock/verification metadata trong PR riêng, verify source/mirror, rồi pin revision. Không xóa toàn cache để “sửa” mismatch trước khi giữ evidence và tìm producer.

Offline và cache poisoning

--offline của Maven và --offline của Gradle chỉ dùng dependency đã có local cache; chúng không chứng minh cache đầy đủ hoặc đúng. Offline build có thể fail nếu plugin, metadata, POM/module, transitive dependency hoặc dynamic version chưa tồn tại. Dùng offline mode trong test tái lập sau khi prime cache có kiểm soát, không làm fallback im lặng khi mirror/TLS/credential bị lỗi.

Cache poisoning xảy ra khi bytes/metadata không tin cậy được ghi vào cache mà release sau tin dùng. Giảm rủi ro bằng cache per trust tier, write access tối thiểu, dependency/checksum verification, no shared writable home, cleanup/TTL và re-resolve từ mirror trusted khi có incident. Một cache volume name, Jenkins label hoặc folder không tự tạo boundary; OS identity, mount permission, agent isolation và network policy mới quyết định ai ghi được.

Test reports và failure semantics

Maven Surefire và Failsafe

Maven Surefire thường chạy unit test ở phase test và tạo XML tại target/surefire-reports/. Maven Failsafe thường chạy integration test ở integration-test/verify và tạo XML tại target/failsafe-reports/. verify là command hữu ích khi project đã bind Failsafe: nó cho phép cleanup/post-integration rồi fail ở phase verify; test không chạy Failsafe lifecycle đó.

./mvnw -B -ntp \
  -Dmaven.repo.local="$WORKSPACE/.ci-cache/m2" \
  verify

Đường dẫn report là convention plugin, không phải bằng chứng mọi project dùng đúng cấu hình này. Project có thể đổi reportsDirectory, disable XML hoặc dùng framework/plugin khác. Luôn kiểm pom.xml và output thực tế; junit phải trỏ đúng XML đã được tạo, không trỏ glob rộng toàn workspace.

Gradle JUnit XML

Gradle task test thường ghi JUnit XML dưới build/test-results/test/. check chạy task validation mà project đã wired, thường bao gồm test; nó có thể khác giữa repository. Command CI cơ bản, không dùng daemon, là:

GRADLE_USER_HOME="$WORKSPACE/.ci-cache/gradle" \
  ./gradlew --no-daemon clean test

Nếu project có integration test, task/report path phải được build script định nghĩa rõ, ví dụ một integrationTest source set sẽ không tự dùng cùng path test. Không giả định XML của custom task; inspect build.gradle/build.gradle.kts và test trên agent sandbox.

Jenkins JUnit step

junit publish XML đã có vào Jenkins và hiển thị failure trend. Nó không chạy Maven/Gradle, không thay test gate và không tự tạo report. JUnit plugin và storage/retention build phải tồn tại trên controller. Publish report trong post { always } của stage test để vẫn có evidence khi test fail, nhưng chọn allowEmptyResults có chủ đích:

Tình huốngallowEmptyResultsLý do
Unit test bắt buộc luôn phải tạo XMLfalseMissing report là lỗi cấu hình/build rõ ràng.
Report phụ trợ/chỉ có ở profile tùy chọntrue, kèm evidence policy riêngTránh che lỗi gốc; không diễn giải missing report là pass.
Build fail trước khi test runner khởi độngTheo policy stage/reportGiữ failure gốc và điều tra compile/agent/tool trước.

Không dùng catchError bao test bắt buộc rồi archive/publish tiếp như release hợp lệ. sh trả exit code khác 0 phải làm Pipeline fail trừ khi policy đã phân loại rõ; xem Xử lý lỗi và Retry.

Artifact bất biến và archive Jenkins

Artifact release cần version/coordinate bất biến, SHA-256/SHA-512, metadata source/toolchain và SBOM/provenance khi policy yêu cầu. Tạo checksum của file đóng gói cuối rồi archive theo allowlist hẹp. archiveArtifacts lưu evidence theo retention Jenkins; artifact repository là nguồn distribution riêng. fingerprint: true hỗ trợ nối build producer/consumer bằng MD5 lịch sử, nên không thay checksum mật mã, chữ ký hoặc validation dependency.

Dữ liệuDùng đểKhông được chứa
JAR/WAR/packageConsumer hoặc promotion resolve version bất biếnSecret, config production, private key.
SHA-256 sidecarVerify bytes sau archive/downloadToken hoặc URL ký ngắn hạn.
Test XMLEvidence quality theo buildSecret từ test fixture/log.
SBOM/metadataProvenance, dependency inventory, toolchain/revisionCredential, raw environment, request header.
Jenkins fingerprintTruy vết nội bộ giữa buildBằng chứng integrity mật mã.

archiveArtifacts cần path tương đối workspace. Dùng pattern hẹp như target/catalog-api-1.4.0.jar hoặc build/libs/catalog-api-1.4.0.jar, không archive **/*. allowEmptyArchive: false phù hợp output release bắt buộc. Thiết kế buildDiscarder/retention riêng cho log/report Jenkins, cache và repository release; controller không phải kho bytes vô hạn. Xem Build Artifacts và Artifact repositories với Jenkins.

Jenkinsfile tham khảo

Pipeline Maven

Mẫu này giả định Multibranch Pipeline, main được bảo vệ và chỉ trusted source mới có quyền merge vào đó, wrapper Maven executable, JDK/pin agent đã chuẩn bị, Git/JUnit plugin và agent Linux. Các file target/catalog-api-1.4.0.jar và target/catalog-api-1.4.0.jar.sha256 là contract minh họa của project; thay bằng coordinate/path thật đã review. Pipeline không có URL repository, token hay publish command: publish phải là stage trusted riêng, sau gate artifact, với credential publish chỉ tồn tại trong closure tối thiểu.

agent none cùng skipDefaultCheckout(true) ngăn implicit checkout trên mọi agent. Stage build checkout revision SCM đã được Jenkins chọn cho run, ghi git rev-parse HEAD cạnh artifact rồi chỉ stash JAR, checksum và revision đó. Stage publish chỉ được cấp agent sau when { beforeAgent true; branch 'main' }; tại workspace trusted mới, nó checkout lại SCM của cùng run, xác minh GIT_COMMIT, HEAD và revision đã stash khớp nhau, rồi mới unstash/gọi script. Vì vậy không chép toàn source từ lane build/PR sang release workspace, không rebuild artifact, và fail closed nếu revision hay script không đúng.

pipeline {
  agent none

  options {
    skipDefaultCheckout(true)
    timeout(time: 35, unit: 'MINUTES')
    buildDiscarder(logRotator(
      daysToKeepStr: '30', numToKeepStr: '30',
      artifactDaysToKeepStr: '14', artifactNumToKeepStr: '10'
    ))
  }

  stages {
    stage('Build and test Maven') {
      agent { label 'linux && jdk17' }
      steps {
        checkout scm
        sh '''#!/bin/sh
          set -eu
          test -x ./mvnw
          ./mvnw -B -ntp \
            -Dmaven.repo.local="$WORKSPACE/.ci-cache/m2" \
            verify
          test -s target/catalog-api-1.4.0.jar
          sha256sum target/catalog-api-1.4.0.jar \
            > target/catalog-api-1.4.0.jar.sha256
          (cd target && sha256sum -c catalog-api-1.4.0.jar.sha256)
          git rev-parse --verify HEAD > target/release-input.git-commit
          test -s target/release-input.git-commit
        '''
        stash(
          name: 'maven-verified-output',
          includes: 'target/catalog-api-1.4.0.jar,target/catalog-api-1.4.0.jar.sha256,target/release-input.git-commit',
          useDefaultExcludes: true
        )
      }
      post {
        always {
          junit(
            testResults: 'target/surefire-reports/*.xml,target/failsafe-reports/*.xml',
            allowEmptyResults: false
          )
        }
      }
    }

    stage('Archive verified evidence') {
      agent { label 'linux && jdk17' }
      steps {
        unstash 'maven-verified-output'
        sh '''#!/bin/sh
          set -eu
          test -s target/catalog-api-1.4.0.jar
          test -s target/catalog-api-1.4.0.jar.sha256
          test -s target/release-input.git-commit
          (cd target && sha256sum -c catalog-api-1.4.0.jar.sha256)
        '''
        archiveArtifacts(
          artifacts: 'target/catalog-api-1.4.0.jar,target/catalog-api-1.4.0.jar.sha256,target/release-input.git-commit',
          allowEmptyArchive: false,
          defaultExcludes: true,
          fingerprint: true
        )
      }
    }

    stage('Publish capability after trusted gate') {
      when {
        beforeAgent true
        branch 'main'
      }
      agent { label 'trusted-release-linux && jdk17' }
      steps {
        // Chỉ chạy sau branch gate; checkout source trusted của revision run hiện tại.
        checkout scm
        unstash 'maven-verified-output'
        sh '''#!/bin/sh
          set -eu
          test -x ./ci/publish-verified-maven-artifact
          test -s target/catalog-api-1.4.0.jar
          test -s target/release-input.git-commit
          (cd target && sha256sum -c catalog-api-1.4.0.jar.sha256)
          release_input_revision="$(cat target/release-input.git-commit)"
          checkout_revision="$(git rev-parse --verify HEAD)"
          test -n "${GIT_COMMIT:-}"
          test "$GIT_COMMIT" = "$checkout_revision"
          test "$release_input_revision" = "$checkout_revision"
        '''
        withCredentials([
          file(credentialsId: 'maven-release-settings', variable: 'MAVEN_SETTINGS')
        ]) {
          sh '''#!/bin/sh
            set -eu
            set +x
            # Contract nội bộ: script publish đúng bytes đã verify;
            # MAVEN_SETTINGS là path file tạm, không phải giá trị secret.
            ./ci/publish-verified-maven-artifact \
              --settings "$MAVEN_SETTINGS" \
              --artifact target/catalog-api-1.4.0.jar \
              --checksum target/catalog-api-1.4.0.jar.sha256
          '''
        }
      }
    }
  }
}

Ví dụ cố ý đặt test/package ngoài withCredentials. maven-release-settings chỉ bao publisher sau branch gate, checkout source trusted và checksum/revision verification. stash chỉ chuyển package/checksum/revision đã verify giữa các stage của cùng run; mỗi stage unstash rồi verify SHA-256 lại, nên không giả định workspace agent còn tồn tại. Publish stage dùng checkout scm độc lập để có ./ci/publish-verified-maven-artifact; nó không dùng source từ stash và không gọi Maven để rebuild. git rev-parse HEAD, GIT_COMMIT do Git plugin đặt sau checkout, và release-input.git-commit phải giống nhau; mismatch hoặc thiếu biến làm fail trước credential. Artifact repository vẫn là nguồn distribution lâu dài. Không bind mirror-read credential và release-publish credential trong cùng block.

Tương đương Gradle

Thay stage Maven bằng command Gradle cố định, không nội suy parameter vào shell. Cần điều chỉnh artifact/report path theo build.gradle/build.gradle.kts của project:

stage('Build and test Gradle') {
  agent { label 'linux && jdk17' }
  steps {
    checkout scm
    sh '''#!/bin/sh
      set -eu
      test -x ./gradlew
      GRADLE_USER_HOME="$WORKSPACE/.ci-cache/gradle" \
        ./gradlew --no-daemon clean test
      test -s build/libs/catalog-api-1.4.0.jar
      sha256sum build/libs/catalog-api-1.4.0.jar \
        > build/libs/catalog-api-1.4.0.jar.sha256
      (cd build/libs && sha256sum -c catalog-api-1.4.0.jar.sha256)
    '''
  }
  post {
    always {
      junit(
        testResults: 'build/test-results/test/*.xml',
        allowEmptyResults: false
      )
    }
  }
}

Để tạo package release, project Gradle có thể cần task jar, bootJar, assemble, check hoặc task custom. Không gọi clean test là universal release command. Nếu publish bằng Gradle, giữ task publish trong stage trusted, bind config/credential file trong closure ngắn, và không đưa password vào -P, environment dump hay report. Đọc Jenkinsfile để đối chiếu syntax với controller.

Lab local tái lập không cần Maven Central

Fixture và kiểm tra layout

Lab chỉ tạo wrapper marker, JUnit XML giả, artifact text và checksum trong temp directory. Nó không chạy Maven/Gradle/JDK, không tải wrapper distribution, không gọi network, không tạo credential và không archive Jenkins. Cần Bash, mktemp, python3, sha256sum, dirname và rm.

#!/usr/bin/env bash
set -euo pipefail
umask 077

LAB_PARENT="$(cd -P -- "${TMPDIR:-/tmp}" && pwd)"
LAB_PREFIX='jenkins-maven-gradle-lab.'
LAB_ROOT="$(mktemp -d "${LAB_PARENT}/${LAB_PREFIX}XXXXXX")"
LAB_MARKER="$LAB_ROOT/.lab-owned-marker"

case "$LAB_ROOT" in
  "$LAB_PARENT"/"$LAB_PREFIX"*) ;;
  *) printf 'Refuse unexpected lab path.\n' >&2; exit 1 ;;
esac
LAB_DIRECT_PARENT="$(dirname -- "$LAB_ROOT")"
LAB_DIRECT_PARENT_REAL="$(cd -P -- "$LAB_DIRECT_PARENT" && pwd)"
if [ "$LAB_DIRECT_PARENT_REAL" != "$LAB_PARENT" ]; then
  printf 'Refuse non-child lab path.\n' >&2
  exit 1
fi
printf '%s\n' 'maven-gradle-lab-v1' > "$LAB_MARKER"

mkdir -p \
  "$LAB_ROOT/maven/target/surefire-reports" \
  "$LAB_ROOT/maven/target/failsafe-reports" \
  "$LAB_ROOT/gradle/build/test-results/test" \
  "$LAB_ROOT/gradle/build/libs"
printf '#!/bin/sh\nexit 0\n' > "$LAB_ROOT/maven/mvnw"
printf '#!/bin/sh\nexit 0\n' > "$LAB_ROOT/gradle/gradlew"
chmod 0755 "$LAB_ROOT/maven/mvnw" "$LAB_ROOT/gradle/gradlew"
printf '<testsuite name="unit" tests="1" failures="0"/>\n' \
  > "$LAB_ROOT/maven/target/surefire-reports/TEST-unit.xml"
printf '<testsuite name="integration" tests="1" failures="0"/>\n' \
  > "$LAB_ROOT/maven/target/failsafe-reports/TEST-integration.xml"
printf '<testsuite name="gradle" tests="1" failures="0"/>\n' \
  > "$LAB_ROOT/gradle/build/test-results/test/TEST-demo.xml"
printf 'synthetic jar bytes\n' > "$LAB_ROOT/gradle/build/libs/catalog-api-1.4.0.jar"
(
  cd "$LAB_ROOT/gradle/build/libs"
  sha256sum catalog-api-1.4.0.jar > catalog-api-1.4.0.jar.sha256
  sha256sum -c catalog-api-1.4.0.jar.sha256
)

python3 - "$LAB_ROOT" <<'PY'
from pathlib import Path
import sys
import xml.etree.ElementTree as ET

root = Path(sys.argv[1])
assert (root / 'maven/mvnw').stat().st_mode & 0o111
assert (root / 'gradle/gradlew').stat().st_mode & 0o111
reports = [
    root / 'maven/target/surefire-reports/TEST-unit.xml',
    root / 'maven/target/failsafe-reports/TEST-integration.xml',
    root / 'gradle/build/test-results/test/TEST-demo.xml',
]
for report in reports:
    assert ET.parse(report).getroot().tag == 'testsuite'
assert (root / 'gradle/build/libs/catalog-api-1.4.0.jar.sha256').is_file()
print('fixture layout and JUnit XML: PASS')
print('This is a static layout check; Maven, Gradle, JDK, Jenkins, and network were not run.')
PY

case "${LAB_ROOT:-}" in
  "${LAB_PARENT}"/"${LAB_PREFIX}"*) ;;
  *) printf 'Refuse cleanup: unexpected lab directory.\n' >&2; exit 1 ;;
esac
if [ "$(dirname -- "$LAB_ROOT")" != "$LAB_PARENT" ] || \
   [ "$(cd -P -- "$(dirname -- "$LAB_ROOT")" && pwd)" != "$LAB_PARENT" ] || \
   [ ! -f "$LAB_MARKER" ] || \
   [ "$(cat -- "$LAB_MARKER")" != 'maven-gradle-lab-v1' ]; then
  printf 'Refuse cleanup: direct-parent or marker guard failed.\n' >&2
  exit 1
fi
cd / || exit 1
rm -rf -- "$LAB_ROOT"
printf 'guarded cleanup: PASS\n'

Kết quả và giới hạn runtime

Kết quả đúng gồm checksum OK, fixture layout and JUnit XML: PASS, thông báo static layout, rồi guarded cleanup: PASS. Cleanup chỉ chạy khi path có prefix, parent canonical là LAB_PARENT và marker khớp. Không thay LAB_ROOT bằng workspace Jenkins, JENKINS_HOME, cache volume hay path do người dùng nhập.

Lab chứng minh layout report/artifact và guard shell, không phải Maven/Gradle build. Nếu runtime thiếu JDK, wrapper executable, Maven/Gradle distribution, JUnit plugin hoặc Jenkins agent label, ghi đó là limitation runtime và chạy static checks/lab này trước; không report rằng dependency/test/Pipeline đã chạy thành công.

Troubleshooting

Triệu chứngEvidence cần xemHành động an toàn
./mvnw: Permission denied hoặc ./gradlew: Permission deniedMode file trong SCM/checkouts, agent filesystem và script pathSửa executable bit trong SCM, kiểm checkout; không chmod tạm để che revision lỗi.
Wrapper tải/fail distributionWrapper properties, distribution checksum/URL, mirror, CA/TLS, egress và tool versionVerify source/mirror trên sandbox; không tắt TLS hoặc đổi sang binary không pin.
Dependency resolve/cache mismatchLock/verification metadata, POM/build script, mirror audit, cache trust tier và checksumDừng release, giữ evidence, isolate/clear đúng cache tier sau điều tra; không dùng cache shared writable từ PR.
Offline build failDependency/plugin/metadata có thật trong local cache, task/profile và prior prime jobPrime cache bằng trusted lane hoặc cho phép mirror theo policy; không gọi offline là reliable fallback.
Không thấy Surefire/Failsafe/Gradle XMLpom.xml/build script, task lifecycle, reports directory, test runner log và glob junitSửa report config/path; không bật allowEmptyResults để gọi test là pass.
Build xanh nhưng report không có testjunit configuration, report contents, stage result và policy required testFail/check gate khi report bắt buộc thiếu; review test task thay vì chỉ dashboard màu.
Artifact thiếu hoặc checksum mismatchOutput path, package version, SHA-256 sidecar, archive/download metadataDừng publish, rebuild từ input đã review hoặc fetch immutable artifact theo policy; không overwrite release.
Tool/JDK mismatch giữa local và CIjava -version, wrapper version, tool config, image digest/agent label và compiler targetPin/cập nhật ma trận toolchain, test sandbox và ghi metadata; không đổi global tool giữa build để thử.
Publish credential lộ vào test/cacheBinding scope, Jenkinsfile diff, agent/process isolation, archive/cache globsRotate nếu cần, tách compile/test/scan khỏi publish closure và review cache/artifact retention.

Checklist trước khi áp dụng

  • Maven Wrapper hoặc Gradle Wrapper, JDK, agent image/tool configuration và dependency/plugin version được pin/review theo policy.
  • Wrapper executable bit, distribution source/checksum, CA/TLS, mirror và offline behavior đã được thử trên sandbox tương ứng.
  • Cache Maven/Gradle phân theo job/branch/trust tier hoặc read-only trusted; PR không ghi vào cache release/shared home.
  • Cache không có secret, credential file, private key, .env, artifact release hay report nhạy cảm; cache lifecycle/quota/owner rõ ràng.
  • Mirror read credential tách release publisher; test/compile/scan hoàn tất trước credential publish và PR không nhận capability nhạy cảm.
  • Maven report Surefire/Failsafe hoặc Gradle JUnit XML path đã đối chiếu project; junit behavior khi report thiếu được chọn có chủ đích.
  • Test failure, timeout và missing required report vẫn chặn gate; retry không làm test/scan xanh giả.
  • Artifact có version/digest bất biến, SHA-256/SBOM/metadata theo policy; fingerprint chỉ là truy vết MD5 bổ sung.
  • archiveArtifacts dùng allowlist path, retention/quota rõ, không archive toàn workspace/secret/file binding.
  • Publish chỉ chạy branch trusted sau verification, credential scope hẹp, không qua argv/log/URL và artifact không bị rebuild trước publish.
  • Lab local dùng fixture tạm, marker, prefix và canonical direct-parent guard; evidence lab không bị gọi là runtime Maven/Gradle/Jenkins evidence.

Nguồn chính thức

Đọc tiếp

On this page