Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 124 additions & 45 deletions jenkins/TensorRT_LLM_PLC.groovy
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@Library(['trtllm-jenkins-shared-lib@main']) _
import groovy.json.JsonSlurper

def createKubernetesPodConfig()
{
Expand Down Expand Up @@ -78,23 +79,30 @@ def getLLMRepo () {
def installTools() {
container("cpu") {
sh "apt update"
sh "apt install -y git git-lfs openjdk-17-jdk python3-dev python3-venv curl unzip wget"
sh "apt install -y git git-lfs openjdk-17-jdk python3-dev python3-venv curl zip unzip wget"
}
}

def validateBranchName(String branch) {
container("cpu") {
def rc = sh(script: "git check-ref-format --branch '${branch}'", returnStatus: true)
if (rc != 0) {
error("Invalid branch name: '${branch}'")
}
}
}

def checkoutSource ()
{
container("cpu") {
trtllm_utils.setupGitMirror()
stage("Checkout TRTLLM Source") {
def LLM_REPO = getLLMRepo()
sh "git config --global --add safe.directory ${env.WORKSPACE}"
trtllm_utils.checkoutSource(LLM_REPO, params.branchName, env.WORKSPACE, false, true)
}
def LLM_REPO = getLLMRepo()
sh "git config --global --add safe.directory ${env.WORKSPACE}"
trtllm_utils.checkoutSource(LLM_REPO, params.branchName, env.WORKSPACE, false, true)
}
}

def getPulseToken() {
def getPulseToken(serviceId, scopes) {
def token
//Configure credential 'starfleet-client-id' under Jenkins Credential Manager
withCredentials([usernamePassword(
Expand All @@ -104,10 +112,10 @@ def getPulseToken() {
)]) {
// Do not save AUTH_HEADER to a groovy variable since that
// will expose the auth_header without being masked
token= sh(script: '''
AUTH_HEADER=$(echo -n $SF_CLIENT_ID:$SF_CLIENT_SECRET | base64 -w0)
curl -s --request POST --header "Authorization: Basic $AUTH_HEADER" --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()
token= sh(script: """
AUTH_HEADER=\$(echo -n \$SF_CLIENT_ID:\$SF_CLIENT_SECRET | base64 -w0)
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 '"'
""", returnStdout: true).trim()
}
return token
}
Expand Down Expand Up @@ -169,22 +177,22 @@ def sonarScan()
}
}

def pulseScan(llmRepo, branchName) {
def pulseScanSourceCode(llmRepo, branchName) {
container("docker") {
sh "apk add jq curl"
def token = getPulseToken()
def token = getPulseToken("4ubglassowmtsi7ogqwarmut7msn1q5ynts62fwnr1i", "verify:nspectid%20sourcecode:blackduck%20update:report")
if (!token) {
throw new Exception("Invalid token get")
}
withCredentials([
usernamePassword(
credentialsId: "svc_tensorrt_gitlab_read_api_token",
usernameVariable: 'USERNAME',
passwordVariable: 'PASSWORD'
usernameVariable: 'GITLAB_USERNAME',
passwordVariable: 'GITLAB_PASSWORD'
),
string(credentialsId: 'default-git-url', variable: 'DEFAULT_GIT_URL')
]) {
trtllm_utils.llmExecStepWithRetry(this, script: "docker login ${DEFAULT_GIT_URL}:5005 -u ${USERNAME} -p ${PASSWORD}")
trtllm_utils.llmExecStepWithRetry(this, script: "docker login ${DEFAULT_GIT_URL}:5005 -u ${GITLAB_USERNAME} -p ${GITLAB_PASSWORD}")
docker.withRegistry("https://${DEFAULT_GIT_URL}:5005") {
docker.image("pstooling/pulse-group/pulse-open-source-scanner/pulse-oss-cli:stable")
.inside("--user 0 --privileged -v /var/run/docker.sock:/var/run/docker.sock") {
Expand All @@ -207,25 +215,95 @@ def pulseScan(llmRepo, branchName) {
}
}
container("cpu") {
sh "cat nspect_scan_report.json"
sh 'unzip -p sbom.zip "*.json" > sbom_toupload.json'
sh "cat sbom_toupload.json"
def outputDir = "scan_report/source_code"
sh "mkdir -p ${outputDir}"
sh "unzip -p sbom.zip \"*.json\" > ${outputDir}/sbom.json"
sh "mv nspect_scan_report.json ${outputDir}/vulns.json"
}
}
def pulseScanContainer(llmRepo, branchName) {
// imageTags: key -> [image: <full image:tag>, platform: <platform or empty>]
def imageTags = [:]
container("cpu") {
def output = sh(
script: "./jenkins/scripts/get_image_key_to_tag.sh ${params.branchName}",
returnStdout: true
).trim()
def containerTagMap = new JsonSlurper().parseText(output)
imageTags["release_amd64"] = [image: containerTagMap["NGC Release Image amd64"], platform: "linux/amd64"]
imageTags["release_arm64"] = [image: containerTagMap["NGC Release Image arm64"], platform: "linux/arm64"]

def baseImage = sh(script: "grep -m1 '^ARG BASE_IMAGE=' docker/Dockerfile.multi | cut -d= -f2", returnStdout: true).trim()
def baseTag = sh(script: "grep -m1 '^ARG BASE_TAG=' docker/Dockerfile.multi | cut -d= -f2", returnStdout: true).trim()
imageTags["base_amd64"] = [image: "${baseImage}:${baseTag}", platform: "linux/amd64"]
imageTags["base_arm64"] = [image: "${baseImage}:${baseTag}", platform: "linux/arm64"]
}
container("docker") {
sh "apk add jq curl"
def token = getPulseToken("x9thwm-cootr2q1jdv5p7b8iw4fs4ob3x6nqqsoznyk", "nspect.verify%20scan.anchore")
if (!token) {
throw new Exception("Invalid token get")
}
withCredentials([
usernamePassword(
credentialsId: "svc_tensorrt_gitlab_read_api_token",
usernameVariable: 'GITLAB_USERNAME',
passwordVariable: 'GITLAB_PASSWORD'
),
usernamePassword(
credentialsId: "urm-artifactory-creds",
usernameVariable: 'URM_USERNAME',
passwordVariable: 'URM_PASSWORD'
),
string(credentialsId: 'default-git-url', variable: 'DEFAULT_GIT_URL'),
]) {
trtllm_utils.llmExecStepWithRetry(this, script: "docker login ${DEFAULT_GIT_URL}:5005 -u ${GITLAB_USERNAME} -p ${GITLAB_PASSWORD}")
trtllm_utils.llmExecStepWithRetry(this, script: "docker login urm.nvidia.com -u ${URM_USERNAME} -p ${URM_PASSWORD}")
docker.withRegistry("https://${DEFAULT_GIT_URL}:5005") {
docker.image("gitlab-master.nvidia.com:5005/pstooling/pulse-group/pulse-container-scanner/pulse-cli:5.1.0")
.inside("--user 0 --privileged -v /var/run/docker.sock:/var/run/docker.sock") {
withEnv([
"NSPECT_ID=NSPECT-95LK-6FZF",
"SSA_TOKEN=${token}",
]) {
imageTags.each { key, entry ->
def platform = entry.platform.replace("linux/", "")
def outputDir = "scan_report/${key}"
sh "mkdir -p ${outputDir}"
echo "Scanning ${key}: ${entry.image} (${entry.platform}) -> ${outputDir}"
sh "pulse-cli -n \$NSPECT_ID --ssa \$SSA_TOKEN scan-image -i ${entry.image} --platform ${entry.platform} --sbom=cyclonedx-json --output-dir=${outputDir} -o"
}
}
}
}
}
}
}

def processScanResults(branchName) {
container("cpu") {
def ELASTICSEARCH_POST_URL = "http://nvdataflow.nvidia.com/dataflow/swdl-tensorrt-infra-plc-scan/posting"
def ELASTICSEARCH_QUERY_URL = "https://gpuwa.nvidia.com/elasticsearch"
def TRTLLM_ES_INDEX_BASE = "df-swdl-tensorrt-infra-plc-scan"
def TRTLLM_ES_INDEX_PREAPPROVED_BASE = "df-swdl-tensorrt-infra-plc-container-pre-approve"
def jobPath = env.JOB_NAME.replaceAll("/", "%2F")
def pipelineUrl = "${env.JENKINS_URL}blue/organizations/jenkins/${jobPath}/detail/${jobPath}/${env.BUILD_NUMBER}/pipeline"
withCredentials([string(credentialsId: 'trtllm_plc_slack_webhook', variable: 'PLC_SLACK_WEBHOOK')]) {
def ELASTICSEARCH_POST_URL = "http://nvdataflow.nvidia.com/dataflow/swdl-tensorrt-infra-plc/posting"
def ELASTICSEARCH_QUERY_URL = "https://gpuwa.nvidia.com/elasticsearch"
def TRTLLM_ES_INDEX_BASE = "df-swdl-tensorrt-infra-plc"
def jobPath = env.JOB_NAME.replaceAll("/", "%2F")
def pipelineUrl = "${env.JENKINS_URL}blue/organizations/jenkins/${jobPath}/detail/${jobPath}/${env.BUILD_NUMBER}/pipeline"
withEnv([
"TRTLLM_ES_POST_URL=${ELASTICSEARCH_POST_URL}",
"TRTLLM_ES_QUERY_URL=${ELASTICSEARCH_QUERY_URL}",
"TRTLLM_ES_INDEX_BASE=${TRTLLM_ES_INDEX_BASE}",
"TRTLLM_ES_INDEX_PREAPPROVED_BASE=${TRTLLM_ES_INDEX_PREAPPROVED_BASE}",
"TRTLLM_PLC_WEBHOOK=${PLC_SLACK_WEBHOOK}"
]) {
sh """
python3 -m venv venv
venv/bin/pip install requests elasticsearch==7.13.4
venv/bin/python ./jenkins/scripts/submit_vulnerability_report.py --build-url ${pipelineUrl} --build-number ${env.BUILD_NUMBER} --branch ${params.branchName}
venv/bin/python ./jenkins/scripts/pulse_in_pipeline_scanning/main.py \
--build-url ${pipelineUrl} \
--build-number ${env.BUILD_NUMBER} \
--branch ${branchName} \
--report-directory ${pwd()}/scan_report
"""
}
}
Expand Down Expand Up @@ -256,7 +334,6 @@ pipeline {
// Jobs in other folders (e.g. personal/dev pipelines) will have no cron trigger.
parameterizedCron(env.JOB_NAME.startsWith('LLM/helpers/') ? '''
H 2 * * * %branchName=main;repoUrlKey=tensorrt_llm_github
H 3 * * * %branchName=release/1.2;repoUrlKey=tensorrt_llm_github
''' : '')
}
stages {
Expand All @@ -265,40 +342,42 @@ pipeline {
script {
installTools()
checkoutSource()
validateBranchName(params.branchName)
}
}
}
stage('Run TRT-LLM PLC Jobs') {
parallel {
stage("Source Code OSS Scanning"){
stages {
stage("Generate Lock Files"){
steps
{
script {
generateLockFiles(env.LLM_REPO, env.BRANCH_NAME)
}
}
stage("Source Code OSS Scanning") {
steps {
script {
generateLockFiles(env.LLM_REPO, env.BRANCH_NAME)
pulseScanSourceCode(env.LLM_REPO, env.BRANCH_NAME)
}
stage("Run Pulse Scanning"){
steps
{
script {
pulseScan(env.LLM_REPO, env.BRANCH_NAME)
}
}
}
}
stage("Run Container Scanning") {
steps {
script {
pulseScanContainer(env.LLM_REPO, env.BRANCH_NAME)
}
}
}
stage("SonarQube Code Analysis"){
steps
{
stage("SonarQube Code Analysis") {
steps {
script {
sonarScan()
}
}
}
}
}
stage("Process Scan Result") {
steps {
script {
processScanResults(env.BRANCH_NAME)
}
}
}
} // stages
} // pipeline
45 changes: 45 additions & 0 deletions jenkins/scripts/get_image_key_to_tag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason to make this a Bash script if all the other scripts are Python?


set -e

BRANCH_NAME="${1}"

if [[ -z "$BRANCH_NAME" ]]; then
echo "Usage: $0 <branch_name>" >&2
exit 1
fi

JENKINS_BASE="https://prod.blsm.nvidia.com/sw-tensorrt-top-1/job/LLM/job/${BRANCH_NAME}/job/L0_PostMerge"
ARTIFACTORY_BASE="https://urm.nvidia.com/artifactory/sw-tensorrt-generic-local/llm-artifacts/LLM/${BRANCH_NAME}/L0_PostMerge"

echo "Fetching latest build number from Jenkins for branch: ${BRANCH_NAME}" >&2

# Query Jenkins API for the last successful build number
BUILD_NUMBER=$(curl -fsSL "${JENKINS_BASE}/lastBuild/api/json" | python3 -c "import json,sys; print(json.load(sys.stdin)['number'])" 2>/dev/null)

if [[ -z "$BUILD_NUMBER" ]]; then
echo "Failed to get last successful build number. Trying last completed build..." >&2
BUILD_NUMBER=$(curl -fsSL "${JENKINS_BASE}/lastCompletedBuild/api/json" | python3 -c "import json,sys; print(json.load(sys.stdin)['number'])" 2>/dev/null)
fi

if [[ -z "$BUILD_NUMBER" ]]; then
echo "Error: Could not determine the latest build number from ${JENKINS_BASE}" >&2
exit 1
fi

echo "Latest build number: ${BUILD_NUMBER}" >&2

while [[ "${BUILD_NUMBER}" -gt 0 ]]; do
ARTIFACT_URL="${ARTIFACTORY_BASE}/${BUILD_NUMBER}/imageKeyToTag.json"
echo "Fetching: ${ARTIFACT_URL}" >&2
HTTP_STATUS=$(curl -sL -o /tmp/imageKeyToTag.json -w "%{http_code}" "${ARTIFACT_URL}")
if [[ "${HTTP_STATUS}" == "200" ]]; then
cat /tmp/imageKeyToTag.json
exit 0
fi
echo "Got HTTP ${HTTP_STATUS} for build ${BUILD_NUMBER}, trying build $((BUILD_NUMBER - 1))..." >&2
BUILD_NUMBER=$((BUILD_NUMBER - 1))
done

echo "Error: Could not find imageKeyToTag.json in any recent build" >&2
exit 1
Loading