Skip to content

Commit 011e849

Browse files
[None][infra] PLC nightly pipeline update (#15274)
Signed-off-by: Yuanjing Xue <197832395+yuanjingx87@users.noreply.github.com> Co-authored-by: tburt-nv <195370667+tburt-nv@users.noreply.github.com>
1 parent 65bcb44 commit 011e849

4 files changed

Lines changed: 172 additions & 91 deletions

File tree

jenkins/TensorRT_LLM_PLC.groovy

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ def getLLMRepo () {
8585
LLM_REPO = DEFAULT_LLM_REPO
8686
}
8787
}
88+
if (params.repoUrlKey == "tensorrt_llm_github_sync") {
89+
withCredentials([string(credentialsId: 'github-llm-repo-sync', variable: 'GITHUB_LLM_REPO_SYNC')]) {
90+
LLM_REPO = GITHUB_LLM_REPO_SYNC
91+
}
92+
}
8893
if (params.repoUrlKey == "github_fork") {
8994
if (!isValidGithubUser(params.forkOwner)) {
9095
throw new Exception("Invalid fork owner provided")
@@ -121,6 +126,14 @@ def validateRef() {
121126
}
122127
}
123128

129+
def savePipelineScripts() {
130+
// Capture the pipeline's own version of the scan scripts before the workspace
131+
// is overwritten by checkoutSource() with params.ref. This ensures the script
132+
// logic always matches the Groovy that is actually running.
133+
checkout scm
134+
sh "cp -r jenkins/scripts/pulse_in_pipeline_scanning /tmp/pulse_in_pipeline_scanning"
135+
}
136+
124137
def checkoutSource ()
125138
{
126139
trtllm_utils.setupGitMirror()
@@ -131,19 +144,38 @@ def checkoutSource ()
131144
}
132145

133146
def getPulseToken(serviceId, scopes) {
147+
def maxRetries = 3
148+
def retryDelaySec = 10
134149
def token
135150
//Configure credential 'starfleet-client-id' under Jenkins Credential Manager
136151
withCredentials([usernamePassword(
137152
credentialsId: "NSPECT_CLIENT-prod",
138153
usernameVariable: 'SF_CLIENT_ID',
139154
passwordVariable: 'SF_CLIENT_SECRET'
140155
)]) {
141-
// Do not save AUTH_HEADER to a groovy variable since that
142-
// will expose the auth_header without being masked
143-
token= sh(script: """
144-
AUTH_HEADER=\$(echo -n \$SF_CLIENT_ID:\$SF_CLIENT_SECRET | base64 -w0)
145-
curl -s --request POST --header "Authorization: Basic \$AUTH_HEADER" --header "Content-Type: application/x-www-form-urlencoded" "https://${serviceId}.ssa.nvidia.com/token?grant_type=client_credentials&scope=${scopes}" | jq ".access_token" | tr -d '"'
146-
""", returnStdout: true).trim()
156+
for (int attempt = 1; attempt <= maxRetries; attempt++) {
157+
try {
158+
// Do not save AUTH_HEADER to a groovy variable since that
159+
// will expose the auth_header without being masked
160+
token = sh(script: """
161+
AUTH_HEADER=\$(echo -n \$SF_CLIENT_ID:\$SF_CLIENT_SECRET | base64 -w0)
162+
curl -s --request POST --header "Authorization: Basic \$AUTH_HEADER" --header "Content-Type: application/x-www-form-urlencoded" "https://${serviceId}.ssa.nvidia.com/token?grant_type=client_credentials&scope=${scopes}" | jq ".access_token" | tr -d '"'
163+
""", returnStdout: true).trim()
164+
} catch (Exception e) {
165+
echo "getPulseToken attempt ${attempt}/${maxRetries} failed: ${e.getMessage()}"
166+
token = ""
167+
}
168+
if (token && token != "null") {
169+
break
170+
}
171+
if (attempt < maxRetries) {
172+
echo "getPulseToken returned an empty token (attempt ${attempt}/${maxRetries}), retrying in ${retryDelaySec}s..."
173+
sleep(retryDelaySec)
174+
}
175+
}
176+
}
177+
if (!token || token == "null") {
178+
error("getPulseToken failed to obtain a valid token after ${maxRetries} attempts")
147179
}
148180
return token
149181
}
@@ -199,7 +231,7 @@ def sonarScan()
199231
{
200232
container("cpu") {
201233
def sonarScannerCliVer = "8.0.0.6341"
202-
sh "wget https://repo1.maven.org/maven2/org/sonarsource/scanner/cli/sonar-scanner-cli/${sonarScannerCliVer}/sonar-scanner-cli-${sonarScannerCliVer}.zip"
234+
sh "wget https://maven-central.storage-download.googleapis.com/maven2/org/sonarsource/scanner/cli/sonar-scanner-cli/${sonarScannerCliVer}/sonar-scanner-cli-${sonarScannerCliVer}.zip"
203235
sh "unzip sonar-scanner-cli-${sonarScannerCliVer}.zip"
204236
sh "mv sonar-scanner-${sonarScannerCliVer} ../sonar-scanner"
205237
sh "rm sonar-scanner-cli-${sonarScannerCliVer}.zip"
@@ -309,15 +341,22 @@ def processScanResults(ref) {
309341
python3 -m venv venv
310342
venv/bin/pip install requests elasticsearch==7.13.4
311343
"""
344+
def skipArgs = ""
345+
if (!params.runSourceCodeScanning) {
346+
skipArgs += " --skip-source-code"
347+
}
348+
if (!params.runContainerScanning) {
349+
skipArgs += " --skip-container"
350+
}
312351
def token = getPulseToken("4ubglassowmtsi7ogqwarmut7msn1q5ynts62fwnr1i", "public.api:read")
313352
def output = withEnv(["LICENSE_CHECK_TOKEN=${token}"]) {
314353
sh(script: """
315-
venv/bin/python ./jenkins/scripts/pulse_in_pipeline_scanning/main.py \
354+
venv/bin/python /tmp/pulse_in_pipeline_scanning/main.py \
316355
--build-url ${pipelineUrl} \
317356
--build-number ${env.BUILD_NUMBER} \
318357
--ref ${ref} \
319358
--report-directory ${pwd()}/scan_report \
320-
--scan-mode ${params.scanMode}
359+
--scan-mode ${params.scanMode}${skipArgs}
321360
""", returnStdout: true).trim()
322361
}
323362
echo "Scan result: ${output}"
@@ -342,11 +381,14 @@ pipeline {
342381
}
343382
parameters {
344383
string(name: 'ref', defaultValue: 'main', description: 'Branch name or commit SHA (7–40 hex chars) to check out; branch push steps are skipped when a commit SHA is provided')
345-
choice(name: 'repoUrlKey', choices: ['tensorrt_llm_github','tensorrt_llm_internal', 'github_fork'], description: "The repo url to process")
384+
choice(name: 'repoUrlKey', choices: ['tensorrt_llm_github','tensorrt_llm_internal', 'tensorrt_llm_github_sync', 'github_fork'], description: "The repo url to process")
346385
string(name: 'forkOwner', defaultValue: '', description: 'Name of the fork owner, need to select \"github_fork\" for repoUrlKey, otherwise it will be ignored')
347386
string(name: 'postMergePipelineName', defaultValue: '', description: 'Optional: post-merge pipeline job name to associate with this scan')
348387
string(name: 'postMergeBuildNumber', defaultValue: '', description: 'Optional: post-merge pipeline build number to associate with this scan')
349-
choice(name: 'scanMode', choices: ['monitor','release'], description: "When set to monitor, only report newly introduced dependencies. When set to release, will report all detected risks")
388+
choice(name: 'scanMode', choices: ['monitor','release','pre_merge'], description: "When set to monitor, only report newly introduced dependencies. When set to release, will report all detected risks")
389+
booleanParam(name: 'runSourceCodeScanning', defaultValue: true, description: 'Run Source Code OSS Scanning (lock file generation + Pulse OSS scan)')
390+
booleanParam(name: 'runContainerScanning', defaultValue: true, description: 'Run Container Scanning (Pulse container scan)')
391+
booleanParam(name: 'runSonarQube', defaultValue: true, description: 'Run SonarQube Code Analysis')
350392
}
351393
options {
352394
skipDefaultCheckout()
@@ -370,6 +412,7 @@ pipeline {
370412
steps {
371413
script {
372414
container("cpu") {
415+
savePipelineScripts()
373416
installTools()
374417
checkoutSource()
375418
validateRef()
@@ -380,6 +423,9 @@ pipeline {
380423
stage('Run TRT-LLM PLC Jobs') {
381424
parallel {
382425
stage("Source Code OSS Scanning") {
426+
when {
427+
expression { return params.runSourceCodeScanning }
428+
}
383429
steps {
384430
script {
385431
generateLockFiles(env.LLM_REPO, env.REF)
@@ -388,13 +434,19 @@ pipeline {
388434
}
389435
}
390436
stage("Container Scanning") {
437+
when {
438+
expression { return params.runContainerScanning }
439+
}
391440
steps {
392441
script {
393442
pulseScanContainer(env.LLM_REPO, env.REF)
394443
}
395444
}
396445
}
397446
stage("SonarQube Code Analysis") {
447+
when {
448+
expression { return params.runSonarQube }
449+
}
398450
steps {
399451
script {
400452
sonarScan()

jenkins/scripts/pulse_in_pipeline_scanning/main.py

Lines changed: 82 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,21 @@
3636
"if set to release, all risks will be reported"
3737
),
3838
)
39+
parser.add_argument(
40+
"--skip-source-code",
41+
action="store_true",
42+
default=False,
43+
help="Skip processing source code scanning results",
44+
)
45+
parser.add_argument(
46+
"--skip-container",
47+
action="store_true",
48+
default=False,
49+
help="Skip processing container scanning results",
50+
)
3951
args = parser.parse_args()
4052

41-
LICENSE_CHECK_TOKEN = os.environ.get("LICENSE_CHECK_TOKEN")
53+
LICENSE_CHECK_TOKEN = os.environ.get("LICENSE_CHECK_TOKEN", "")
4254
if not LICENSE_CHECK_TOKEN:
4355
raise EnvironmentError("Error: Environment variable 'LICENSE_CHECK_TOKEN' is not set!")
4456

@@ -59,83 +71,85 @@
5971

6072
def process_result():
6173
RISKY_DEPENDENCIES = []
62-
63-
last_source_vulns = get_last_scan_results("source_code_vulnerability", args.ref)
64-
source_vulns = submit_source_code_vulns(
65-
os.path.join(args.report_directory, "source_code/vulns.json"),
66-
last_source_vulns,
67-
**SUBMIT_KWARG,
68-
)
69-
if len(source_vulns) > 0:
70-
RISKY_DEPENDENCIES.append(f"{len(source_vulns)} new source code vulnerability")
71-
72-
last_source_licenses = get_last_scan_results("source_code_license", args.ref)
73-
source_licenses = submit_source_code_licenses(
74-
os.path.join(args.report_directory, "source_code/sbom.json"),
75-
last_source_licenses,
76-
**SUBMIT_KWARG,
77-
license_check_token=LICENSE_CHECK_TOKEN,
78-
)
79-
if source_licenses is None:
80-
RISKY_DEPENDENCIES.append("source code SBOM not found")
81-
sbom_missing = True
82-
else:
83-
sbom_missing = False
84-
if len(source_licenses) > 0:
74+
source_licenses = []
75+
count_container_licenses = 0
76+
77+
if not args.skip_source_code:
78+
last_source_vulns = get_last_scan_results("source_code_vulnerability", args.ref)
79+
source_vulns = submit_source_code_vulns(
80+
os.path.join(args.report_directory, "source_code/vulns.json"),
81+
last_source_vulns,
82+
**SUBMIT_KWARG,
83+
)
84+
if len(source_vulns) > 0:
85+
RISKY_DEPENDENCIES.append(f"{len(source_vulns)} new source code vulnerability")
86+
87+
last_source_licenses = get_last_scan_results("source_code_license", args.ref)
88+
source_licenses = submit_source_code_licenses(
89+
os.path.join(args.report_directory, "source_code/sbom.json"),
90+
last_source_licenses,
91+
**SUBMIT_KWARG,
92+
license_check_token=LICENSE_CHECK_TOKEN,
93+
)
94+
if source_licenses is None:
95+
RISKY_DEPENDENCIES.append("source code SBOM not found")
96+
else:
97+
if len(source_licenses) > 0:
98+
RISKY_DEPENDENCIES.append(
99+
f"{len(source_licenses)} new source code non-permissive license"
100+
)
101+
102+
if not args.skip_container:
103+
last_container_vulns = get_last_scan_results("container_vulnerability", args.ref)
104+
amd64_container_vulns = submit_container_vulns(
105+
os.path.join(args.report_directory, "release_amd64/vulns.json"),
106+
os.path.join(args.report_directory, "base_amd64/vulns.json"),
107+
"amd64",
108+
last_container_vulns,
109+
**SUBMIT_KWARG,
110+
)
111+
arm64_container_vulns = submit_container_vulns(
112+
os.path.join(args.report_directory, "release_arm64/vulns.json"),
113+
os.path.join(args.report_directory, "base_arm64/vulns.json"),
114+
"arm64",
115+
last_container_vulns,
116+
**SUBMIT_KWARG,
117+
)
118+
count_container_vulns = len(amd64_container_vulns + arm64_container_vulns)
119+
if count_container_vulns > 0:
120+
RISKY_DEPENDENCIES.append(f"{count_container_vulns} new container vulnerability")
121+
122+
last_container_licenses = get_last_scan_results("container_license", args.ref)
123+
amd64_container_licenses = submit_container_licenses(
124+
os.path.join(args.report_directory, "release_amd64/licenses.json"),
125+
os.path.join(args.report_directory, "base_amd64/licenses.json"),
126+
"amd64",
127+
last_container_licenses,
128+
**SUBMIT_KWARG,
129+
license_check_token=LICENSE_CHECK_TOKEN,
130+
)
131+
arm64_container_licenses = submit_container_licenses(
132+
os.path.join(args.report_directory, "release_arm64/licenses.json"),
133+
os.path.join(args.report_directory, "base_arm64/licenses.json"),
134+
"arm64",
135+
last_container_licenses,
136+
**SUBMIT_KWARG,
137+
license_check_token=LICENSE_CHECK_TOKEN,
138+
)
139+
count_container_licenses = len(amd64_container_licenses + arm64_container_licenses)
140+
if count_container_licenses > 0:
85141
RISKY_DEPENDENCIES.append(
86-
f"{len(source_licenses)} new source code non-permissive license"
142+
f"{count_container_licenses} new container non-permissive license"
87143
)
88144

89-
last_container_vulns = get_last_scan_results("container_vulnerability", args.ref)
90-
amd64_container_vulns = submit_container_vulns(
91-
os.path.join(args.report_directory, "release_amd64/vulns.json"),
92-
os.path.join(args.report_directory, "base_amd64/vulns.json"),
93-
"amd64",
94-
last_container_vulns,
95-
**SUBMIT_KWARG,
96-
)
97-
arm64_container_vulns = submit_container_vulns(
98-
os.path.join(args.report_directory, "release_arm64/vulns.json"),
99-
os.path.join(args.report_directory, "base_arm64/vulns.json"),
100-
"arm64",
101-
last_container_vulns,
102-
**SUBMIT_KWARG,
103-
)
104-
count_container_vulns = len(amd64_container_vulns + arm64_container_vulns)
105-
if count_container_vulns > 0:
106-
RISKY_DEPENDENCIES.append(f"{count_container_vulns} new container vulnerability")
107-
108-
last_container_licenses = get_last_scan_results("container_license", args.ref)
109-
amd64_container_licenses = submit_container_licenses(
110-
os.path.join(args.report_directory, "release_amd64/licenses.json"),
111-
os.path.join(args.report_directory, "base_amd64/licenses.json"),
112-
"amd64",
113-
last_container_licenses,
114-
**SUBMIT_KWARG,
115-
license_check_token=LICENSE_CHECK_TOKEN,
116-
)
117-
arm64_container_licenses = submit_container_licenses(
118-
os.path.join(args.report_directory, "release_arm64/licenses.json"),
119-
os.path.join(args.report_directory, "base_arm64/licenses.json"),
120-
"arm64",
121-
last_container_licenses,
122-
**SUBMIT_KWARG,
123-
license_check_token=LICENSE_CHECK_TOKEN,
124-
)
125-
count_container_licenses = len(amd64_container_licenses + arm64_container_licenses)
126-
if count_container_licenses > 0:
127-
RISKY_DEPENDENCIES.append(
128-
f"{count_container_licenses} new container non-permissive license"
129-
)
130-
131145
if RISKY_DEPENDENCIES:
132146
detail = ", ".join(RISKY_DEPENDENCIES)
133147
status = "unstable"
134148
if args.scan_mode == "monitor":
135149
post_slack_msg(args.build_number, args.ref, detail)
136150
if (
137151
args.scan_mode == "release"
138-
and not sbom_missing
152+
and source_licenses is not None
139153
and count_container_licenses + len(source_licenses) == 0
140154
):
141155
status = "success"

jenkins/scripts/pulse_in_pipeline_scanning/submit_report.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
sys.path.append(os.path.abspath(".."))
99
from utils.common import is_permissive, load_json
1010
from utils.es import es_post
11-
from utils.report import diff_licenses, get_preapproved_deps_map, get_vulns
11+
from utils.report import diff_licenses, get_preapproved_deps_map, get_vulns, is_preapproved
1212

1313
ES_POST_URL = os.environ.get("TRTLLM_ES_POST_URL")
1414
if not ES_POST_URL:
@@ -74,9 +74,7 @@ def submit_source_code_vulns(
7474
risks_to_report.append(doc)
7575

7676
bulk_documents.append(doc)
77-
78-
vulnerability_count = len(bulk_documents)
79-
if vulnerability_count:
77+
if bulk_documents:
8078
_, errors = es_post(ES_POST_URL, bulk_documents)
8179
if errors:
8280
raise RuntimeError(
@@ -156,9 +154,11 @@ def submit_source_code_licenses(
156154
"s_component_type": component.get("type"),
157155
"b_is_new": is_new,
158156
}
159-
if (is_new or not only_report_new_risk) and (package_name not in map_preapproved):
157+
if (is_new or not only_report_new_risk) and not is_preapproved(
158+
map_preapproved, package_name, (component.get("type") or "unknown").lower()
159+
):
160160
risks_to_report.append(doc)
161-
sbom_documents.append(doc)
161+
sbom_documents.append(doc)
162162
if sbom_documents:
163163
_, sbom_errors = es_post(ES_POST_URL, sbom_documents)
164164
if sbom_errors:
@@ -290,9 +290,11 @@ def submit_container_licenses(
290290
"s_license_ids": ",".join(license_ids),
291291
"b_is_new": is_new,
292292
}
293-
if (is_new or not only_report_new_risk) and (package_name not in map_preapproved):
293+
if (is_new or not only_report_new_risk) and not is_preapproved(
294+
map_preapproved, package_name, (v.get("type") or "unknown").lower()
295+
):
294296
risks_to_report.append(doc)
295-
docs.append(doc)
297+
docs.append(doc)
296298
if docs:
297299
_, errors = es_post(ES_POST_URL, docs)
298300
if errors:

0 commit comments

Comments
 (0)