mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-02-21 19:48:53 +02:00
Include purl info in the event (#5092)
This commit is contained in:
parent
2175d3808b
commit
5230c3d454
@ -27,7 +27,6 @@ const (
|
||||
|
||||
func mavenBuild(config mavenBuildOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *mavenBuildCommonPipelineEnvironment) {
|
||||
utils := maven.NewUtilsBundle()
|
||||
|
||||
// enables url-log.json creation
|
||||
cmd := reflect.ValueOf(utils).Elem().FieldByName("Command")
|
||||
if cmd.IsValid() {
|
||||
@ -62,7 +61,7 @@ func runMavenBuild(config *mavenBuildOptions, telemetryData *telemetry.CustomDat
|
||||
}
|
||||
|
||||
if config.CreateBOM {
|
||||
goals = append(goals, "org.cyclonedx:cyclonedx-maven-plugin:2.7.8:makeAggregateBom")
|
||||
goals = append(goals, "org.cyclonedx:cyclonedx-maven-plugin:2.7.8:makeBom", "org.cyclonedx:cyclonedx-maven-plugin:2.7.8:makeAggregateBom")
|
||||
createBOMConfig := []string{
|
||||
"-DschemaVersion=1.4",
|
||||
"-DincludeBomSerialNumber=true",
|
||||
@ -183,6 +182,7 @@ func runMavenBuild(config *mavenBuildOptions, telemetryData *telemetry.CustomDat
|
||||
} else {
|
||||
coordinate.BuildPath = filepath.Dir(match)
|
||||
coordinate.URL = config.AltDeploymentRepositoryURL
|
||||
coordinate.PURL = getPurlForThePomAndDeleteIndividualBom(match)
|
||||
buildCoordinates = append(buildCoordinates, coordinate)
|
||||
}
|
||||
}
|
||||
@ -209,6 +209,42 @@ func runMavenBuild(config *mavenBuildOptions, telemetryData *telemetry.CustomDat
|
||||
return err
|
||||
}
|
||||
|
||||
func getPurlForThePomAndDeleteIndividualBom(pomFilePath string) string {
|
||||
bomPath := filepath.Join(filepath.Dir(pomFilePath) + "/target/" + mvnBomFilename + ".xml")
|
||||
exists, _ := piperutils.FileExists(bomPath)
|
||||
if !exists {
|
||||
log.Entry().Debugf("bom file doesn't exist and hence no pURL info: %v", bomPath)
|
||||
return ""
|
||||
}
|
||||
bom, err := piperutils.GetBom(bomPath)
|
||||
if err != nil {
|
||||
log.Entry().Warnf("failed to get bom file %s: %v", bomPath, err)
|
||||
return ""
|
||||
}
|
||||
|
||||
log.Entry().Debugf("Found purl: %s for the bomPath: %s", bom.Metadata.Component.Purl, bomPath)
|
||||
purl := bom.Metadata.Component.Purl
|
||||
|
||||
// Check if the BOM is an aggregated BOM
|
||||
if !isAggregatedBOM(bom) {
|
||||
// Delete the individual BOM file
|
||||
err = os.Remove(bomPath)
|
||||
if err != nil {
|
||||
log.Entry().Warnf("failed to delete bom file %s: %v", bomPath, err)
|
||||
}
|
||||
}
|
||||
return purl
|
||||
}
|
||||
|
||||
func isAggregatedBOM(bom piperutils.Bom) bool {
|
||||
for _, property := range bom.Metadata.Properties {
|
||||
if property.Name == "maven.goal" && property.Value == "makeAggregateBom" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func createOrUpdateProjectSettingsXML(projectSettingsFile string, altDeploymentRepositoryID string, altDeploymentRepositoryUser string, altDeploymentRepositoryPassword string, utils maven.Utils) (string, error) {
|
||||
if len(projectSettingsFile) > 0 {
|
||||
projectSettingsFilePath, err := maven.UpdateProjectSettingsXML(projectSettingsFile, altDeploymentRepositoryID, altDeploymentRepositoryUser, altDeploymentRepositoryPassword, utils)
|
||||
|
@ -4,8 +4,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -157,3 +160,105 @@ func TestMavenBuild(t *testing.T) {
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestIsAggregatedBOM(t *testing.T) {
|
||||
t.Run("is aggregated BOM", func(t *testing.T) {
|
||||
bom := piperutils.Bom{
|
||||
Metadata: piperutils.Metadata{
|
||||
Properties: []piperutils.BomProperty{
|
||||
{Name: "maven.goal", Value: "makeAggregateBom"},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.True(t, isAggregatedBOM(bom))
|
||||
})
|
||||
|
||||
t.Run("is not aggregated BOM", func(t *testing.T) {
|
||||
bom := piperutils.Bom{
|
||||
Metadata: piperutils.Metadata{
|
||||
Properties: []piperutils.BomProperty{
|
||||
{Name: "some.property", Value: "someValue"},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.False(t, isAggregatedBOM(bom))
|
||||
})
|
||||
}
|
||||
|
||||
func createTempFile(t *testing.T, dir string, filename string, content string) string {
|
||||
filePath := filepath.Join(dir, filename)
|
||||
err := os.WriteFile(filePath, []byte(content), 0666)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %s", err)
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
func TestGetPurlForThePomAndDeleteIndividualBom(t *testing.T) {
|
||||
t.Run("valid BOM file, non-aggregated", func(t *testing.T) {
|
||||
tempDir, err := piperutils.Files{}.TempDir("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %s", err)
|
||||
}
|
||||
|
||||
bomContent := `<bom>
|
||||
<metadata>
|
||||
<component>
|
||||
<purl>pkg:maven/com.example/mycomponent@1.0.0</purl>
|
||||
</component>
|
||||
<properties>
|
||||
<property name="name1" value="value1" />
|
||||
</properties>
|
||||
</metadata>
|
||||
</bom>`
|
||||
pomFilePath := createTempFile(t, tempDir, "pom.xml", "")
|
||||
bomDir := filepath.Join(tempDir, "target")
|
||||
if err := os.MkdirAll(bomDir, 0777); err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %s", err)
|
||||
}
|
||||
bomFilePath := createTempFile(t, bomDir, mvnBomFilename+".xml", bomContent)
|
||||
defer os.Remove(bomFilePath)
|
||||
|
||||
purl := getPurlForThePomAndDeleteIndividualBom(pomFilePath)
|
||||
assert.Equal(t, "pkg:maven/com.example/mycomponent@1.0.0", purl)
|
||||
_, err = os.Stat(bomFilePath)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
})
|
||||
|
||||
t.Run("valid BOM file, aggregated BOM", func(t *testing.T) {
|
||||
tempDir, err := piperutils.Files{}.TempDir("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %s", err)
|
||||
}
|
||||
|
||||
bomContent := `<bom>
|
||||
<metadata>
|
||||
<component>
|
||||
<purl>pkg:maven/com.example/aggregatecomponent@1.0.0</purl>
|
||||
</component>
|
||||
<properties>
|
||||
<property name="maven.goal" value="makeAggregateBom" />
|
||||
</properties>
|
||||
</metadata>
|
||||
</bom>`
|
||||
pomFilePath := createTempFile(t, tempDir, "pom.xml", "")
|
||||
bomDir := filepath.Join(tempDir, "target")
|
||||
if err := os.MkdirAll(bomDir, 0777); err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %s", err)
|
||||
}
|
||||
bomFilePath := createTempFile(t, bomDir, mvnBomFilename+".xml", bomContent)
|
||||
|
||||
purl := getPurlForThePomAndDeleteIndividualBom(pomFilePath)
|
||||
assert.Equal(t, "pkg:maven/com.example/aggregatecomponent@1.0.0", purl)
|
||||
_, err = os.Stat(bomFilePath)
|
||||
assert.False(t, os.IsNotExist(err)) // File should not be deleted
|
||||
})
|
||||
|
||||
t.Run("BOM file does not exist", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
pomFilePath := createTempFile(t, tempDir, "pom.xml", "") // Create a temp pom file
|
||||
|
||||
purl := getPurlForThePomAndDeleteIndividualBom(pomFilePath)
|
||||
assert.Equal(t, "", purl)
|
||||
})
|
||||
}
|
||||
|
@ -217,6 +217,7 @@ func (exec *Execute) publish(packageJSON, registry, username, password string, p
|
||||
coordinate.BuildPath = filepath.Dir(packageJSON)
|
||||
coordinate.URL = registry
|
||||
coordinate.Packaging = "tgz"
|
||||
coordinate.PURL = getPurl(packageJSON)
|
||||
|
||||
*buildCoordinates = append(*buildCoordinates, coordinate)
|
||||
}
|
||||
@ -225,6 +226,21 @@ func (exec *Execute) publish(packageJSON, registry, username, password string, p
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPurl(packageJSON string) string {
|
||||
expectedBomFilePath := filepath.Join(filepath.Dir(packageJSON) + "/" + npmBomFilename)
|
||||
exists, _ := CredentialUtils.FileExists(expectedBomFilePath)
|
||||
if !exists {
|
||||
log.Entry().Debugf("bom file doesn't exist and hence no pURL info: %v", expectedBomFilePath)
|
||||
return ""
|
||||
}
|
||||
bom, err := CredentialUtils.GetBom(expectedBomFilePath)
|
||||
if err != nil {
|
||||
log.Entry().Warnf("unable to get bom metdata : %v", err)
|
||||
return ""
|
||||
}
|
||||
return bom.Metadata.Component.Purl
|
||||
}
|
||||
|
||||
func (exec *Execute) readPackageScope(packageJSON string) (string, error) {
|
||||
b, err := exec.Utils.FileRead(packageJSON)
|
||||
if err != nil {
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"github.com/SAP/jenkins-library/pkg/versioning"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
)
|
||||
|
||||
type npmMockUtilsBundleRelativeGlob struct {
|
||||
@ -573,3 +574,46 @@ func TestNpmPublish(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createTempFile(t *testing.T, dir string, filename string, content string) string {
|
||||
filePath := filepath.Join(dir, filename)
|
||||
err := os.WriteFile(filePath, []byte(content), 0666)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %s", err)
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
func TestGetPurl(t *testing.T) {
|
||||
t.Run("valid BOM file", func(t *testing.T) {
|
||||
tempDir, err := piperutils.Files{}.TempDir("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %s", err)
|
||||
}
|
||||
|
||||
bomContent := `<bom>
|
||||
<metadata>
|
||||
<component>
|
||||
<purl>pkg:npm/com.example/mycomponent@1.0.0</purl>
|
||||
</component>
|
||||
<properties>
|
||||
<property name="name1" value="value1" />
|
||||
</properties>
|
||||
</metadata>
|
||||
</bom>`
|
||||
packageJsonFilePath := createTempFile(t, tempDir, "package.json", "")
|
||||
bomFilePath := createTempFile(t, tempDir, npmBomFilename, bomContent)
|
||||
defer os.Remove(bomFilePath)
|
||||
|
||||
purl := getPurl(packageJsonFilePath)
|
||||
assert.Equal(t, "pkg:npm/com.example/mycomponent@1.0.0", purl)
|
||||
})
|
||||
|
||||
t.Run("BOM file does not exist", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
packageJsonFilePath := createTempFile(t, tempDir, "pom.xml", "") // Create a temp pom file
|
||||
|
||||
purl := getPurl(packageJsonFilePath)
|
||||
assert.Equal(t, "", purl)
|
||||
})
|
||||
}
|
||||
|
48
pkg/piperutils/cyclonedxBom.go
Normal file
48
pkg/piperutils/cyclonedxBom.go
Normal file
@ -0,0 +1,48 @@
|
||||
package piperutils
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// To serialize the cyclonedx BOM file
|
||||
type Bom struct {
|
||||
Metadata Metadata `xml:"metadata"`
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Component BomComponent `xml:"component"`
|
||||
Properties []BomProperty `xml:"properties>property"`
|
||||
}
|
||||
|
||||
type BomProperty struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
type BomComponent struct {
|
||||
Purl string `xml:"purl"`
|
||||
}
|
||||
|
||||
func GetBom(absoluteBomPath string) (Bom, error) {
|
||||
xmlFile, err := os.Open(absoluteBomPath)
|
||||
if err != nil {
|
||||
log.Entry().Debugf("failed to open bom file %s", absoluteBomPath)
|
||||
return Bom{}, err
|
||||
}
|
||||
defer xmlFile.Close()
|
||||
byteValue, err := io.ReadAll(xmlFile)
|
||||
if err != nil {
|
||||
log.Entry().Debugf("failed to read bom file %s", absoluteBomPath)
|
||||
return Bom{}, err
|
||||
}
|
||||
var bom Bom
|
||||
err = xml.Unmarshal(byteValue, &bom)
|
||||
if err != nil {
|
||||
log.Entry().Debugf("failed to unmarshal bom file %s", absoluteBomPath)
|
||||
return Bom{}, err
|
||||
}
|
||||
return bom, nil
|
||||
}
|
126
pkg/piperutils/cyclonedxbom_test.go
Normal file
126
pkg/piperutils/cyclonedxbom_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
package piperutils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func createTempFile(t *testing.T, content string) (string, func()) {
|
||||
dir := t.TempDir()
|
||||
fileName := filepath.Join(dir, "test.xml")
|
||||
err := os.WriteFile(fileName, []byte(content), 0666)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %s", err)
|
||||
}
|
||||
return fileName, func() {
|
||||
os.Remove(fileName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBom(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
xmlContent string
|
||||
expectedBom Bom
|
||||
expectError bool
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "valid file",
|
||||
xmlContent: `<bom>
|
||||
<metadata>
|
||||
<component>
|
||||
<purl>pkg:maven/com.example/mycomponent@1.0.0</purl>
|
||||
</component>
|
||||
<properties>
|
||||
<property name="name1" value="value1" />
|
||||
<property name="name2" value="value2" />
|
||||
</properties>
|
||||
</metadata>
|
||||
</bom>`,
|
||||
expectedBom: Bom{
|
||||
Metadata: Metadata{
|
||||
Component: BomComponent{
|
||||
Purl: "pkg:maven/com.example/mycomponent@1.0.0",
|
||||
},
|
||||
Properties: []BomProperty{
|
||||
{Name: "name1", Value: "value1"},
|
||||
{Name: "name2", Value: "value2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "file not found",
|
||||
xmlContent: "",
|
||||
expectedBom: Bom{},
|
||||
expectError: true,
|
||||
expectedError: "no such file or directory",
|
||||
},
|
||||
{
|
||||
name: "invalid XML file",
|
||||
xmlContent: "<bom><metadata><component><purl>invalid xml</metadata></bom>",
|
||||
expectedBom: Bom{},
|
||||
expectError: true,
|
||||
expectedError: "XML syntax error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var fileName string
|
||||
var cleanup func()
|
||||
if tt.xmlContent != "" {
|
||||
var err error
|
||||
fileName, cleanup = createTempFile(t, tt.xmlContent)
|
||||
defer cleanup()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %s", err)
|
||||
}
|
||||
} else {
|
||||
// Use a non-existent file path
|
||||
fileName = "nonexistent.xml"
|
||||
}
|
||||
|
||||
bom, err := GetBom(fileName)
|
||||
if (err != nil) != tt.expectError {
|
||||
t.Errorf("Expected error: %v, got: %v", tt.expectError, err)
|
||||
}
|
||||
|
||||
if err != nil && !tt.expectError {
|
||||
if !tt.expectError && !containsSubstring(err.Error(), tt.expectedError) {
|
||||
t.Errorf("Expected error message: %v, got: %v", tt.expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !tt.expectError && !bomEquals(bom, tt.expectedBom) {
|
||||
t.Errorf("Expected BOM: %+v, got: %+v", tt.expectedBom, bom)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func bomEquals(a, b Bom) bool {
|
||||
// compare a and b manually since reflect.DeepEqual can be problematic with slices and nil values
|
||||
return a.Metadata.Component.Purl == b.Metadata.Component.Purl &&
|
||||
len(a.Metadata.Properties) == len(b.Metadata.Properties) &&
|
||||
propertiesMatch(a.Metadata.Properties, b.Metadata.Properties)
|
||||
}
|
||||
|
||||
func propertiesMatch(a, b []BomProperty) bool {
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func containsSubstring(str, substr string) bool {
|
||||
if len(substr) == 0 {
|
||||
return true
|
||||
}
|
||||
return len(str) >= len(substr) && str[:len(substr)] == substr
|
||||
}
|
@ -19,6 +19,7 @@ type Coordinates struct {
|
||||
Packaging string
|
||||
BuildPath string
|
||||
URL string
|
||||
PURL string
|
||||
}
|
||||
|
||||
// Artifact defines the versioning operations for various build tools
|
||||
|
Loading…
x
Reference in New Issue
Block a user