Skip to content

Commit 60a6409

Browse files
authored
feat: add validation for sbom generated by gradle (#5522)
1 parent cd4c270 commit 60a6409

File tree

3 files changed

+193
-84
lines changed

3 files changed

+193
-84
lines changed

cmd/gradleExecuteBuild.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,33 @@ func createBOM(config *gradleExecuteBuildOptions, utils gradleExecuteBuildUtils)
235235
log.Entry().WithError(err).Errorf("failed to create BOM: %v", err)
236236
return err
237237
}
238+
239+
// Validate generated SBOMs
240+
bomFilename := gradleBomFilename + ".xml"
241+
err = utils.WalkDir(rootPath, func(path string, d fs.DirEntry, err error) error {
242+
if err != nil {
243+
return err
244+
}
245+
if d.IsDir() {
246+
return nil
247+
}
248+
if strings.HasSuffix(path, bomFilename) {
249+
log.Entry().Infof("Validating generated SBOM: %s", path)
250+
251+
if err := piperutils.ValidateCycloneDX14(path); err != nil {
252+
log.Entry().Warnf("SBOM validation failed: %v", err)
253+
} else {
254+
purl := piperutils.GetPurl(path)
255+
log.Entry().Infof("SBOM validation passed")
256+
log.Entry().Infof("SBOM PURL: %s", purl)
257+
}
258+
}
259+
return nil
260+
})
261+
if err != nil {
262+
log.Entry().Warnf("Failed to walk directory for SBOM validation: %v", err)
263+
}
264+
238265
return nil
239266
}
240267

cmd/gradleExecuteBuild_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/pkg/errors"
1313
"github.com/stretchr/testify/assert"
1414

