Skip to content

Commit 9571127

Browse files
committed
NEXUS-42442: Created Jenkinsfile to publish docker SBOMs
1 parent 151de4d commit 9571127

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed

Diff for: Jenkinsfile-sbom-release

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
@Library(['private-pipeline-library', 'jenkins-shared']) _
2+
3+
import com.sonatype.jenkins.pipeline.OsTools
4+
5+
import groovy.json.JsonSlurper
6+
7+
IQ_URL_BASE = "https://sonatype.sonatype.app/platform"
8+
REPO_BASE_URL = "https://repo.sonatype.com/service/rest"
9+
TARGET_REPO_NAME = "sonatype-sboms"
10+
REDHAT_SBOM_REPO_URL_BASE = "https://access.redhat.com/security/data/sbom/beta"
11+
REDHAT_CONTAINER_API_URL_BASE = "https://catalog.redhat.com/api/containers/v1"
12+
CYCLONEDX_VERSION = "1.5"
13+
14+
properties([
15+
parameters([
16+
string(name: 'docker_nexus3_tag', defaultValue: '',
17+
description: 'Nexus Repository Manager Version. The result SBOMs will be tagged with this version.')
18+
])
19+
])
20+
21+
22+
node('ubuntu-zion') {
23+
def buildDir = "./.sbom-build/job-${env.BUILD_NUMBER}"
24+
def ubiImageName = ''
25+
def ubiImageVersion = ''
26+
def nexusVersion = ''
27+
def dockerImageVersion = ''
28+
def ubiSbomAvailable = true
29+
30+
try {
31+
stage('Inspect Release Image') {
32+
// Get RedHat UBI version
33+
sh "docker pull sonatype/nexus3:${params.docker_nexus3_tag}"
34+
35+
nexusVersion = sh(script: "docker inspect sonatype/nexus3:${params.docker_nexus3_tag} \
36+
| jq -r '.[0].Config.Labels.version' ",
37+
returnStdout: true).trim()
38+
dockerImageVersion = sh(script: "docker inspect sonatype/nexus3:${params.docker_nexus3_tag} \
39+
| jq -r '.[0].Config.Labels.release' ",
40+
returnStdout: true).trim()
41+
42+
def ubiImageId = sh(script: "docker inspect sonatype/nexus3:${params.docker_nexus3_tag} \
43+
| jq -r '.[0].Config.Labels.\"base-image-ref\"' \
44+
| sed -En 's/^.+image=(.+)\$/\\1/p'",
45+
returnStdout: true).trim()
46+
47+
ubiImageName = sh(script: "curl -s -X 'GET' '${REDHAT_CONTAINER_API_URL_BASE}/images/id/${ubiImageId}' -H 'accept: application/json' \
48+
| jq -r '.brew.build' \
49+
| sed -En 's/(ubi[0-9]+-minimal)-container-([0-9]+\\.[0-9]+-[0-9]+\\.?[0-9]*)/\\1-\\2/p'",
50+
returnStdout: true).trim()
51+
52+
ubiImageVersion = sh(script: "curl -s -X 'GET' '${REDHAT_CONTAINER_API_URL_BASE}/images/id/${ubiImageId}' -H 'accept: application/json' \
53+
| jq -r '.brew.build' \
54+
| sed -En 's/ubi[0-9]+-minimal-container-([0-9]+\\.[0-9]+-[0-9]+\\.?[0-9]*)/\\1/p'",
55+
returnStdout: true).trim()
56+
}
57+
stage('Download SBOMs') {
58+
sh "mkdir -p ${buildDir}/spdx && mkdir -p ${buildDir}/cyclonedx"
59+
60+
// Get nexus-internal SBOM
61+
getComponentSbom(buildDir, "nexus-internal", nexusVersion)
62+
// Get nxrm-db-migrator SBOM
63+
getComponentSbom(buildDir, "nxrm-db-migrator", nexusVersion)
64+
// Get docker-nexus3 SBOM
65+
getComponentSbom(buildDir, "docker-nexus3", dockerImageVersion)
66+
// Get UBI Minimal SBOM
67+
ubiSbomAvailable = getUbiImageSbom(buildDir, ubiImageName, ubiImageVersion)
68+
69+
sh "echo 'Available SPDX SBOMS' && ls ${buildDir}/spdx"
70+
sh "echo 'Available CycloneDx SBOMS' && ls ${buildDir}/cyclonedx"
71+
}
72+
73+
stage('Merge supported sboms') {
74+
def pythonEnvDir = "${buildDir}/.spdxmerge"
75+
76+
sh """#!/bin/bash
77+
if ! [ -d "${buildDir}/SPDXMerge" ]; then
78+
git clone https://github.com/philips-software/SPDXMerge.git '${buildDir}/SPDXMerge'
79+
fi
80+
"""
81+
82+
sh """#!/bin/bash
83+
if mkdir -p '${pythonEnvDir}' && python3 -m venv '${pythonEnvDir}' && ls '${pythonEnvDir}' && . '${pythonEnvDir}/bin/activate'; then
84+
if python3 -m pip install -r '${buildDir}/SPDXMerge/requirements.txt' \
85+
&& python3 -m pip install setuptools \
86+
&& python3 '${buildDir}/SPDXMerge/spdxmerge/SPDXMerge.py' --docpath '${buildDir}/spdx' --outpath '${buildDir}/' \
87+
--name "docker-nexus3-aggregate" --mergetype "1" --author "Sonatype Inc." --email "[email protected]" \
88+
--docnamespace "https://sonatype.sonatype.app/platform/ui/links/application/docker-nexus3/report/b0c5f7f12ac84b439ded3ff255bd5eef" \
89+
--filetype J \
90+
&& mv '${buildDir}/merged-SBoM-deep.json' '${buildDir}/spdx/docker-nexus3-aggregate-${dockerImageVersion}-spdx.json'; then
91+
echo 'Merge completed!'
92+
else
93+
echo 'Merge failed!'
94+
FAILED=1
95+
fi
96+
97+
deactivate
98+
fi
99+
100+
exit \${FAILED:-0}
101+
"""
102+
}
103+
104+
stage('Publish SBOMs') {
105+
if (ubiSbomAvailable) {
106+
publishComponent(buildDir, "ubi-minimal", ubiImageVersion, false)
107+
}
108+
publishComponent(buildDir, "nexus-internal", nexusVersion)
109+
publishComponent(buildDir, "nxrm-db-migrator", nexusVersion)
110+
publishComponent(buildDir, "docker-nexus3", dockerImageVersion)
111+
publishComponent(buildDir, "docker-nexus3-aggregate", dockerImageVersion, false)
112+
}
113+
} finally {
114+
OsTools.runSafe(this, "rm -rf '${buildDir}'")
115+
}
116+
}
117+
118+
def getComponentSbom(String buildDir, String componentName, String componentVersion) {
119+
def componentInfo = getComponentInfo(componentName)
120+
def componentId = componentInfo.applications[0].id
121+
122+
withCredentials([
123+
usernamePassword(
124+
credentialsId: 'jenkins-saas-service-acct',
125+
usernameVariable: 'IQ_USER',
126+
passwordVariable: 'IQ_PASSWORD')
127+
]) {
128+
// Get SPDX
129+
sh "curl -s -L -w 'Status: %{http_code}' -u \$IQ_USER:\$IQ_PASSWORD -o '${buildDir}/spdx/${componentName}-${componentVersion}-spdx.json' -X GET \
130+
-H 'Accept: application/json' '${IQ_URL_BASE}/api/v2/spdx/${componentId}/stages/release?format=json'"
131+
// Get CycloneDx
132+
sh "curl -s -L -w 'Status: %{http_code}' -u \$IQ_USER:\$IQ_PASSWORD -o '${buildDir}/cyclonedx/${componentName}-${componentVersion}-cyclonedx.json' -X GET \
133+
-H 'Accept: application/json' '${IQ_URL_BASE}/api/v2/cycloneDx/${CYCLONEDX_VERSION}/${componentId}/stages/release'"
134+
}
135+
}
136+
137+
def getUbiImageSbom(String buildDir, String ubiMinimalName, String ubiMinimalVersion) {
138+
// Get ubi-minimal SBOM (as RedHat SBOM repo is still in beta, this has to be optional)
139+
def httpStatus = sh(
140+
script: "curl -s -w \"%{http_code}\" \
141+
-X GET ${REDHAT_SBOM_REPO_URL_BASE}/spdx/${ubiMinimalName}.json.bz2 \
142+
-o '${buildDir}/spdx/ubi-minimal-${ubiMinimalVersion}.json.bz2'",
143+
returnStdout: true)
144+
145+
if (!"200".equals(httpStatus)) {
146+
echo """ Error ${httpStatus}: Could not load UBI minimal SBOM version ${ubiMinimalVersion}.
147+
This could happen because RedHat SBOM repo is still in beta. UBI SBOM will be skipped.
148+
Please visit https://access.redhat.com/security/data for further information.
149+
"""
150+
151+
sh "rm '${buildDir}/spdx/ubi-minimal-${ubiMinimalVersion}.json.bz2'"
152+
153+
return false
154+
} else {
155+
sh "(cd ${buildDir}/spdx && bzip2 -d 'ubi-minimal-${ubiMinimalVersion}.json.bz2')"
156+
return true
157+
}
158+
}
159+
160+
def getComponentInfo(String componentName) {
161+
def jsonSlurper = new JsonSlurper()
162+
def response = null
163+
164+
withCredentials([
165+
usernamePassword(
166+
credentialsId: 'jenkins-saas-service-acct',
167+
usernameVariable: 'IQ_USER',
168+
passwordVariable: 'IQ_PASSWORD')
169+
]) {
170+
def rawResponse = sh(returnStdout: true, script: "curl -s -u \$IQ_USER:\$IQ_PASSWORD -X GET '${IQ_URL_BASE}/api/v2/applications?publicId=${componentName}'")
171+
response = jsonSlurper.parseText(rawResponse)
172+
}
173+
}
174+
175+
def publishComponent(String buildDir, String componentName, String componentVersion, boolean cyclonedxAvailable = true) {
176+
def publishCommand = "curl -v -s -w 'Status: %{http_code}' -u \$NXRM_USER:\$NXRM_PASSWORD -X POST '${REPO_BASE_URL}/v1/components?repository=${TARGET_REPO_NAME}' \
177+
-F 'raw.directory=/${componentName}/${componentVersion}/' \
178+
-F 'raw.asset1=@${buildDir}/spdx/${componentName}-${componentVersion}-spdx.json' \
179+
-F 'raw.asset1.filename=${componentName}-${componentVersion}-spdx.json'"
180+
181+
if (cyclonedxAvailable) {
182+
publishCommand = "${publishCommand} \
183+
-F 'raw.asset2=@${buildDir}/cyclonedx/${componentName}-${componentVersion}-cyclonedx.json' \
184+
-F 'raw.asset2.filename=${componentName}-${componentVersion}-cyclonedx.json'"
185+
}
186+
187+
withCredentials([
188+
usernamePassword(
189+
credentialsId: 'sonatype-sbom-deployer',
190+
usernameVariable: 'NXRM_USER',
191+
passwordVariable: 'NXRM_PASSWORD')
192+
]) {
193+
sh(publishCommand)
194+
}
195+
}

0 commit comments

Comments
 (0)