1
0
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:
Manjunath 2024-10-02 09:34:34 +02:00 committed by GitHub
parent 2175d3808b
commit 5230c3d454
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 378 additions and 2 deletions

View File

@ -27,7 +27,6 @@ const (
func mavenBuild(config mavenBuildOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *mavenBuildCommonPipelineEnvironment) { func mavenBuild(config mavenBuildOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *mavenBuildCommonPipelineEnvironment) {
utils := maven.NewUtilsBundle() utils := maven.NewUtilsBundle()
// enables url-log.json creation // enables url-log.json creation
cmd := reflect.ValueOf(utils).Elem().FieldByName("Command") cmd := reflect.ValueOf(utils).Elem().FieldByName("Command")
if cmd.IsValid() { if cmd.IsValid() {
@ -62,7 +61,7 @@ func runMavenBuild(config *mavenBuildOptions, telemetryData *telemetry.CustomDat
} }
if config.CreateBOM { 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{ createBOMConfig := []string{
"-DschemaVersion=1.4", "-DschemaVersion=1.4",
"-DincludeBomSerialNumber=true", "-DincludeBomSerialNumber=true",
@ -183,6 +182,7 @@ func runMavenBuild(config *mavenBuildOptions, telemetryData *telemetry.CustomDat
} else { } else {
coordinate.BuildPath = filepath.Dir(match) coordinate.BuildPath = filepath.Dir(match)
coordinate.URL = config.AltDeploymentRepositoryURL coordinate.URL = config.AltDeploymentRepositoryURL
coordinate.PURL = getPurlForThePomAndDeleteIndividualBom(match)
buildCoordinates = append(buildCoordinates, coordinate) buildCoordinates = append(buildCoordinates, coordinate)
} }
} }
@ -209,6 +209,42 @@ func runMavenBuild(config *mavenBuildOptions, telemetryData *telemetry.CustomDat
return err 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) { func createOrUpdateProjectSettingsXML(projectSettingsFile string, altDeploymentRepositoryID string, altDeploymentRepositoryUser string, altDeploymentRepositoryPassword string, utils maven.Utils) (string, error) {
if len(projectSettingsFile) > 0 { if len(projectSettingsFile) > 0 {
projectSettingsFilePath, err := maven.UpdateProjectSettingsXML(projectSettingsFile, altDeploymentRepositoryID, altDeploymentRepositoryUser, altDeploymentRepositoryPassword, utils) projectSettingsFilePath, err := maven.UpdateProjectSettingsXML(projectSettingsFile, altDeploymentRepositoryID, altDeploymentRepositoryUser, altDeploymentRepositoryPassword, utils)

View File

@ -4,8 +4,11 @@
package cmd package cmd
import ( import (
"os"
"path/filepath"
"testing" "testing"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/stretchr/testify/assert" "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)
})
}

View File

@ -217,6 +217,7 @@ func (exec *Execute) publish(packageJSON, registry, username, password string, p
coordinate.BuildPath = filepath.Dir(packageJSON) coordinate.BuildPath = filepath.Dir(packageJSON)
coordinate.URL = registry coordinate.URL = registry
coordinate.Packaging = "tgz" coordinate.Packaging = "tgz"
coordinate.PURL = getPurl(packageJSON)
*buildCoordinates = append(*buildCoordinates, coordinate) *buildCoordinates = append(*buildCoordinates, coordinate)
} }
@ -225,6 +226,21 @@ func (exec *Execute) publish(packageJSON, registry, username, password string, p
return nil 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) { func (exec *Execute) readPackageScope(packageJSON string) (string, error) {
b, err := exec.Utils.FileRead(packageJSON) b, err := exec.Utils.FileRead(packageJSON)
if err != nil { if err != nil {

View File

@ -12,6 +12,7 @@ import (
"github.com/SAP/jenkins-library/pkg/piperutils" "github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/versioning" "github.com/SAP/jenkins-library/pkg/versioning"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"os"
) )
type npmMockUtilsBundleRelativeGlob struct { 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)
})
}

View 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
}

View 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
}

View File

@ -19,6 +19,7 @@ type Coordinates struct {
Packaging string Packaging string
BuildPath string BuildPath string
URL string URL string
PURL string
} }
// Artifact defines the versioning operations for various build tools // Artifact defines the versioning operations for various build tools