1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-16 05:16:08 +02:00

feat: add build artifacts metadata for mtaBuild (#5166)

This commit is contained in:
phgermanov 2024-11-04 12:30:39 +02:00 committed by GitHub
parent da609e1536
commit 6988f43f7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 401 additions and 306 deletions

View File

@ -41,7 +41,7 @@ func mavenBuild(config mavenBuildOptions, telemetryData *telemetry.CustomData, c
}
func runMakeBOMGoal(config *mavenBuildOptions, utils maven.Utils) error {
var flags = []string{"-update-snapshots", "--batch-mode"}
flags := []string{"-update-snapshots", "--batch-mode"}
if len(config.Profiles) > 0 {
flags = append(flags, "--activate-profiles", strings.Join(config.Profiles, ","))
}
@ -89,8 +89,7 @@ func runMakeBOMGoal(config *mavenBuildOptions, utils maven.Utils) error {
}
func runMavenBuild(config *mavenBuildOptions, _ *telemetry.CustomData, utils maven.Utils, commonPipelineEnvironment *mavenBuildCommonPipelineEnvironment) error {
var flags = []string{"-update-snapshots", "--batch-mode"}
flags := []string{"-update-snapshots", "--batch-mode"}
if len(config.Profiles) > 0 {
flags = append(flags, "--activate-profiles", strings.Join(config.Profiles, ","))
@ -255,7 +254,7 @@ func createBuildArtifactsMetadata(config *mavenBuildOptions, commonPipelineEnvir
} else {
coordinate.BuildPath = filepath.Dir(match)
coordinate.URL = config.AltDeploymentRepositoryURL
coordinate.PURL = getPurlForThePom(match)
coordinate.PURL = piperutils.GetPurl(filepath.Join(filepath.Dir(match), "/target/"+mvnSimpleBomFilename+".xml"))
buildCoordinates = append(buildCoordinates, coordinate)
}
}
@ -274,25 +273,6 @@ func createBuildArtifactsMetadata(config *mavenBuildOptions, commonPipelineEnvir
return nil, false
}
func getPurlForThePom(pomFilePath string) string {
bomPath := filepath.Join(filepath.Dir(pomFilePath) + "/target/" + mvnSimpleBomFilename + ".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
return purl
}
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)
@ -310,7 +290,7 @@ func createOrUpdateProjectSettingsXML(projectSettingsFile string, altDeploymentR
}
func loadRemoteRepoCertificates(certificateList []string, client piperhttp.Downloader, flags *[]string, runner command.ExecRunner, fileUtils piperutils.FileUtils, javaCaCertFilePath string) error {
//TODO: make use of java/keytool package
// TODO: make use of java/keytool package
existingJavaCaCerts := filepath.Join(os.Getenv("JAVA_HOME"), "jre", "lib", "security", "cacerts")
if len(javaCaCertFilePath) > 0 {
@ -318,7 +298,6 @@ func loadRemoteRepoCertificates(certificateList []string, client piperhttp.Downl
}
exists, err := fileUtils.FileExists(existingJavaCaCerts)
if err != nil {
return errors.Wrap(err, "Could not find the existing java cacerts")
}

View File

@ -4,18 +4,14 @@
package cmd
import (
"os"
"path/filepath"
"testing"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/stretchr/testify/assert"
)
var cpe mavenBuildCommonPipelineEnvironment
func TestMavenBuild(t *testing.T) {
t.Run("mavenBuild should install the artifact", func(t *testing.T) {
mockedUtils := newMavenMockUtils()
@ -123,54 +119,4 @@ func TestMavenBuild(t *testing.T) {
assert.Equal(t, mockedUtils.Calls[0].Exec, "mvn")
assert.Empty(t, cpe.custom.mavenBuildArtifacts)
})
}
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, 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, mvnSimpleBomFilename+".xml", bomContent)
purl := getPurlForThePom(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 := getPurlForThePom(pomFilePath)
assert.Equal(t, "", purl)
})
}

View File

@ -5,6 +5,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
@ -14,8 +15,10 @@ import (
"text/template"
"time"
"github.com/SAP/jenkins-library/pkg/build"
"github.com/SAP/jenkins-library/pkg/buildsettings"
"github.com/SAP/jenkins-library/pkg/npm"
"github.com/SAP/jenkins-library/pkg/versioning"
"github.com/SAP/jenkins-library/pkg/command"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
@ -53,7 +56,7 @@ const (
NEO MTABuildTarget = iota
// CF ...
CF MTABuildTarget = iota
//XSA ...
// XSA ...
XSA MTABuildTarget = iota
)
@ -67,7 +70,7 @@ func ValueOfBuildTarget(str string) (MTABuildTarget, error) {
case "XSA":
return XSA, nil
default:
return -1, fmt.Errorf("Unknown Platform: '%s'", str)
return -1, fmt.Errorf("unknown platform: '%s'", str)
}
}
@ -94,6 +97,9 @@ type mtaBuildUtils interface {
SetNpmRegistries(defaultNpmRegistry string) error
InstallAllDependencies(defaultNpmRegistry string) error
Open(name string) (io.ReadWriteCloser, error)
SendRequest(method, url string, body io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error)
}
type mtaBuildUtilsBundle struct {
@ -131,9 +137,7 @@ func newMtaBuildUtilsBundle() mtaBuildUtils {
return &utils
}
func mtaBuild(config mtaBuildOptions,
telemetryData *telemetry.CustomData,
commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment) {
func mtaBuild(config mtaBuildOptions, _ *telemetry.CustomData, commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment) {
log.Entry().Debugf("Launching mta build")
utils := newMtaBuildUtilsBundle()
@ -145,39 +149,31 @@ func mtaBuild(config mtaBuildOptions,
}
}
func runMtaBuild(config mtaBuildOptions,
commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment,
utils mtaBuildUtils) error {
var err error
err = handleSettingsFiles(config, utils)
if err != nil {
func runMtaBuild(config mtaBuildOptions, commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment, utils mtaBuildUtils) error {
if err := handleSettingsFiles(config, utils); err != nil {
return err
}
err = handleActiveProfileUpdate(config, utils)
if err != nil {
if err := handleActiveProfileUpdate(config, utils); err != nil {
return err
}
err = utils.SetNpmRegistries(config.DefaultNpmRegistry)
if err := utils.SetNpmRegistries(config.DefaultNpmRegistry); err != nil {
return err
}
mtaYamlFile := filepath.Join(getSourcePath(config), "mta.yaml")
mtaYamlFileExists, err := utils.FileExists(mtaYamlFile)
if err != nil {
return err
}
if !mtaYamlFileExists {
if err = createMtaYamlFile(mtaYamlFile, config.ApplicationName, utils); err != nil {
return err
}
} else {
log.Entry().Infof("\"%s\" file found in project sources", mtaYamlFile)
log.Entry().Infof(`"%s" file found in project sources`, mtaYamlFile)
}
if config.EnableSetTimestamp {
@ -187,20 +183,17 @@ func runMtaBuild(config mtaBuildOptions,
}
mtarName, isMtarNativelySuffixed, err := getMtarName(config, mtaYamlFile, utils)
if err != nil {
return err
}
var call []string
platform, err := ValueOfBuildTarget(config.Platform)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return err
}
call = append(call, "mbt", "build", "--mtar", mtarName, "--platform", platform.String())
call := []string{"mbt", "build", "--mtar", mtarName, "--platform", platform.String()}
if len(config.Extensions) != 0 {
call = append(call, fmt.Sprintf("--extensions=%s", config.Extensions))
}
@ -229,7 +222,7 @@ func runMtaBuild(config mtaBuildOptions,
utils.AppendEnv([]string{"MAVEN_OPTS=-Dmaven.repo.local=" + absolutePath})
}
log.Entry().Infof("Executing mta build call: \"%s\"", strings.Join(call, " "))
log.Entry().Infof(`Executing mta build call: "%s"`, strings.Join(call, " "))
if err := utils.RunExecutable(call[0], call[1:]...); err != nil {
log.SetErrorCategory(log.ErrorBuild)
@ -261,65 +254,97 @@ func runMtaBuild(config mtaBuildOptions,
commonPipelineEnvironment.custom.mtaBuildToolDesc = filepath.ToSlash(mtaYamlFile)
if config.InstallArtifacts {
// install maven artifacts in local maven repo because `mbt build` executes `mvn package -B`
err = installMavenArtifacts(utils, config)
if err != nil {
if err = installMavenArtifacts(utils, config); err != nil {
return err
}
// mta-builder executes 'npm install --production', therefore we need 'npm ci/install' to install the dev-dependencies
err = utils.InstallAllDependencies(config.DefaultNpmRegistry)
if err != nil {
if err = utils.InstallAllDependencies(config.DefaultNpmRegistry); err != nil {
return err
}
}
if config.Publish {
log.Entry().Infof("publish detected")
if (len(config.MtaDeploymentRepositoryPassword) > 0) && (len(config.MtaDeploymentRepositoryUser) > 0) &&
(len(config.MtaDeploymentRepositoryURL) > 0) {
if (len(config.MtarGroup) > 0) && (len(config.Version) > 0) {
httpClient := &piperhttp.Client{}
credentialsEncoded := "Basic " + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", config.MtaDeploymentRepositoryUser, config.MtaDeploymentRepositoryPassword)))
headers := http.Header{}
headers.Add("Authorization", credentialsEncoded)
config.MtarGroup = strings.ReplaceAll(config.MtarGroup, ".", "/")
mtarArtifactName := mtarName
// only trim the .mtar suffix from the mtarName
if !isMtarNativelySuffixed {
mtarArtifactName = strings.TrimSuffix(mtarArtifactName, ".mtar")
}
config.MtaDeploymentRepositoryURL += config.MtarGroup + "/" + mtarArtifactName + "/" + config.Version + "/" + fmt.Sprintf("%v-%v.%v", mtarArtifactName, config.Version, "mtar")
commonPipelineEnvironment.custom.mtarPublishedURL = config.MtaDeploymentRepositoryURL
log.Entry().Infof("pushing mtar artifact to repository : %s", config.MtaDeploymentRepositoryURL)
data, err := os.Open(getMtarFilePath(config, mtarName))
if err != nil {
return errors.Wrap(err, "failed to open mtar archive for upload")
}
_, httpErr := httpClient.SendRequest("PUT", config.MtaDeploymentRepositoryURL, data, headers, nil)
if httpErr != nil {
return errors.Wrap(err, "failed to upload mtar to repository")
}
} else {
return errors.New("mtarGroup, version not found and must be present")
}
} else {
return errors.New("mtaDeploymentRepositoryUser, mtaDeploymentRepositoryPassword and mtaDeploymentRepositoryURL not found , must be present")
if err = handlePublish(config, commonPipelineEnvironment, utils, mtarName, isMtarNativelySuffixed); err != nil {
return err
}
} else {
log.Entry().Infof("no publish detected, skipping upload of mtar artifact")
}
return err
return nil
}
func handlePublish(config mtaBuildOptions, commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment, utils mtaBuildUtils, mtarName string, isMtarNativelySuffixed bool) error {
log.Entry().Infof("publish detected")
if len(config.MtaDeploymentRepositoryPassword) == 0 ||
len(config.MtaDeploymentRepositoryUser) == 0 ||
len(config.MtaDeploymentRepositoryURL) == 0 {
return errors.New("mtaDeploymentRepositoryUser, mtaDeploymentRepositoryPassword and mtaDeploymentRepositoryURL not found, must be present")
}
if len(config.MtarGroup) == 0 || len(config.Version) == 0 {
return errors.New("mtarGroup, version not found and must be present")
}
credentialsEncoded := "Basic " + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", config.MtaDeploymentRepositoryUser, config.MtaDeploymentRepositoryPassword)))
headers := http.Header{}
headers.Add("Authorization", credentialsEncoded)
config.MtarGroup = strings.ReplaceAll(config.MtarGroup, ".", "/")
mtarArtifactName := mtarName
if !isMtarNativelySuffixed {
mtarArtifactName = strings.TrimSuffix(mtarArtifactName, ".mtar")
}
config.MtaDeploymentRepositoryURL += config.MtarGroup + "/" + mtarArtifactName + "/" + config.Version + "/" + fmt.Sprintf("%v-%v.%v", mtarArtifactName, config.Version, "mtar")
commonPipelineEnvironment.custom.mtarPublishedURL = config.MtaDeploymentRepositoryURL
log.Entry().Infof("pushing mtar artifact to repository : %s", config.MtaDeploymentRepositoryURL)
mtarPath := getMtarFilePath(config, mtarName)
data, err := utils.Open(mtarPath)
if err != nil {
return errors.Wrap(err, "failed to open mtar archive for upload")
}
defer data.Close()
if _, httpErr := utils.SendRequest("PUT", config.MtaDeploymentRepositoryURL, data, headers, nil); httpErr != nil {
return errors.Wrap(httpErr, "failed to upload mtar to repository")
}
if config.CreateBuildArtifactsMetadata {
if err := buildArtifactsMetadata(config, commonPipelineEnvironment, mtarPath); err != nil {
log.Entry().Warnf("unable to create build artifacts metadata: %v", err)
return nil
}
}
return nil
}
func buildArtifactsMetadata(config mtaBuildOptions, commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment, mtarPath string) error {
mtarDir := filepath.Dir(mtarPath)
buildArtifacts := build.BuildArtifacts{
Coordinates: []versioning.Coordinates{
{
GroupID: config.MtarGroup,
ArtifactID: config.MtarName,
Version: config.Version,
Packaging: "mtar",
BuildPath: getSourcePath(config),
URL: config.MtaDeploymentRepositoryURL,
PURL: piperutils.GetPurl(filepath.Join(mtarDir, "sbom-gen/bom-mta.xml")),
},
},
}
jsonResult, err := json.Marshal(buildArtifacts)
if err != nil {
return fmt.Errorf("failed to marshal build artifacts: %v", err)
}
commonPipelineEnvironment.custom.mtaBuildArtifacts = string(jsonResult)
return nil
}
func handleActiveProfileUpdate(config mtaBuildOptions, utils mtaBuildUtils) error {
@ -355,15 +380,12 @@ func addNpmBinToPath(utils mtaBuildUtils) error {
}
func getMtarName(config mtaBuildOptions, mtaYamlFile string, utils mtaBuildUtils) (string, bool, error) {
mtarName := config.MtarName
isMtarNativelySuffixed := false
if len(mtarName) == 0 {
log.Entry().Debugf("mtar name not provided via config. Extracting from file \"%s\"", mtaYamlFile)
log.Entry().Debugf(`mtar name not provided via config. Extracting from file "%s"`, mtaYamlFile)
mtaID, err := getMtaID(mtaYamlFile, utils)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return "", isMtarNativelySuffixed, err
@ -371,10 +393,10 @@ func getMtarName(config mtaBuildOptions, mtaYamlFile string, utils mtaBuildUtils
if len(mtaID) == 0 {
log.SetErrorCategory(log.ErrorConfiguration)
return "", isMtarNativelySuffixed, fmt.Errorf("Invalid mtar ID. Was empty")
return "", isMtarNativelySuffixed, fmt.Errorf("invalid mtar ID. Was empty")
}
log.Entry().Debugf("mtar name extracted from file \"%s\": \"%s\"", mtaYamlFile, mtaID)
log.Entry().Debugf(`mtar name extracted from file "%s": "%s"`, mtaYamlFile, mtaID)
// there can be cases where the mtaId itself has the value com.myComapany.mtar , adding an extra .mtar causes .mtar.mtar
if !strings.HasSuffix(mtaID, ".mtar") {
@ -387,11 +409,9 @@ func getMtarName(config mtaBuildOptions, mtaYamlFile string, utils mtaBuildUtils
}
return mtarName, isMtarNativelySuffixed, nil
}
func setTimeStamp(mtaYamlFile string, utils mtaBuildUtils) error {
mtaYaml, err := utils.FileRead(mtaYamlFile)
if err != nil {
return err
@ -406,9 +426,9 @@ func setTimeStamp(mtaYamlFile string, utils mtaBuildUtils) error {
log.SetErrorCategory(log.ErrorConfiguration)
return err
}
log.Entry().Infof("Timestamp replaced in \"%s\"", mtaYamlFile)
log.Entry().Infof(`Timestamp replaced in "%s"`, mtaYamlFile)
} else {
log.Entry().Infof("No timestamp contained in \"%s\". File has not been modified.", mtaYamlFile)
log.Entry().Infof(`No timestamp contained in "%s". File has not been modified.`, mtaYamlFile)
}
return nil
@ -420,14 +440,16 @@ func getTimestamp() string {
}
func createMtaYamlFile(mtaYamlFile, applicationName string, utils mtaBuildUtils) error {
log.Entry().Infof("\"%s\" file not found in project sources", mtaYamlFile)
log.Entry().Infof(`"%s" file not found in project sources`, mtaYamlFile)
if len(applicationName) == 0 {
return fmt.Errorf("'%[1]s' not found in project sources and 'applicationName' not provided as parameter - cannot generate '%[1]s' file", mtaYamlFile)
}
packageFileExists, err := utils.FileExists("package.json")
if err != nil {
return err
}
if !packageFileExists {
return fmt.Errorf("package.json file does not exist")
}
@ -437,16 +459,18 @@ func createMtaYamlFile(mtaYamlFile, applicationName string, utils mtaBuildUtils)
if err != nil {
return err
}
json.Unmarshal(pContent, &result)
if err := json.Unmarshal(pContent, &result); err != nil {
return fmt.Errorf("failed to unmarshal package.json: %w", err)
}
version, ok := result["version"].(string)
if !ok {
return fmt.Errorf("Version not found in \"package.json\" (or wrong type)")
return fmt.Errorf(`version not found in "package.json" (or wrong type)`)
}
name, ok := result["name"].(string)
if !ok {
return fmt.Errorf("Name not found in \"package.json\" (or wrong type)")
return fmt.Errorf(`name not found in "package.json" (or wrong type)`)
}
mtaConfig, err := generateMta(name, applicationName, version)
@ -457,7 +481,7 @@ func createMtaYamlFile(mtaYamlFile, applicationName string, utils mtaBuildUtils)
if err := utils.FileWrite(mtaYamlFile, []byte(mtaConfig), 0644); err != nil {
return fmt.Errorf("failed to write %v: %w", mtaYamlFile, err)
}
log.Entry().Infof("\"%s\" created.", mtaYamlFile)
log.Entry().Infof(`"%s" created.`, mtaYamlFile)
return nil
}
@ -467,15 +491,14 @@ func handleSettingsFiles(config mtaBuildOptions, utils mtaBuildUtils) error {
}
func generateMta(id, applicationName, version string) (string, error) {
if len(id) == 0 {
return "", fmt.Errorf("Generating mta file: ID not provided")
return "", fmt.Errorf("generating mta file: ID not provided")
}
if len(applicationName) == 0 {
return "", fmt.Errorf("Generating mta file: ApplicationName not provided")
return "", fmt.Errorf("generating mta file: ApplicationName not provided")
}
if len(version) == 0 {
return "", fmt.Errorf("Generating mta file: Version not provided")
return "", fmt.Errorf("generating mta file: Version not provided")
}
tmpl, e := template.New("mta.yaml").Parse(templateMtaYml)
@ -499,7 +522,6 @@ func generateMta(id, applicationName, version string) (string, error) {
}
func getMtaID(mtaYamlFile string, utils mtaBuildUtils) (string, error) {
var result map[string]interface{}
p, err := utils.FileRead(mtaYamlFile)
if err != nil {
@ -512,7 +534,7 @@ func getMtaID(mtaYamlFile string, utils mtaBuildUtils) (string, error) {
id, ok := result["ID"].(string)
if !ok || len(id) == 0 {
return "", fmt.Errorf("Id not found in mta yaml file (or wrong type)")
return "", fmt.Errorf("id not found in mta yaml file (or wrong type)")
}
return id, nil

View File

@ -45,6 +45,7 @@ type mtaBuildOptions struct {
BuildSettingsInfo string `json:"buildSettingsInfo,omitempty"`
CreateBOM bool `json:"createBOM,omitempty"`
EnableSetTimestamp bool `json:"enableSetTimestamp,omitempty"`
CreateBuildArtifactsMetadata bool `json:"createBuildArtifactsMetadata,omitempty"`
}
type mtaBuildCommonPipelineEnvironment struct {
@ -53,6 +54,7 @@ type mtaBuildCommonPipelineEnvironment struct {
mtaBuildToolDesc string
mtarPublishedURL string
buildSettingsInfo string
mtaBuildArtifacts string
}
}
@ -66,6 +68,7 @@ func (p *mtaBuildCommonPipelineEnvironment) persist(path, resourceName string) {
{category: "custom", name: "mtaBuildToolDesc", value: p.custom.mtaBuildToolDesc},
{category: "custom", name: "mtarPublishedUrl", value: p.custom.mtarPublishedURL},
{category: "custom", name: "buildSettingsInfo", value: p.custom.buildSettingsInfo},
{category: "custom", name: "mtaBuildArtifacts", value: p.custom.mtaBuildArtifacts},
}
errCount := 0
@ -266,6 +269,7 @@ func addMtaBuildFlags(cmd *cobra.Command, stepConfig *mtaBuildOptions) {
cmd.Flags().StringVar(&stepConfig.BuildSettingsInfo, "buildSettingsInfo", os.Getenv("PIPER_buildSettingsInfo"), "build settings info is typically filled by the step automatically to create information about the build settings that were used during the mta build . This information is typically used for compliance related processes.")
cmd.Flags().BoolVar(&stepConfig.CreateBOM, "createBOM", false, "Creates the bill of materials (BOM) using CycloneDX plugin.")
cmd.Flags().BoolVar(&stepConfig.EnableSetTimestamp, "enableSetTimestamp", true, "Enables setting the timestamp in the `mta.yaml` when it contains `${timestamp}`. Disable this when you want the MTA Deploy Service to do this instead.")
cmd.Flags().BoolVar(&stepConfig.CreateBuildArtifactsMetadata, "createBuildArtifactsMetadata", false, "metadata about the artifacts that are build and published, this metadata is generally used by steps downstream in the pipeline")
}
@ -529,6 +533,15 @@ func mtaBuildMetadata() config.StepData {
Aliases: []config.Alias{},
Default: true,
},
{
Name: "createBuildArtifactsMetadata",
ResourceRef: []config.ResourceReference{},
Scope: []string{"STEPS", "STAGES", "PARAMETERS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Default: false,
},
},
},
Containers: []config.Container{
@ -544,6 +557,7 @@ func mtaBuildMetadata() config.StepData {
{"name": "custom/mtaBuildToolDesc"},
{"name": "custom/mtarPublishedUrl"},
{"name": "custom/buildSettingsInfo"},
{"name": "custom/mtaBuildArtifacts"},
},
},
{

View File

@ -1,12 +1,15 @@
package cmd
import (
"bytes"
"errors"
"io"
"net/http"
"os"
"path/filepath"
"testing"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
@ -18,6 +21,8 @@ type mtaBuildTestUtilsBundle struct {
projectSettingsFile string
globalSettingsFile string
registryUsedInSetNpmRegistries string
openReturns string
sendRequestReturns func() (*http.Response, error)
}
func (m *mtaBuildTestUtilsBundle) SetNpmRegistries(defaultNpmRegistry string) error {
@ -26,7 +31,7 @@ func (m *mtaBuildTestUtilsBundle) SetNpmRegistries(defaultNpmRegistry string) er
}
func (m *mtaBuildTestUtilsBundle) InstallAllDependencies(defaultNpmRegistry string) error {
return errors.New("Test should not install dependencies.") //TODO implement test
return errors.New("Test should not install dependencies.") // TODO implement test
}
func (m *mtaBuildTestUtilsBundle) DownloadAndCopySettingsFiles(globalSettingsFile string, projectSettingsFile string) error {
@ -39,6 +44,36 @@ func (m *mtaBuildTestUtilsBundle) DownloadFile(url, filename string, header http
return errors.New("Test should not download files.")
}
func (m *mtaBuildTestUtilsBundle) Open(name string) (io.ReadWriteCloser, error) {
if m.openReturns != "" {
return NewMockReadCloser(m.openReturns), nil
}
return nil, errors.New("Test should not open files.")
}
// MockReadCloser is a struct that implements io.ReadCloser
type MockReadWriteCloser struct {
io.Reader
io.Writer
}
// Close is a no-op method to satisfy the io.Closer interface
func (m *MockReadWriteCloser) Close() error {
return nil
}
// NewMockReadCloser returns a new MockReadCloser with the given data
func NewMockReadCloser(data string) io.ReadWriteCloser {
return &MockReadWriteCloser{Reader: bytes.NewBufferString(data)}
}
func (m *mtaBuildTestUtilsBundle) SendRequest(method, url string, body io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) {
if m.sendRequestReturns != nil {
return m.sendRequestReturns()
}
return nil, errors.New("Test should not send requests.")
}
func newMtaBuildTestUtilsBundle() *mtaBuildTestUtilsBundle {
utilsBundle := mtaBuildTestUtilsBundle{
ExecMockRunner: &mock.ExecMockRunner{},
@ -48,11 +83,11 @@ func newMtaBuildTestUtilsBundle() *mtaBuildTestUtilsBundle {
}
func TestMtaBuild(t *testing.T) {
cpe := mtaBuildCommonPipelineEnvironment{}
SetConfigOptions(ConfigCommandOptions{
OpenFile: config.OpenPiperFile,
})
t.Run("Application name not set", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
options := mtaBuildOptions{}
@ -60,15 +95,20 @@ func TestMtaBuild(t *testing.T) {
assert.NotNil(t, err)
assert.Equal(t, "'mta.yaml' not found in project sources and 'applicationName' not provided as parameter - cannot generate 'mta.yaml' file", err.Error())
})
t.Run("Provide default npm registry", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
options := mtaBuildOptions{ApplicationName: "myApp", Platform: "CF", DefaultNpmRegistry: "https://example.org/npm", MtarName: "myName", Source: "./", Target: "./"}
options := mtaBuildOptions{
ApplicationName: "myApp",
Platform: "CF",
DefaultNpmRegistry: "https://example.org/npm",
MtarName: "myName",
Source: "./",
Target: "./",
}
utilsMock.AddFile("package.json", []byte("{\"name\": \"myName\", \"version\": \"1.2.3\"}"))
utilsMock.AddFile("package.json", []byte(`{"name": "myName", "version": "1.2.3"}`))
err := runMtaBuild(options, &cpe, utilsMock)
@ -78,7 +118,6 @@ func TestMtaBuild(t *testing.T) {
})
t.Run("Package json does not exist", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
options := mtaBuildOptions{ApplicationName: "myApp"}
@ -88,16 +127,21 @@ func TestMtaBuild(t *testing.T) {
assert.NotNil(t, err)
assert.Equal(t, "package.json file does not exist", err.Error())
})
t.Run("Write yaml file", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
options := mtaBuildOptions{ApplicationName: "myApp", Platform: "CF", MtarName: "myName", Source: "./", Target: "./", EnableSetTimestamp: true}
options := mtaBuildOptions{
ApplicationName: "myApp",
Platform: "CF",
MtarName: "myName",
Source: "./",
Target: "./",
EnableSetTimestamp: true,
}
utilsMock.AddFile("package.json", []byte("{\"name\": \"myName\", \"version\": \"1.2.3\"}"))
utilsMock.AddFile("package.json", []byte(`{"name": "myName", "version": "1.2.3"}`))
err := runMtaBuild(options, &cpe, utilsMock)
@ -125,16 +169,14 @@ func TestMtaBuild(t *testing.T) {
assert.Equal(t, "myApp", result.Modules[0].Name)
assert.Regexp(t, "^1\\.2\\.3-[\\d]{14}$", result.Modules[0].Parameters["version"])
assert.Equal(t, "myApp", result.Modules[0].Parameters["name"])
})
t.Run("Dont write mta yaml file when already present no timestamp placeholder", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
options := mtaBuildOptions{ApplicationName: "myApp"}
utilsMock.AddFile("package.json", []byte("{\"name\": \"myName\", \"version\": \"1.2.3\"}"))
utilsMock.AddFile("package.json", []byte(`{"name": "myName", "version": "1.2.3"}`))
utilsMock.AddFile("mta.yaml", []byte("already there"))
_ = runMtaBuild(options, &cpe, utilsMock)
@ -143,12 +185,14 @@ func TestMtaBuild(t *testing.T) {
})
t.Run("Write mta yaml file when already present with timestamp placeholder", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
options := mtaBuildOptions{ApplicationName: "myApp", EnableSetTimestamp: true}
options := mtaBuildOptions{
ApplicationName: "myApp",
EnableSetTimestamp: true,
}
utilsMock.AddFile("package.json", []byte("{\"name\": \"myName\", \"version\": \"1.2.3\"}"))
utilsMock.AddFile("package.json", []byte(`{"name": "myName", "version": "1.2.3"}`))
utilsMock.AddFile("mta.yaml", []byte("already there with-${timestamp}"))
_ = runMtaBuild(options, &cpe, utilsMock)
@ -157,14 +201,13 @@ func TestMtaBuild(t *testing.T) {
})
t.Run("Mta build mbt toolset", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
cpe.mtarFilePath = ""
options := mtaBuildOptions{ApplicationName: "myApp", Platform: "CF", MtarName: "myName.mtar", Source: "./", Target: "./"}
utilsMock.AddFile("package.json", []byte("{\"name\": \"myName\", \"version\": \"1.2.3\"}"))
utilsMock.AddFile("package.json", []byte(`{"name": "myName", "version": "1.2.3"}`))
err := runMtaBuild(options, &cpe, utilsMock)
@ -179,14 +222,19 @@ func TestMtaBuild(t *testing.T) {
t.Run("Source and target related tests", func(t *testing.T) {
t.Run("Mta build mbt toolset with custom source and target paths", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
cpe.mtarFilePath = ""
options := mtaBuildOptions{ApplicationName: "myApp", Platform: "CF", MtarName: "myName.mtar", Source: "mySourcePath/", Target: "myTargetPath/"}
options := mtaBuildOptions{
ApplicationName: "myApp",
Platform: "CF",
MtarName: "myName.mtar",
Source: "mySourcePath/",
Target: "myTargetPath/",
}
utilsMock.AddFile("package.json", []byte("{\"name\": \"myName\", \"version\": \"1.2.3\"}"))
utilsMock.AddFile("package.json", []byte(`{"name": "myName", "version": "1.2.3"}`))
err := runMtaBuild(options, &cpe, utilsMock)
@ -194,9 +242,11 @@ func TestMtaBuild(t *testing.T) {
if assert.Len(t, utilsMock.Calls, 1) {
assert.Equal(t, "mbt", utilsMock.Calls[0].Exec)
assert.Equal(t, []string{"build", "--mtar", "myName.mtar", "--platform", "CF",
assert.Equal(t, []string{
"build", "--mtar", "myName.mtar", "--platform", "CF",
"--source", filepath.FromSlash("mySourcePath/"),
"--target", filepath.Join(_ignoreError(os.Getwd()), filepath.FromSlash("mySourcePath/myTargetPath/"))},
"--target", filepath.Join(_ignoreError(os.Getwd()), filepath.FromSlash("mySourcePath/myTargetPath/")),
},
utilsMock.Calls[0].Params)
}
assert.Equal(t, "mySourcePath/myTargetPath/myName.mtar", cpe.mtarFilePath)
@ -206,14 +256,20 @@ func TestMtaBuild(t *testing.T) {
t.Run("M2Path related tests", func(t *testing.T) {
t.Run("Mta build mbt toolset with m2Path", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.CurrentDir = "root_folder/workspace"
cpe.mtarFilePath = ""
options := mtaBuildOptions{ApplicationName: "myApp", Platform: "CF", MtarName: "myName.mtar", Source: "./", Target: "./", M2Path: ".pipeline/local_repo"}
options := mtaBuildOptions{
ApplicationName: "myApp",
Platform: "CF",
MtarName: "myName.mtar",
Source: "./",
Target: "./",
M2Path: ".pipeline/local_repo",
}
utilsMock.AddFile("mta.yaml", []byte("ID: \"myNameFromMtar\""))
utilsMock.AddFile("mta.yaml", []byte(`ID: "myNameFromMtar"`))
err := runMtaBuild(options, &cpe, utilsMock)
@ -223,13 +279,18 @@ func TestMtaBuild(t *testing.T) {
})
t.Run("Settings file releatd tests", func(t *testing.T) {
t.Run("Copy global settings file", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.AddFile("mta.yaml", []byte("ID: \"myNameFromMtar\""))
utilsMock.AddFile("mta.yaml", []byte(`ID: "myNameFromMtar"`))
options := mtaBuildOptions{ApplicationName: "myApp", GlobalSettingsFile: "/opt/maven/settings.xml", Platform: "CF", MtarName: "myName", Source: "./", Target: "./"}
options := mtaBuildOptions{
ApplicationName: "myApp",
GlobalSettingsFile: "/opt/maven/settings.xml",
Platform: "CF",
MtarName: "myName",
Source: "./",
Target: "./",
}
err := runMtaBuild(options, &cpe, utilsMock)
@ -240,9 +301,8 @@ func TestMtaBuild(t *testing.T) {
})
t.Run("Copy project settings file", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.AddFile("mta.yaml", []byte("ID: \"myNameFromMtar\""))
utilsMock.AddFile("mta.yaml", []byte(`ID: "myNameFromMtar"`))
options := mtaBuildOptions{ApplicationName: "myApp", ProjectSettingsFile: "/my/project/settings.xml", Platform: "CF", MtarName: "myName", Source: "./", Target: "./"}
@ -256,36 +316,102 @@ func TestMtaBuild(t *testing.T) {
})
t.Run("publish related tests", func(t *testing.T) {
t.Run("error when no repository url", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.AddFile("mta.yaml", []byte("ID: \"myNameFromMtar\""))
utilsMock.AddFile("mta.yaml", []byte(`ID: "myNameFromMtar"`))
options := mtaBuildOptions{ApplicationName: "myApp", GlobalSettingsFile: "/opt/maven/settings.xml", Platform: "CF", MtarName: "myName", Source: "./", Target: "./", Publish: true}
options := mtaBuildOptions{
ApplicationName: "myApp",
GlobalSettingsFile: "/opt/maven/settings.xml",
Platform: "CF",
MtarName: "myName",
Source: "./",
Target: "./",
Publish: true,
}
err := runMtaBuild(options, &cpe, utilsMock)
assert.Equal(t, "mtaDeploymentRepositoryUser, mtaDeploymentRepositoryPassword and mtaDeploymentRepositoryURL not found , must be present", err.Error())
assert.Equal(t, "mtaDeploymentRepositoryUser, mtaDeploymentRepositoryPassword and mtaDeploymentRepositoryURL not found, must be present", err.Error())
})
t.Run("error when no mtar group", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.AddFile("mta.yaml", []byte("ID: \"myNameFromMtar\""))
utilsMock.AddFile("mta.yaml", []byte(`ID: "myNameFromMtar"`))
options := mtaBuildOptions{ApplicationName: "myApp", GlobalSettingsFile: "/opt/maven/settings.xml", Platform: "CF", MtarName: "myName", Source: "./", Target: "./", Publish: true,
MtaDeploymentRepositoryURL: "dummy", MtaDeploymentRepositoryPassword: "dummy", MtaDeploymentRepositoryUser: "dummy"}
options := mtaBuildOptions{
ApplicationName: "myApp",
GlobalSettingsFile: "/opt/maven/settings.xml",
Platform: "CF",
MtarName: "myName",
Source: "./",
Target: "./",
Publish: true,
MtaDeploymentRepositoryURL: "dummy",
MtaDeploymentRepositoryPassword: "dummy",
MtaDeploymentRepositoryUser: "dummy",
}
err := runMtaBuild(options, &cpe, utilsMock)
assert.Equal(t, "mtarGroup, version not found and must be present", err.Error())
})
t.Run("successful publish", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.sendRequestReturns = func() (*http.Response, error) {
return &http.Response{StatusCode: 200}, nil
}
utilsMock.AddFile("mta.yaml", []byte(`ID: "myNameFromMtar"`))
utilsMock.openReturns = `{"version":"1.2.3"}`
options := mtaBuildOptions{
ApplicationName: "myApp",
GlobalSettingsFile: "/opt/maven/settings.xml",
Platform: "CF",
MtarName: "test",
Source: "./",
Target: "./",
Publish: true,
MtaDeploymentRepositoryURL: "dummy",
MtaDeploymentRepositoryPassword: "dummy",
MtaDeploymentRepositoryUser: "dummy",
MtarGroup: "dummy",
Version: "dummy",
}
err := runMtaBuild(options, &cpe, utilsMock)
assert.Nil(t, err)
})
t.Run("succesful build artifact", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.AddFile("mta.yaml", []byte(`ID: "myNameFromMtar"`))
utilsMock.openReturns = `{"version":"1.2.3"}`
utilsMock.sendRequestReturns = func() (*http.Response, error) {
return &http.Response{StatusCode: 200}, nil
}
options := mtaBuildOptions{
ApplicationName: "myApp",
GlobalSettingsFile: "/opt/maven/settings.xml",
Platform: "CF",
MtarName: "test",
Source: "./",
Target: "./",
Publish: true,
MtaDeploymentRepositoryURL: "dummy",
MtaDeploymentRepositoryPassword: "dummy",
MtaDeploymentRepositoryUser: "dummy",
MtarGroup: "dummy",
Version: "dummy",
CreateBuildArtifactsMetadata: true,
}
err := runMtaBuild(options, &cpe, utilsMock)
assert.Nil(t, err)
assert.Equal(t, cpe.custom.mtaBuildArtifacts, `{"Coordinates":[{"groupId":"dummy","artifactId":"test","version":"dummy","packaging":"mtar","buildPath":"./","url":"dummydummy/test/dummy/test-dummy.mtar","purl":""}]}`)
})
})
}
func TestMtaBuildSourceDir(t *testing.T) {
cpe := mtaBuildCommonPipelineEnvironment{}
t.Run("getSourcePath", func(t *testing.T) {
t.Parallel()
@ -338,7 +464,7 @@ func TestMtaBuildSourceDir(t *testing.T) {
t.Run("create mta.yaml from config.source", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.AddFile("package.json", []byte("{\"name\": \"myName\", \"version\": \"1.2.3\"}"))
utilsMock.AddFile("package.json", []byte(`{"name": "myName", "version": "1.2.3"}`))
_ = runMtaBuild(mtaBuildOptions{ApplicationName: "myApp", Source: "create"}, &cpe, utilsMock)
@ -359,35 +485,33 @@ func TestMtaBuildSourceDir(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
options := mtaBuildOptions{ApplicationName: "myApp", Platform: "CF", DefaultNpmRegistry: "https://example.org/npm", MtarName: "myName", Source: "./", Target: "./", CreateBOM: true}
utilsMock.AddFile("package.json", []byte("{\"name\": \"myName\", \"version\": \"1.2.3\"}"))
utilsMock.AddFile("package.json", []byte(`{"name": "myName", "version": "1.2.3"}`))
err := runMtaBuild(options, &cpe, utilsMock)
assert.Nil(t, err)
assert.Contains(t, utilsMock.Calls[0].Params, "--sbom-file-path")
})
}
func TestMtaBuildMtar(t *testing.T) {
t.Run("getMtarName", func(t *testing.T) {
t.Parallel()
t.Run("mtar name from yaml", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.AddFile("mta.yaml", []byte("ID: \"nameFromMtar\""))
utilsMock.AddFile("mta.yaml", []byte(`ID: "nameFromMtar"`))
assert.Equal(t, filepath.FromSlash("nameFromMtar.mtar"), _ignoreErrorForGetMtarName(getMtarName(mtaBuildOptions{MtarName: ""}, "mta.yaml", utilsMock)))
})
t.Run("mtar name from yaml with suffixed value", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.AddFile("mta.yaml", []byte("ID: \"nameFromMtar.mtar\""))
utilsMock.AddFile("mta.yaml", []byte(`ID: "nameFromMtar.mtar"`))
assert.Equal(t, filepath.FromSlash("nameFromMtar.mtar"), _ignoreErrorForGetMtarName(getMtarName(mtaBuildOptions{MtarName: ""}, "mta.yaml", utilsMock)))
})
t.Run("mtar name from config", func(t *testing.T) {
utilsMock := newMtaBuildTestUtilsBundle()
utilsMock.AddFile("mta.yaml", []byte("ID: \"nameFromMtar\""))
utilsMock.AddFile("mta.yaml", []byte(`ID: "nameFromMtar"`))
assert.Equal(t, filepath.FromSlash("nameFromConfig.mtar"), _ignoreErrorForGetMtarName(getMtarName(mtaBuildOptions{MtarName: "nameFromConfig.mtar"}, "mta.yaml", utilsMock)))
})
@ -412,7 +536,6 @@ func TestMtaBuildMtar(t *testing.T) {
assert.Equal(t, filepath.FromSlash("source/target/mta.mtar"), getMtarFilePath(mtaBuildOptions{Source: "source", Target: "target"}, "mta.mtar"))
})
})
}
func _ignoreError(s string, e error) string {

View File

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
CredentialUtils "github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/versioning"
)
@ -217,7 +218,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)
coordinate.PURL = piperutils.GetPurl(filepath.Join(filepath.Dir(packageJSON), npmBomFilename))
*buildCoordinates = append(*buildCoordinates, coordinate)
}
@ -226,21 +227,6 @@ 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 {

View File

@ -4,15 +4,15 @@
package npm
import (
"github.com/SAP/jenkins-library/pkg/mock"
"io"
"path/filepath"
"testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/versioning"
"github.com/stretchr/testify/assert"
"os"
)
type npmMockUtilsBundleRelativeGlob struct {
@ -531,7 +531,7 @@ func TestNpmPublish(t *testing.T) {
// This stub simulates the behavior of npm pack and puts a tgz into the requested
utils.execRunner.Stub = func(call string, stdoutReturn map[string]string, shouldFailOnCommand map[string]error, stdout io.Writer) error {
//tgzTargetPath := filepath.Dir(test.packageDescriptors[0])
// tgzTargetPath := filepath.Dir(test.packageDescriptors[0])
utils.AddFile(filepath.Join(".", "package.tgz"), []byte("this is a tgz file"))
return nil
}
@ -574,46 +574,3 @@ 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

@ -2,9 +2,10 @@ package piperutils
import (
"encoding/xml"
"github.com/SAP/jenkins-library/pkg/log"
"io"
"os"
"github.com/SAP/jenkins-library/pkg/log"
)
// To serialize the cyclonedx BOM file
@ -46,3 +47,12 @@ func GetBom(absoluteBomPath string) (Bom, error) {
}
return bom, nil
}
func GetPurl(bomFilePath string) string {
bom, err := GetBom(bomFilePath)
if err != nil {
log.Entry().Warnf("unable to get bom metadata: %v", err)
return ""
}
return bom.Metadata.Component.Purl
}

View File

@ -18,6 +18,18 @@ func createTempFile(t *testing.T, content string) (string, func()) {
}
}
const validBom = `<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>`
func TestGetBom(t *testing.T) {
tests := []struct {
name string
@ -27,18 +39,8 @@ func TestGetBom(t *testing.T) {
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>`,
name: "valid file",
xmlContent: validBom,
expectedBom: Bom{
Metadata: Metadata{
Component: BomComponent{
@ -73,12 +75,8 @@ func TestGetBom(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"
@ -102,6 +100,57 @@ func TestGetBom(t *testing.T) {
}
}
func TestGetPurl(t *testing.T) {
tests := []struct {
name string
filePath string
bomFilename string
xmlContent string
expectedPurl string
expectError bool
expectedError string
}{
{
name: "valid BOM file",
xmlContent: validBom,
expectedPurl: "pkg:maven/com.example/mycomponent@1.0.0",
},
{
name: "BOM file not found",
xmlContent: "",
expectedPurl: "",
expectError: true,
expectedError: "no such file or directory",
},
{
name: "invalid BOM file",
xmlContent: "<bom><metadata><component><purl>invalid xml</metadata></bom>",
expectedPurl: "",
expectError: true,
expectedError: "XML syntax error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var filePath string
var cleanup func()
if tt.xmlContent != "" {
filePath, cleanup = createTempFile(t, tt.xmlContent)
defer cleanup()
} else {
// Use a non-existent file path
filePath = "nonexistent.xml"
}
purl := GetPurl(filePath)
if purl != tt.expectedPurl {
t.Errorf("Expected PURL: %v, got: %v", tt.expectedPurl, purl)
}
})
}
}
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 &&

View File

@ -244,6 +244,14 @@ spec:
- STAGES
- PARAMETERS
default: true
- name: createBuildArtifactsMetadata
type: bool
default: false
description: metadata about the artifacts that are build and published, this metadata is generally used by steps downstream in the pipeline
scope:
- STEPS
- STAGES
- PARAMETERS
outputs:
resources:
- name: commonPipelineEnvironment
@ -253,6 +261,7 @@ spec:
- name: custom/mtaBuildToolDesc
- name: custom/mtarPublishedUrl
- name: custom/buildSettingsInfo
- name: custom/mtaBuildArtifacts
- name: reports
type: reports
params: