Skip to content

Commit f42a6cb

Browse files
authored
[None][infra] Add source code pulse scan to PLC nightly pipeline (#10961)
Signed-off-by: Yuanjing Xue <197832395+yuanjingx87@users.noreply.github.com>
1 parent 5d7411e commit f42a6cb

File tree

3 files changed

+179
-19
lines changed

3 files changed

+179
-19
lines changed

examples/scaffolding/contrib/mcp/weather/uv.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jenkins/TensorRT_LLM_PLC.groovy

Lines changed: 111 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ def createKubernetesPodConfig()
1414
apiVersion: v1
1515
kind: Pod
1616
spec:
17-
qosClass: Guaranteed
1817
nodeSelector: ${selectors}
1918
containers:
2019
- name: alpine
@@ -31,10 +30,27 @@ def createKubernetesPodConfig()
3130
memory: 32Gi
3231
ephemeral-storage: 200Gi
3332
imagePullPolicy: Always
33+
- name: docker
34+
image: urm.nvidia.com/sw-tensorrt-docker/tensorrt-llm:202505221445_docker_dind_withbash
35+
tty: true
36+
resources:
37+
requests:
38+
cpu: 16
39+
memory: 72Gi
40+
ephemeral-storage: 200Gi
41+
limits:
42+
cpu: 16
43+
memory: 256Gi
44+
ephemeral-storage: 200Gi
45+
imagePullPolicy: Always
46+
securityContext:
47+
privileged: true
48+
capabilities:
49+
add:
50+
- SYS_ADMIN
3451
qosClass: Guaranteed
3552
""".stripIndent(),
3653
]
37-
3854
return podConfig
3955
}
4056

@@ -58,24 +74,34 @@ def checkoutSource ()
5874
{
5975
def LLM_REPO = getLLMRepo()
6076
sh "git config --global --add safe.directory ${env.WORKSPACE}"
61-
sh "git config --global user.email \"90828364+tensorrt-cicd@users.noreply.github.com\""
62-
sh "git config --global user.name \"TensorRT LLM\""
6377
trtllm_utils.checkoutSource(LLM_REPO, params.branchName, env.WORKSPACE, false, true)
6478
}
6579

66-
def generate()
67-
{
68-
sh "pwd && ls -alh"
80+
def getPulseToken() {
81+
def token
82+
//Configure credential 'starfleet-client-id' under Jenkins Credential Manager
83+
withCredentials([usernamePassword(
84+
credentialsId: "NSPECT_CLIENT-prod",
85+
usernameVariable: 'SF_CLIENT_ID',
86+
passwordVariable: 'SF_CLIENT_SECRET'
87+
)]) {
88+
def AuthHeader = sh(script: "set +x && echo -n $SF_CLIENT_ID:$SF_CLIENT_SECRET | base64 -w0", returnStdout: true).trim()
89+
token= sh(script: "curl -s --request POST --header \"Authorization: Basic ${AuthHeader}\" --header \"Content-Type: application/x-www-form-urlencoded\" \"https://4ubglassowmtsi7ogqwarmut7msn1q5ynts62fwnr1i.ssa.nvidia.com/token?grant_type=client_credentials&scope=verify:nspectid%20sourcecode:blackduck%20update:report\" | jq \".access_token\" | tr -d '\"'", returnStdout: true).trim()
90+
}
91+
return token
92+
}
6993

94+
def generateLockFiles(llmRepo, branchName)
95+
{
7096
container("alpine") {
7197
sh "apt update"
7298
sh "apt install -y python3-dev git curl git-lfs"
73-
def LLM_REPO = getLLMRepo()
74-
checkoutSource()
7599
sh "python3 --version"
76100
sh "curl -sSL https://install.python-poetry.org | python3 -"
77-
sh "cd ${env.WORKSPACE}"
78101
sh "/root/.local/bin/poetry -h"
102+
sh "git config --global --add safe.directory ${env.WORKSPACE}"
103+
sh "git config --global user.email \"90828364+tensorrt-cicd@users.noreply.github.com\""
104+
sh "git config --global user.name \"TensorRT LLM\""
79105
sh "export PATH=\"/root/.local/bin:\$PATH\" && python3 scripts/generate_lock_file.py"
80106
def count = sh(script: "git status --porcelain security_scanning/ | wc -l", returnStdout: true).trim()
81107
echo "Changed/untracked file count: ${count}"
@@ -96,15 +122,15 @@ def generate()
96122
]) {
97123
def authedUrl
98124
if (params.repoUrlKey == "tensorrt_llm_internal") {
99-
authedUrl = LLM_REPO.replaceFirst('https://', "https://svc_tensorrt:${GITLAB_API_TOKEN}@")
125+
authedUrl = llmRepo.replaceFirst('https://', "https://svc_tensorrt:${GITLAB_API_TOKEN}@")
100126
} else {
101-
authedUrl = LLM_REPO.replaceFirst('https://', "https://svc_tensorrt:${GITHUB_API_TOKEN}@")
127+
authedUrl = llmRepo.replaceFirst('https://', "https://svc_tensorrt:${GITHUB_API_TOKEN}@")
102128
}
103129
sh "git remote set-url origin ${authedUrl}"
104-
sh "git fetch origin ${params.branchName}"
130+
sh "git fetch origin ${branchName}"
105131
sh "git status"
106-
sh "git rebase origin/${params.branchName}"
107-
sh "git push origin HEAD:${params.branchName}"
132+
sh "git rebase origin/${branchName}"
133+
sh "git push origin HEAD:${branchName}"
108134
}
109135
}
110136
}
@@ -125,6 +151,51 @@ def sonar_scan()
125151
}
126152
}
127153

154+
def pulseScan(llmRepo, branchName) {
155+
container("docker") {
156+
sh "apk add jq curl"
157+
def token = getPulseToken()
158+
withCredentials([
159+
usernamePassword(
160+
credentialsId: "svc_tensorrt_gitlab_read_api_token",
161+
usernameVariable: 'USERNAME',
162+
passwordVariable: 'PASSWORD'
163+
),
164+
string(credentialsId: 'default-git-url', variable: 'DEFAULT_GIT_URL')
165+
]) {
166+
trtllm_utils.llmExecStepWithRetry(this, script: "docker login ${DEFAULT_GIT_URL}:5005 -u ${USERNAME} -p ${PASSWORD}")
167+
docker.withRegistry("https://${DEFAULT_GIT_URL}:5005") {
168+
docker.image("pstooling/pulse-group/pulse-open-source-scanner/pulse-oss-cli:stable")
169+
.inside("--user 0 --privileged -v /var/run/docker.sock:/var/run/docker.sock") {
170+
withEnv([
171+
"PULSE_NSPECT_ID=NSPECT-95LK-6FZF",
172+
"PULSE_BEARER_TOKEN=${token}",
173+
"PULSE_REPO_URL=${llmRepo}",
174+
"PULSE_SCAN_PROJECT=TRT-LLM",
175+
"PULSE_SCAN_PROJECT_VERSION=${branchName}",
176+
"PULSE_SCAN_VULNERABILITY_REPORT=nspect_scan_report.json"
177+
]) {
178+
sh 'pulse scan --no-fail .'
179+
}
180+
}
181+
}
182+
}
183+
sh "ls"
184+
sh "cat nspect_scan_report.json"
185+
withCredentials([string(credentialsId: 'trtllm_plc_slack_webhook', variable: 'PLC_SLACK_WEBHOOK')]) {
186+
def jobPath = env.JOB_NAME.replaceAll("/", "%2F")
187+
def pipelineUrl = "${env.JENKINS_URL}blue/organizations/jenkins/${jobPath}/detail/${jobPath}/${env.BUILD_NUMBER}/pipeline"
188+
sh """
189+
export TRTLLM_PLC_WEBHOOK=${PLC_SLACK_WEBHOOK}
190+
python3 -m venv venv
191+
source venv/bin/activate
192+
pip install requests
193+
python ./jenkins/scripts/submit_vulnerability_report.py --build-url ${pipelineUrl}
194+
"""
195+
}
196+
}
197+
}
198+
128199
pipeline {
129200
agent {
130201
kubernetes createKubernetesPodConfig()
@@ -139,6 +210,10 @@ pipeline {
139210
timestamps()
140211
timeout(time: 60, unit: 'MINUTES')
141212
}
213+
environment {
214+
LLM_REPO = getLLMRepo()
215+
BRANCH_NAME = "${params.branchName}"
216+
}
142217

143218
triggers {
144219
parameterizedCron('''
@@ -150,13 +225,30 @@ pipeline {
150225
stages {
151226
stage('TRT-LLM PLC Jobs') {
152227
parallel {
153-
stage("Generating Poetry Locks"){
228+
stage("Source Code OSS Scanning"){
154229
agent {
155230
kubernetes createKubernetesPodConfig()
156231
}
157-
steps
158-
{
159-
generate()
232+
stages {
233+
stage("Prepare Environment"){
234+
steps
235+
{
236+
checkoutSource()
237+
sh "cd ${env.WORKSPACE}"
238+
}
239+
}
240+
stage("Generate Lock Files"){
241+
steps
242+
{
243+
generateLockFiles(env.LLM_REPO, env.BRANCH_NAME)
244+
}
245+
}
246+
stage("Run Pulse Scanning"){
247+
steps
248+
{
249+
pulseScan(env.LLM_REPO, env.BRANCH_NAME)
250+
}
251+
}
160252
}
161253
}
162254
stage("SonarQube Code Analysis"){
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import argparse
2+
import json
3+
import os
4+
from pathlib import Path
5+
6+
import requests
7+
8+
# slack workflow setting can be edited at https://slack.com/shortcuts/Ft0AAER075L3/a314cf6f4a81543cc37b75702c5d10f1
9+
SLACK_WEBHOOK_URL = os.environ.get("TRTLLM_PLC_WEBHOOK")
10+
# this json file will be generated from pulse in pipeline scanning
11+
INPUT_FILE = "./nspect_scan_report.json"
12+
13+
parser = argparse.ArgumentParser()
14+
parser.add_argument("--build-url", required=True, help="Jenkins build URL")
15+
16+
args = parser.parse_args()
17+
18+
# Throw error if not set
19+
if not SLACK_WEBHOOK_URL:
20+
raise EnvironmentError("Error: Environment variable 'TRTLLM_PLC_WEBHOOK' is not set!")
21+
22+
# Read file
23+
raw_input = Path(INPUT_FILE).read_text()
24+
vulnerabilities = json.loads(raw_input)
25+
26+
27+
def safe(value, default="N/A"):
28+
return value if value else default
29+
30+
31+
# Build attachment text
32+
message_lines = ["* TensorRT LLM Source Code Vulnerability Scan Report*\n"]
33+
34+
severity_rank = {"Critical": 4, "High": 3, "Medium": 2, "Low": 1}
35+
36+
for v in vulnerabilities:
37+
sev = v.get("Severity", "Low")
38+
if severity_rank.get(sev, 0) <= 2:
39+
continue
40+
shortTermVersion = safe(v.get("Upgrade-Guidance", {}).get("Short-Term"))
41+
longTermVersion = safe(v.get("Upgrade-Guidance", {}).get("Long-Term"))
42+
lines = [
43+
f"🔴 *{safe(v.get('Severity'))}* — *{safe(v.get('Package Name'))}* `{safe(v.get('Package Version'))}`",
44+
f"• *CVE:* {safe(v.get('Related Vuln'))} | *BDSA:* {safe(v.get('CVE ID'))}",
45+
f"• *Score:* {safe(v.get('Score'))}",
46+
f"• *Status:* {safe(v.get('Status'))}",
47+
f"• *Published:* {safe(v.get('Vulnerability Published Date'))}",
48+
f"• *Upgrade:* `{shortTermVersion}` → `{longTermVersion}`",
49+
"─" * 40, # separator line
50+
]
51+
message_lines.extend(lines)
52+
53+
message_text = "\n".join(message_lines)
54+
55+
payload = {"report": message_text, "pipelineUrl": args.build_url}
56+
57+
print(payload)
58+
# Send to Slack
59+
resp = requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=60)
60+
resp.raise_for_status()

0 commit comments

Comments
 (0)