15+
"github.com/SAP/jenkins-library/pkg/config"
1516
"github.com/SAP/jenkins-library/pkg/mock"
1617
"github.com/SAP/jenkins-library/pkg/piperenv"
1718
)
@@ -43,6 +44,9 @@ func (d isDirEntryMock) Info() (fs.FileInfo, error) {
4344

4445
func TestRunGradleExecuteBuild(t *testing.T) {
4546
pipelineEnv := &gradleExecuteBuildCommonPipelineEnvironment{}
47+
SetConfigOptions(ConfigCommandOptions{
48+
OpenFile: config.OpenPiperFile,
49+
})
4650

4751
t.Run("failed case - build.gradle isn't present", func(t *testing.T) {
4852
utils := gradleExecuteBuildMockUtils{
@@ -61,9 +65,13 @@ func TestRunGradleExecuteBuild(t *testing.T) {
6165
})
6266

6367
t.Run("success case - only build", func(t *testing.T) {
68+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
69+
return nil // No BOM files
70+
}
6471
utils := gradleExecuteBuildMockUtils{
6572
ExecMockRunner: &mock.ExecMockRunner{},
6673
FilesMock: &mock.FilesMock{},
74+
Filepath: walkDir,
6775
}
6876
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
6977
options := &gradleExecuteBuildOptions{
@@ -79,9 +87,13 @@ func TestRunGradleExecuteBuild(t *testing.T) {
7987
})
8088

8189
t.Run("success case - build with flags", func(t *testing.T) {
90+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
91+
return nil // No BOM files
92+
}
8293
utils := gradleExecuteBuildMockUtils{
8394
ExecMockRunner: &mock.ExecMockRunner{},
8495
FilesMock: &mock.FilesMock{},
96+
Filepath: walkDir,
8597
}
8698
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
8799
options := &gradleExecuteBuildOptions{
@@ -98,11 +110,24 @@ func TestRunGradleExecuteBuild(t *testing.T) {
98110
})
99111

100112
t.Run("success case - bom creation", func(t *testing.T) {
113+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
114+
var dirMock isDirEntryMock = func() bool {
115+
return false
116+
}
117+
// Simulate finding a BOM file
118+
return fn(filepath.Join("path", "to", "build", "reports", "bom-gradle.xml"), dirMock, nil)
119+
}
101120
utils := gradleExecuteBuildMockUtils{
102121
ExecMockRunner: &mock.ExecMockRunner{},
103122
FilesMock: &mock.FilesMock{},
123+
Filepath: walkDir,
104124
}
105125
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
126+
// Add a valid BOM file for validation
127+
utils.FilesMock.AddFile(filepath.Join("path", "to", "build", "reports", "bom-gradle.xml"), []byte(`<?xml version="1.0" encoding="UTF-8"?>
128+
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" version="1">
129+
<components/>
130+
</bom>`))
106131
options := &gradleExecuteBuildOptions{
107132
Path: "path/to",
108133
Task: "build",
@@ -153,9 +178,13 @@ func TestRunGradleExecuteBuild(t *testing.T) {
153178
})
154179

155180
t.Run("success case - build using wrapper", func(t *testing.T) {
181+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
182+
return nil // No BOM files
183+
}
156184
utils := gradleExecuteBuildMockUtils{
157185
ExecMockRunner: &mock.ExecMockRunner{},
158186
FilesMock: &mock.FilesMock{},
187+
Filepath: walkDir,
159188
}
160189
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
161190
utils.FilesMock.AddFile("gradlew", []byte{})
@@ -172,11 +201,15 @@ func TestRunGradleExecuteBuild(t *testing.T) {
172201
})
173202

174203
t.Run("failed case - build", func(t *testing.T) {
204+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
205+
return nil // No BOM files
206+
}
175207
utils := gradleExecuteBuildMockUtils{
176208
ExecMockRunner: &mock.ExecMockRunner{
177209
ShouldFailOnCommand: map[string]error{"gradle build -p path/to": errors.New("failed to build")},
178210
},
179211
FilesMock: &mock.FilesMock{},
212+
Filepath: walkDir,
180213
}
181214
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
182215
options := &gradleExecuteBuildOptions{
@@ -191,11 +224,15 @@ func TestRunGradleExecuteBuild(t *testing.T) {
191224
})
192225

193226
t.Run("failed case - build with flags", func(t *testing.T) {
227+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
228+
return nil // No BOM files
229+
}
194230
utils := gradleExecuteBuildMockUtils{
195231
ExecMockRunner: &mock.ExecMockRunner{
196232
ShouldFailOnCommand: map[string]error{"gradle clean build -x test -p path/to": errors.New("failed to build with flags")},
197233
},
198234
FilesMock: &mock.FilesMock{},
235+
Filepath: walkDir,
199236
}
200237
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
201238
options := &gradleExecuteBuildOptions{
@@ -211,11 +248,15 @@ func TestRunGradleExecuteBuild(t *testing.T) {
211248
})
212249

213250
t.Run("failed case - bom creation", func(t *testing.T) {
251+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
252+
return nil // No BOM files (build failed before creation)
253+
}
214254
utils := gradleExecuteBuildMockUtils{
215255
ExecMockRunner: &mock.ExecMockRunner{
216256
ShouldFailOnCommand: map[string]error{"./gradlew cyclonedxBom -p path/to --init-script initScript.gradle.tmp": errors.New("failed to create bom")},
217257
},
218258
FilesMock: &mock.FilesMock{},
259+
Filepath: walkDir,
219260
}
220261
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
221262
utils.FilesMock.AddFile("gradlew", []byte{})
@@ -232,11 +273,15 @@ func TestRunGradleExecuteBuild(t *testing.T) {
232273
})
233274

234275
t.Run("failed case - publish artifacts", func(t *testing.T) {
276+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
277+
return nil // No module files
278+
}
235279
utils := gradleExecuteBuildMockUtils{
236280
ExecMockRunner: &mock.ExecMockRunner{
237281
ShouldFailOnCommand: map[string]error{"./gradlew publish -p path/to --init-script initScript.gradle.tmp": errors.New("failed to publish artifacts")},
238282
},
239283
FilesMock: &mock.FilesMock{},
284+
Filepath: walkDir,
240285
}
241286
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
242287
utils.FilesMock.AddFile("gradlew", []byte{})
@@ -251,6 +296,70 @@ func TestRunGradleExecuteBuild(t *testing.T) {
251296
assert.Error(t, err)
252297
assert.Contains(t, err.Error(), "failed to publish artifacts")
253298
})
299+
300+
t.Run("success case - bom creation with multiple modules", func(t *testing.T) {
301+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
302+
var dirMock isDirEntryMock = func() bool {
303+
return false
304+
}
305+
// Simulate finding multiple BOM files from different modules
306+
_ = fn(filepath.Join("module1", "build", "reports", "bom-gradle.xml"), dirMock, nil)
307+
_ = fn(filepath.Join("module2", "build", "reports", "bom-gradle.xml"), dirMock, nil)
308+
return nil
309+
}
310+
utils := gradleExecuteBuildMockUtils{
311+
ExecMockRunner: &mock.ExecMockRunner{},
312+
FilesMock: &mock.FilesMock{},
313+
Filepath: walkDir,
314+
}
315+
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
316+
// Add valid BOM files for both modules
317+
validBOM := []byte(`<?xml version="1.0" encoding="UTF-8"?>
318+
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" version="1">
319+
<components/>
320+
</bom>`)
321+
utils.FilesMock.AddFile(filepath.Join("module1", "build", "reports", "bom-gradle.xml"), validBOM)
322+
utils.FilesMock.AddFile(filepath.Join("module2", "build", "reports", "bom-gradle.xml"), validBOM)
323+
options := &gradleExecuteBuildOptions{
324+
Path: "path/to",
325+
Task: "build",
326+
UseWrapper: false,
327+
CreateBOM: true,
328+
}
329+
330+
err := runGradleExecuteBuild(options, nil, utils, pipelineEnv)
331+
assert.NoError(t, err)
332+
assert.Equal(t, 3, len(utils.Calls))
333+
})
334+
335+
t.Run("success case - bom creation with invalid BOM (validation warns but doesn't fail)", func(t *testing.T) {
336+
var walkDir WalkDirFunc = func(root string, fn fs.WalkDirFunc) error {
337+
var dirMock isDirEntryMock = func() bool {
338+
return false
339+
}
340+
// Simulate finding an invalid BOM file
341+
return fn(filepath.Join("path", "to", "build", "reports", "bom-gradle.xml"), dirMock, nil)
342+
}
343+
utils := gradleExecuteBuildMockUtils{
344+
ExecMockRunner: &mock.ExecMockRunner{},
345+
FilesMock: &mock.FilesMock{},
346+
Filepath: walkDir,
347+
}
348+
utils.FilesMock.AddFile("path/to/build.gradle", []byte{})
349+
// Add an invalid BOM file
350+
utils.FilesMock.AddFile(filepath.Join("path", "to", "build", "reports", "bom-gradle.xml"), []byte(`invalid xml content`))
351+
options := &gradleExecuteBuildOptions{
352+
Path: "path/to",
353+
Task: "build",
354+
UseWrapper: false,
355+
CreateBOM: true,
356+
}
357+
358+
// Should not fail even with invalid BOM (validation only warns)
359+
err := runGradleExecuteBuild(options, nil, utils, pipelineEnv)
360+
assert.NoError(t, err)
361+
assert.Equal(t, 3, len(utils.Calls))
362+
})
254363
}
255364

256365
func TestGetPublishedArtifactsNames(t *testing.T) {

0 commit comments

Comments
 (0)