mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-16 05:16:08 +02:00
whitesourceExecuteScan: bypass UA for mta, maven, npm and yarn (#1879)
Co-authored-by: Florian Wilhelm <florian.wilhelm02@sap.com>
This commit is contained in:
parent
e2aea73022
commit
54ea3ed51a
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ import (
|
||||
"github.com/SAP/jenkins-library/pkg/versioning"
|
||||
ws "github.com/SAP/jenkins-library/pkg/whitesource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -77,7 +78,7 @@ var mockLibrary = ws.Library{
|
||||
Name: "mock-library",
|
||||
Filename: "mock-library-file",
|
||||
Version: "mock-library-version",
|
||||
Project: "mock-project",
|
||||
Project: "mock-project - 1",
|
||||
}
|
||||
|
||||
func newWhitesourceSystemMock(lastUpdateDate string) *whitesourceSystemMock {
|
||||
@ -94,7 +95,7 @@ func newWhitesourceSystemMock(lastUpdateDate string) *whitesourceSystemMock {
|
||||
projects: []ws.Project{
|
||||
{
|
||||
ID: 42,
|
||||
Name: "mock-project",
|
||||
Name: "mock-project - 1",
|
||||
PluginName: "mock-plugin-name",
|
||||
Token: "mock-project-token",
|
||||
UploadedBy: "MrBean",
|
||||
@ -104,10 +105,13 @@ func newWhitesourceSystemMock(lastUpdateDate string) *whitesourceSystemMock {
|
||||
},
|
||||
alerts: []ws.Alert{
|
||||
{
|
||||
Vulnerability: ws.Vulnerability{},
|
||||
Library: mockLibrary,
|
||||
Project: "mock-project",
|
||||
CreationDate: "last-thursday",
|
||||
Vulnerability: ws.Vulnerability{
|
||||
Name: "something severe",
|
||||
Score: 5,
|
||||
},
|
||||
Library: mockLibrary,
|
||||
Project: "mock-project - 1",
|
||||
CreationDate: "last-thursday",
|
||||
},
|
||||
},
|
||||
libraries: []ws.Library{mockLibrary},
|
||||
@ -130,8 +134,12 @@ type downloadedFile struct {
|
||||
type whitesourceUtilsMock struct {
|
||||
*mock.FilesMock
|
||||
*mock.ExecMockRunner
|
||||
coordinates whitesourceCoordinatesMock
|
||||
downloadedFiles []downloadedFile
|
||||
coordinates whitesourceCoordinatesMock
|
||||
usedBuildTool string
|
||||
usedBuildDescriptorFile string
|
||||
usedOptions versioning.Options
|
||||
downloadedFiles []downloadedFile
|
||||
npmInstalledModules []string
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsMock) DownloadFile(url, filename string, _ http.Header, _ []*http.Cookie) error {
|
||||
@ -139,8 +147,8 @@ func (w *whitesourceUtilsMock) DownloadFile(url, filename string, _ http.Header,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsMock) FileOpen(name string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
return nil, fmt.Errorf("FileOpen() is unsupported by the mock implementation")
|
||||
func (w *whitesourceUtilsMock) FileOpen(name string, flag int, perm os.FileMode) (wsFile, error) {
|
||||
return w.Open(name, flag, perm)
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsMock) RemoveAll(path string) error {
|
||||
@ -148,7 +156,11 @@ func (w *whitesourceUtilsMock) RemoveAll(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsMock) GetArtifactCoordinates(_ *ScanOptions) (versioning.Coordinates, error) {
|
||||
func (w *whitesourceUtilsMock) GetArtifactCoordinates(buildTool, buildDescriptorFile string,
|
||||
options *versioning.Options) (versioning.Coordinates, error) {
|
||||
w.usedBuildTool = buildTool
|
||||
w.usedBuildDescriptorFile = buildDescriptorFile
|
||||
w.usedOptions = *options
|
||||
return w.coordinates, nil
|
||||
}
|
||||
|
||||
@ -158,9 +170,17 @@ func (w *whitesourceUtilsMock) FindPackageJSONFiles(_ *ScanOptions) ([]string, e
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsMock) InstallAllNPMDependencies(_ *ScanOptions, _ []string) error {
|
||||
w.npmInstalledModules = append(w.npmInstalledModules, w.CurrentDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
const wsTimeNow = "2010-05-10 00:15:42"
|
||||
|
||||
func (w *whitesourceUtilsMock) Now() time.Time {
|
||||
now, _ := time.Parse("2006-01-02 15:04:05", wsTimeNow)
|
||||
return now
|
||||
}
|
||||
|
||||
func newWhitesourceUtilsMock() *whitesourceUtilsMock {
|
||||
return &whitesourceUtilsMock{
|
||||
FilesMock: &mock.FilesMock{},
|
||||
@ -174,41 +194,517 @@ func newWhitesourceUtilsMock() *whitesourceUtilsMock {
|
||||
}
|
||||
|
||||
func TestResolveProjectIdentifiers(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
// init
|
||||
config := ScanOptions{
|
||||
ScanType: "mta",
|
||||
VersioningModel: "major",
|
||||
ProductName: "mock-product",
|
||||
BuildTool: "mta",
|
||||
BuildDescriptorFile: "my-mta.yml",
|
||||
VersioningModel: "major",
|
||||
ProductName: "mock-product",
|
||||
M2Path: "m2/path",
|
||||
ProjectSettingsFile: "project-settings.xml",
|
||||
GlobalSettingsFile: "global-settings.xml",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
systemMock := newWhitesourceSystemMock("ignored")
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := resolveProjectIdentifiers(&config, utilsMock, systemMock)
|
||||
err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock)
|
||||
// assert
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, "mock-group-id-mock-artifact-id", config.ProjectName)
|
||||
assert.Equal(t, "mock-group-id-mock-artifact-id", scan.aggregateProjectName)
|
||||
assert.Equal(t, "1", config.ProductVersion)
|
||||
assert.Equal(t, "mock-project-token", config.ProjectToken)
|
||||
assert.Equal(t, "mock-product-token", config.ProductToken)
|
||||
assert.Equal(t, "mta", utilsMock.usedBuildTool)
|
||||
assert.Equal(t, "my-mta.yml", utilsMock.usedBuildDescriptorFile)
|
||||
assert.Equal(t, "project-settings.xml", utilsMock.usedOptions.ProjectSettingsFile)
|
||||
assert.Equal(t, "global-settings.xml", utilsMock.usedOptions.GlobalSettingsFile)
|
||||
assert.Equal(t, "m2/path", utilsMock.usedOptions.M2Path)
|
||||
}
|
||||
})
|
||||
t.Run("retrieves token for configured project name", func(t *testing.T) {
|
||||
// init
|
||||
config := ScanOptions{
|
||||
BuildTool: "mta",
|
||||
BuildDescriptorFile: "my-mta.yml",
|
||||
VersioningModel: "major",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project - 1",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
systemMock := newWhitesourceSystemMock("ignored")
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock)
|
||||
// assert
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, "mock-project - 1", scan.aggregateProjectName)
|
||||
assert.Equal(t, "1", config.ProductVersion)
|
||||
assert.Equal(t, "mock-product-token", config.ProductToken)
|
||||
assert.Equal(t, "mta", utilsMock.usedBuildTool)
|
||||
assert.Equal(t, "my-mta.yml", utilsMock.usedBuildDescriptorFile)
|
||||
assert.Equal(t, "mock-project-token", config.ProjectToken)
|
||||
}
|
||||
})
|
||||
t.Run("product not found", func(t *testing.T) {
|
||||
// init
|
||||
config := ScanOptions{
|
||||
ScanType: "mta",
|
||||
BuildTool: "mta",
|
||||
VersioningModel: "major",
|
||||
ProductName: "does-not-exist",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
systemMock := newWhitesourceSystemMock("ignored")
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := resolveProjectIdentifiers(&config, utilsMock, systemMock)
|
||||
err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock)
|
||||
// assert
|
||||
assert.EqualError(t, err, "no product with name 'does-not-exist' found in Whitesource")
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecuteScanUA(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("happy path UA", func(t *testing.T) {
|
||||
// init
|
||||
config := ScanOptions{
|
||||
ScanType: "unified-agent",
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project",
|
||||
ProductVersion: "product-version",
|
||||
AgentDownloadURL: "https://download.ua.org/agent.jar",
|
||||
AgentFileName: "unified-agent.jar",
|
||||
ConfigFilePath: "ua.cfg",
|
||||
M2Path: ".pipeline/m2",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("wss-generated-file.config", []byte("key=value"))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// many assert
|
||||
require.NoError(t, err)
|
||||
|
||||
content, err := utilsMock.FileRead("ua.cfg")
|
||||
require.NoError(t, err)
|
||||
contentAsString := string(content)
|
||||
assert.Contains(t, contentAsString, "key=value\n")
|
||||
assert.Contains(t, contentAsString, "gradle.aggregateModules=true\n")
|
||||
assert.Contains(t, contentAsString, "maven.aggregateModules=true\n")
|
||||
assert.Contains(t, contentAsString, "maven.m2RepositoryPath=.pipeline/m2\n")
|
||||
assert.Contains(t, contentAsString, "excludes=")
|
||||
|
||||
require.Len(t, utilsMock.Calls, 4)
|
||||
fmt.Printf("calls: %v\n", utilsMock.Calls)
|
||||
expectedCall := mock.ExecCall{
|
||||
Exec: "java",
|
||||
Params: []string{
|
||||
"-jar",
|
||||
config.AgentFileName,
|
||||
"-d", ".",
|
||||
"-c", config.ConfigFilePath,
|
||||
"-apiKey", config.OrgToken,
|
||||
"-userKey", config.UserToken,
|
||||
"-project", config.ProjectName,
|
||||
"-product", config.ProductName,
|
||||
"-productVersion", config.ProductVersion,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedCall, utilsMock.Calls[3])
|
||||
})
|
||||
t.Run("UA is downloaded", func(t *testing.T) {
|
||||
// init
|
||||
config := ScanOptions{
|
||||
ScanType: "unified-agent",
|
||||
AgentDownloadURL: "https://download.ua.org/agent.jar",
|
||||
AgentFileName: "unified-agent.jar",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("wss-generated-file.config", []byte("dummy"))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// many assert
|
||||
require.NoError(t, err)
|
||||
require.Len(t, utilsMock.downloadedFiles, 1)
|
||||
assert.Equal(t, "https://download.ua.org/agent.jar", utilsMock.downloadedFiles[0].sourceURL)
|
||||
assert.Equal(t, "unified-agent.jar", utilsMock.downloadedFiles[0].filePath)
|
||||
})
|
||||
t.Run("UA is NOT downloaded", func(t *testing.T) {
|
||||
// init
|
||||
config := ScanOptions{
|
||||
ScanType: "unified-agent",
|
||||
AgentDownloadURL: "https://download.ua.org/agent.jar",
|
||||
AgentFileName: "unified-agent.jar",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("wss-generated-file.config", []byte("dummy"))
|
||||
utilsMock.AddFile("unified-agent.jar", []byte("dummy"))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// many assert
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, utilsMock.downloadedFiles, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecuteScanNPM(t *testing.T) {
|
||||
config := ScanOptions{
|
||||
ScanType: "npm",
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project",
|
||||
ProductVersion: "product-version",
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
t.Run("happy path NPM", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("package.json", []byte(`{"name":"my-module-name"}`))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
require.NoError(t, err)
|
||||
expectedCalls := []mock.ExecCall{
|
||||
{
|
||||
Exec: "npm",
|
||||
Params: []string{
|
||||
"ls",
|
||||
},
|
||||
},
|
||||
{
|
||||
Exec: "npx",
|
||||
Params: []string{
|
||||
"whitesource",
|
||||
"run",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedCalls, utilsMock.Calls)
|
||||
assert.True(t, utilsMock.HasWrittenFile(whiteSourceConfig))
|
||||
assert.True(t, utilsMock.HasRemovedFile(whiteSourceConfig))
|
||||
})
|
||||
t.Run("no NPM modules", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
assert.EqualError(t, err, "found no NPM modules to scan. Configured excludes: []")
|
||||
assert.Len(t, utilsMock.Calls, 0)
|
||||
assert.False(t, utilsMock.HasWrittenFile(whiteSourceConfig))
|
||||
})
|
||||
t.Run("package.json needs name", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("package.json", []byte(`{"key":"value"}`))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
assert.EqualError(t, err, "failed to scan NPM module 'package.json': the file 'package.json/package.json' must configure a name")
|
||||
})
|
||||
t.Run("npm ls fails", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("package.json", []byte(`{"name":"my-module-name"}`))
|
||||
utilsMock.AddFile(filepath.Join("app", "package.json"), []byte(`{"name":"my-app-module-name"}`))
|
||||
utilsMock.AddFile("package-lock.json", []byte("dummy"))
|
||||
|
||||
utilsMock.ShouldFailOnCommand = make(map[string]error)
|
||||
utilsMock.ShouldFailOnCommand["npm ls"] = fmt.Errorf("mock failure")
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"app", ""}, utilsMock.npmInstalledModules)
|
||||
assert.True(t, utilsMock.HasRemovedFile("package-lock.json"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecuteScanMaven(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("maven modules are aggregated", func(t *testing.T) {
|
||||
// init
|
||||
const pomXML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>my-artifact-id</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
</project>
|
||||
`
|
||||
config := ScanOptions{
|
||||
ScanType: "maven",
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project",
|
||||
ProductVersion: "product-version",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("pom.xml", []byte(pomXML))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
require.NoError(t, err)
|
||||
expectedCalls := []mock.ExecCall{
|
||||
{
|
||||
Exec: "mvn",
|
||||
Params: []string{
|
||||
"--file",
|
||||
"pom.xml",
|
||||
"-Dorg.whitesource.orgToken=org-token",
|
||||
"-Dorg.whitesource.product=mock-product",
|
||||
"-Dorg.whitesource.checkPolicies=true",
|
||||
"-Dorg.whitesource.failOnError=true",
|
||||
"-Dorg.whitesource.aggregateProjectName=mock-project",
|
||||
"-Dorg.whitesource.aggregateModules=true",
|
||||
"-Dorg.whitesource.userKey=user-token",
|
||||
"-Dorg.whitesource.productVersion=product-version",
|
||||
"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
|
||||
"--batch-mode",
|
||||
"org.whitesource:whitesource-maven-plugin:19.5.1:update",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedCalls, utilsMock.Calls)
|
||||
})
|
||||
t.Run("maven modules are separate projects", func(t *testing.T) {
|
||||
// init
|
||||
const rootPomXML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>my-artifact-id</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<modules>
|
||||
<module>sub</module>
|
||||
</modules>
|
||||
</project>
|
||||
`
|
||||
const modulePomXML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>my-artifact-id-sub</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
</project>
|
||||
`
|
||||
config := ScanOptions{
|
||||
ScanType: "maven",
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProductVersion: "product-version",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("pom.xml", []byte(rootPomXML))
|
||||
utilsMock.AddFile(filepath.Join("sub", "pom.xml"), []byte(modulePomXML))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
require.NoError(t, err)
|
||||
expectedCalls := []mock.ExecCall{
|
||||
{
|
||||
Exec: "mvn",
|
||||
Params: []string{
|
||||
"--file",
|
||||
"pom.xml",
|
||||
"-Dorg.whitesource.orgToken=org-token",
|
||||
"-Dorg.whitesource.product=mock-product",
|
||||
"-Dorg.whitesource.checkPolicies=true",
|
||||
"-Dorg.whitesource.failOnError=true",
|
||||
"-Dorg.whitesource.userKey=user-token",
|
||||
"-Dorg.whitesource.productVersion=product-version",
|
||||
"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
|
||||
"--batch-mode",
|
||||
"org.whitesource:whitesource-maven-plugin:19.5.1:update",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedCalls, utilsMock.Calls)
|
||||
require.Len(t, scan.scannedProjects, 2)
|
||||
_, existsRoot := scan.scannedProjects["my-artifact-id - product-version"]
|
||||
_, existsModule := scan.scannedProjects["my-artifact-id-sub - product-version"]
|
||||
assert.True(t, existsRoot)
|
||||
assert.True(t, existsModule)
|
||||
})
|
||||
t.Run("pom.xml does not exist", func(t *testing.T) {
|
||||
// init
|
||||
config := ScanOptions{
|
||||
ScanType: "maven",
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProductVersion: "product-version",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
assert.EqualError(t, err,
|
||||
"for scanning with type 'maven', the file 'pom.xml' must exist in the project root")
|
||||
assert.Len(t, utilsMock.Calls, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecuteScanMTA(t *testing.T) {
|
||||
const pomXML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>my-artifact-id</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
</project>
|
||||
`
|
||||
config := ScanOptions{
|
||||
BuildTool: "mta",
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project",
|
||||
ProductVersion: "product-version",
|
||||
}
|
||||
t.Parallel()
|
||||
t.Run("happy path MTA", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("pom.xml", []byte(pomXML))
|
||||
utilsMock.AddFile("package.json", []byte(`{"name":"my-module-name"}`))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
require.NoError(t, err)
|
||||
expectedCalls := []mock.ExecCall{
|
||||
{
|
||||
Exec: "mvn",
|
||||
Params: []string{
|
||||
"--file",
|
||||
"pom.xml",
|
||||
"-Dorg.whitesource.orgToken=org-token",
|
||||
"-Dorg.whitesource.product=mock-product",
|
||||
"-Dorg.whitesource.checkPolicies=true",
|
||||
"-Dorg.whitesource.failOnError=true",
|
||||
"-Dorg.whitesource.aggregateProjectName=mock-project",
|
||||
"-Dorg.whitesource.aggregateModules=true",
|
||||
"-Dorg.whitesource.userKey=user-token",
|
||||
"-Dorg.whitesource.productVersion=product-version",
|
||||
"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
|
||||
"--batch-mode",
|
||||
"org.whitesource:whitesource-maven-plugin:19.5.1:update",
|
||||
},
|
||||
},
|
||||
{
|
||||
Exec: "npm",
|
||||
Params: []string{
|
||||
"ls",
|
||||
},
|
||||
},
|
||||
{
|
||||
Exec: "npx",
|
||||
Params: []string{
|
||||
"whitesource",
|
||||
"run",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedCalls, utilsMock.Calls)
|
||||
assert.True(t, utilsMock.HasWrittenFile(whiteSourceConfig))
|
||||
assert.True(t, utilsMock.HasRemovedFile(whiteSourceConfig))
|
||||
assert.Equal(t, expectedCalls, utilsMock.Calls)
|
||||
})
|
||||
t.Run("MTA with only maven modules", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("pom.xml", []byte(pomXML))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
require.NoError(t, err)
|
||||
expectedCalls := []mock.ExecCall{
|
||||
{
|
||||
Exec: "mvn",
|
||||
Params: []string{
|
||||
"--file",
|
||||
"pom.xml",
|
||||
"-Dorg.whitesource.orgToken=org-token",
|
||||
"-Dorg.whitesource.product=mock-product",
|
||||
"-Dorg.whitesource.checkPolicies=true",
|
||||
"-Dorg.whitesource.failOnError=true",
|
||||
"-Dorg.whitesource.aggregateProjectName=mock-project",
|
||||
"-Dorg.whitesource.aggregateModules=true",
|
||||
"-Dorg.whitesource.userKey=user-token",
|
||||
"-Dorg.whitesource.productVersion=product-version",
|
||||
"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
|
||||
"--batch-mode",
|
||||
"org.whitesource:whitesource-maven-plugin:19.5.1:update",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedCalls, utilsMock.Calls)
|
||||
assert.False(t, utilsMock.HasWrittenFile(whiteSourceConfig))
|
||||
assert.Equal(t, expectedCalls, utilsMock.Calls)
|
||||
})
|
||||
t.Run("MTA with only NPM modules", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
utilsMock.AddFile("package.json", []byte(`{"name":"my-module-name"}`))
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
require.NoError(t, err)
|
||||
expectedCalls := []mock.ExecCall{
|
||||
{
|
||||
Exec: "npm",
|
||||
Params: []string{
|
||||
"ls",
|
||||
},
|
||||
},
|
||||
{
|
||||
Exec: "npx",
|
||||
Params: []string{
|
||||
"whitesource",
|
||||
"run",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedCalls, utilsMock.Calls)
|
||||
assert.True(t, utilsMock.HasWrittenFile(whiteSourceConfig))
|
||||
assert.True(t, utilsMock.HasRemovedFile(whiteSourceConfig))
|
||||
assert.Equal(t, expectedCalls, utilsMock.Calls)
|
||||
})
|
||||
t.Run("MTA with neither Maven nor NPM modules results in error", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := executeScan(&config, scan, utilsMock)
|
||||
// assert
|
||||
assert.EqualError(t, err, "neither Maven nor NPM modules found, no scan performed")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("already new enough", func(t *testing.T) {
|
||||
@ -220,11 +716,8 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
}
|
||||
lastUpdatedDate := "2010-05-30 00:15:01 +0100"
|
||||
systemMock := newWhitesourceSystemMock(lastUpdatedDate)
|
||||
config := &ScanOptions{
|
||||
ProjectToken: systemMock.projects[0].Token,
|
||||
}
|
||||
// test
|
||||
err = blockUntilProjectIsUpdated(config, systemMock, now, 2*time.Second, 1*time.Second, 2*time.Second)
|
||||
err = blockUntilProjectIsUpdated(systemMock.projects[0].Token, systemMock, now, 2*time.Second, 1*time.Second, 2*time.Second)
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
@ -237,11 +730,8 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
}
|
||||
lastUpdatedDate := "2010-05-30 00:07:00 +0100"
|
||||
systemMock := newWhitesourceSystemMock(lastUpdatedDate)
|
||||
config := &ScanOptions{
|
||||
ProjectToken: systemMock.projects[0].Token,
|
||||
}
|
||||
// test
|
||||
err = blockUntilProjectIsUpdated(config, systemMock, now, 2*time.Second, 1*time.Second, 1*time.Second)
|
||||
err = blockUntilProjectIsUpdated(systemMock.projects[0].Token, systemMock, now, 2*time.Second, 1*time.Second, 1*time.Second)
|
||||
// assert
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "timeout while waiting")
|
||||
@ -255,11 +745,8 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
systemMock := newWhitesourceSystemMock("")
|
||||
config := &ScanOptions{
|
||||
ProjectToken: systemMock.projects[0].Token,
|
||||
}
|
||||
// test
|
||||
err = blockUntilProjectIsUpdated(config, systemMock, now, 2*time.Second, 1*time.Second, 1*time.Second)
|
||||
err = blockUntilProjectIsUpdated(systemMock.projects[0].Token, systemMock, now, 2*time.Second, 1*time.Second, 1*time.Second)
|
||||
// assert
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "timeout while waiting")
|
||||
@ -279,8 +766,9 @@ func TestDownloadReports(t *testing.T) {
|
||||
}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock("2010-05-30 00:15:00 +0100")
|
||||
scan := newWhitesourceScan(config)
|
||||
// test
|
||||
paths, err := downloadReports(config, utils, system)
|
||||
paths, err := downloadReports(config, scan, utils, system)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.Len(t, paths, 2) {
|
||||
vPath := filepath.Join("report-dir", "mock-project-vulnerability-report.txt")
|
||||
@ -298,14 +786,45 @@ func TestDownloadReports(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{
|
||||
ProjectToken: "<invalid>",
|
||||
ProjectName: "mock-project",
|
||||
}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock("2010-05-30 00:15:00 +0100")
|
||||
scan := newWhitesourceScan(config)
|
||||
// test
|
||||
path, err := downloadReports(config, utils, system)
|
||||
paths, err := downloadReports(config, scan, utils, system)
|
||||
// assert
|
||||
assert.EqualError(t, err, "no project with token '<invalid>' found in Whitesource")
|
||||
assert.Nil(t, path)
|
||||
assert.Nil(t, paths)
|
||||
})
|
||||
t.Run("multiple scanned projects", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{
|
||||
ReportDirectoryName: "report-dir",
|
||||
VulnerabilityReportFormat: "txt",
|
||||
}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock("2010-05-30 00:15:00 +0100")
|
||||
scan := newWhitesourceScan(config)
|
||||
scan.init()
|
||||
scan.scannedProjects["mock-project"] = ws.Project{
|
||||
Name: "mock-project",
|
||||
Token: "mock-project-token",
|
||||
}
|
||||
// test
|
||||
paths, err := downloadReports(config, scan, utils, system)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.Len(t, paths, 2) {
|
||||
vPath := filepath.Join("report-dir", "mock-project-vulnerability-report.txt")
|
||||
assert.True(t, utils.HasWrittenFile(vPath))
|
||||
vContent, _ := utils.FileRead(vPath)
|
||||
assert.Equal(t, []byte("mock-vulnerability-report"), vContent)
|
||||
|
||||
rPath := filepath.Join("report-dir", "mock-project-risk-report.pdf")
|
||||
assert.True(t, utils.HasWrittenFile(rPath))
|
||||
rContent, _ := utils.FileRead(rPath)
|
||||
assert.Equal(t, []byte("mock-risk-report"), rContent)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -315,6 +834,7 @@ func TestWriteWhitesourceConfigJSON(t *testing.T) {
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project",
|
||||
ProductToken: "mock-product-token",
|
||||
ProductVersion: "42",
|
||||
}
|
||||
|
||||
@ -324,6 +844,7 @@ func TestWriteWhitesourceConfigJSON(t *testing.T) {
|
||||
expected["checkPolicies"] = true
|
||||
expected["productName"] = "mock-product"
|
||||
expected["projectName"] = "mock-project"
|
||||
expected["productToken"] = "mock-product-token"
|
||||
expected["productVer"] = "42"
|
||||
expected["devDep"] = true
|
||||
expected["ignoreNpmLsErrors"] = true
|
||||
@ -370,4 +891,221 @@ func TestWriteWhitesourceConfigJSON(t *testing.T) {
|
||||
assert.Equal(t, mergedExpected, actual)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("extend and merge config, omit productToken", func(t *testing.T) {
|
||||
// init
|
||||
initial := make(map[string]interface{})
|
||||
initial["checkPolicies"] = false
|
||||
initial["productName"] = "mock-product"
|
||||
initial["productVer"] = "41"
|
||||
initial["unknown"] = "preserved"
|
||||
initial["projectToken"] = "mock-project-token"
|
||||
encoded, _ := json.Marshal(initial)
|
||||
|
||||
utils := newWhitesourceUtilsMock()
|
||||
utils.AddFile(whiteSourceConfig, encoded)
|
||||
|
||||
// test
|
||||
err := writeWhitesourceConfigJSON(config, utils, true, true)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.True(t, utils.HasWrittenFile(whiteSourceConfig)) {
|
||||
contents, _ := utils.FileRead(whiteSourceConfig)
|
||||
actual := make(map[string]interface{})
|
||||
_ = json.Unmarshal(contents, &actual)
|
||||
|
||||
mergedExpected := expected
|
||||
mergedExpected["unknown"] = "preserved"
|
||||
mergedExpected["projectToken"] = "mock-project-token"
|
||||
delete(mergedExpected, "productToken")
|
||||
|
||||
assert.Equal(t, mergedExpected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPersisScannedProjects(t *testing.T) {
|
||||
resource := filepath.Join(".pipeline", "commonPipelineEnvironment", "custom", "whitesourceProjectNames")
|
||||
|
||||
t.Parallel()
|
||||
t.Run("write 1 scanned projects", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{ProductVersion: "1"}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
scan := newWhitesourceScan(config)
|
||||
_ = scan.appendScannedProject("project")
|
||||
// test
|
||||
err := persistScannedProjects(config, scan, utils)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.True(t, utils.HasWrittenFile(resource)) {
|
||||
contents, _ := utils.FileRead(resource)
|
||||
assert.Equal(t, "project - 1", string(contents))
|
||||
}
|
||||
})
|
||||
t.Run("write 2 scanned projects", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{ProductVersion: "1"}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
scan := newWhitesourceScan(config)
|
||||
_ = scan.appendScannedProject("project-app")
|
||||
_ = scan.appendScannedProject("project-db")
|
||||
// test
|
||||
err := persistScannedProjects(config, scan, utils)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.True(t, utils.HasWrittenFile(resource)) {
|
||||
contents, _ := utils.FileRead(resource)
|
||||
assert.Equal(t, "project-app - 1,project-db - 1", string(contents))
|
||||
}
|
||||
})
|
||||
t.Run("write no projects", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{ProductVersion: "1"}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
scan := newWhitesourceScan(config)
|
||||
// test
|
||||
err := persistScannedProjects(config, scan, utils)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.True(t, utils.HasWrittenFile(resource)) {
|
||||
contents, _ := utils.FileRead(resource)
|
||||
assert.Equal(t, "", string(contents))
|
||||
}
|
||||
})
|
||||
t.Run("write aggregated project", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{ProjectName: "project", ProductVersion: "1"}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
scan := newWhitesourceScan(config)
|
||||
// test
|
||||
err := persistScannedProjects(config, scan, utils)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.True(t, utils.HasWrittenFile(resource)) {
|
||||
contents, _ := utils.FileRead(resource)
|
||||
assert.Equal(t, "project - 1", string(contents))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAggregateVersionWideLibraries(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{
|
||||
ProductToken: "mock-product-token",
|
||||
ProductVersion: "1",
|
||||
ReportDirectoryName: "mock-reports",
|
||||
}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock("2010-05-30 00:15:00 +0100")
|
||||
// test
|
||||
err := aggregateVersionWideLibraries(config, utils, system)
|
||||
// assert
|
||||
resource := filepath.Join("mock-reports", "libraries-20100510-001542.csv")
|
||||
if assert.NoError(t, err) && assert.True(t, utils.HasWrittenFile(resource)) {
|
||||
contents, _ := utils.FileRead(resource)
|
||||
asString := string(contents)
|
||||
assert.Equal(t, "Library Name, Project Name\nmock-library, mock-project\n", asString)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAggregateVersionWideVulnerabilities(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{
|
||||
ProductToken: "mock-product-token",
|
||||
ProductVersion: "1",
|
||||
ReportDirectoryName: "mock-reports",
|
||||
}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock("2010-05-30 00:15:00 +0100")
|
||||
// test
|
||||
err := aggregateVersionWideVulnerabilities(config, utils, system)
|
||||
// assert
|
||||
resource := filepath.Join("mock-reports", "project-names-aggregated.txt")
|
||||
assert.NoError(t, err)
|
||||
if assert.True(t, utils.HasWrittenFile(resource)) {
|
||||
contents, _ := utils.FileRead(resource)
|
||||
asString := string(contents)
|
||||
assert.Equal(t, "mock-project - 1\n", asString)
|
||||
}
|
||||
reportSheet := filepath.Join("mock-reports", "vulnerabilities-20100510-001542.xlsx")
|
||||
sheetContents, err := utils.FileRead(reportSheet)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, sheetContents)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckAndReportScanResults(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("no reports requested", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{
|
||||
ProductToken: "mock-product-token",
|
||||
ProjectToken: "mock-project-token",
|
||||
ProductVersion: "1",
|
||||
ReportDirectoryName: "mock-reports",
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
vPath := filepath.Join("report-dir", "mock-project-vulnerability-report.txt")
|
||||
assert.False(t, utils.HasWrittenFile(vPath))
|
||||
rPath := filepath.Join("report-dir", "mock-project-risk-report.pdf")
|
||||
assert.False(t, utils.HasWrittenFile(rPath))
|
||||
})
|
||||
t.Run("check vulnerabilities - invalid limit", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{
|
||||
SecurityVulnerabilities: true,
|
||||
CvssSeverityLimit: "invalid",
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
assert.EqualError(t, err, "failed to parse parameter cvssSeverityLimit (invalid) as floating point number: strconv.ParseFloat: parsing \"invalid\": invalid syntax")
|
||||
})
|
||||
t.Run("check vulnerabilities - limit not hit", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{
|
||||
ProductToken: "mock-product-token",
|
||||
ProjectToken: "mock-project-token",
|
||||
ProductVersion: "1",
|
||||
ReportDirectoryName: "mock-reports",
|
||||
SecurityVulnerabilities: true,
|
||||
CvssSeverityLimit: "6.0",
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
t.Run("check vulnerabilities - limit exceeded", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{
|
||||
ProductToken: "mock-product-token",
|
||||
ProjectName: "mock-project - 1",
|
||||
ProjectToken: "mock-project-token",
|
||||
ProductVersion: "1",
|
||||
ReportDirectoryName: "mock-reports",
|
||||
SecurityVulnerabilities: true,
|
||||
CvssSeverityLimit: "4",
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
assert.EqualError(t, err, "1 Open Source Software Security vulnerabilities with CVSS score greater or equal to 4.0 detected in project mock-project - 1")
|
||||
})
|
||||
}
|
||||
|
110
pkg/maven/model.go
Normal file
110
pkg/maven/model.go
Normal file
@ -0,0 +1,110 @@
|
||||
package maven
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Project describes the Maven object model.
|
||||
type Project struct {
|
||||
XMLName xml.Name `xml:"project"`
|
||||
Parent Parent `xml:"parent"`
|
||||
GroupID string `xml:"groupId"`
|
||||
ArtifactID string `xml:"artifactId"`
|
||||
Version string `xml:"version"`
|
||||
Packaging string `xml:"packaging"`
|
||||
Name string `xml:"name"`
|
||||
Dependencies []Dependency `xml:"dependencies>dependency"`
|
||||
Modules []string `xml:"modules>module"`
|
||||
}
|
||||
|
||||
// Parent describes the coordinates a module's parent POM.
|
||||
type Parent struct {
|
||||
XMLName xml.Name `xml:"parent"`
|
||||
GroupID string `xml:"groupId"`
|
||||
ArtifactID string `xml:"artifactId"`
|
||||
Version string `xml:"version"`
|
||||
}
|
||||
|
||||
// Dependency describes a dependency of the module.
|
||||
type Dependency struct {
|
||||
XMLName xml.Name `xml:"dependency"`
|
||||
GroupID string `xml:"groupId"`
|
||||
ArtifactID string `xml:"artifactId"`
|
||||
Version string `xml:"version"`
|
||||
Classifier string `xml:"classifier"`
|
||||
Type string `xml:"type"`
|
||||
Scope string `xml:"scope"`
|
||||
Exclusions []Exclusion `xml:"exclusions>exclusion"`
|
||||
}
|
||||
|
||||
// Exclusion describes an exclusion within a dependency.
|
||||
type Exclusion struct {
|
||||
XMLName xml.Name `xml:"exclusion"`
|
||||
GroupID string `xml:"groupId"`
|
||||
ArtifactID string `xml:"artifactId"`
|
||||
}
|
||||
|
||||
// ParsePOM parses the provided XML raw data into a Project.
|
||||
func ParsePOM(xmlData []byte) (*Project, error) {
|
||||
project := Project{}
|
||||
err := xml.Unmarshal(xmlData, &project)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse POM data: %w", err)
|
||||
}
|
||||
return &project, nil
|
||||
}
|
||||
|
||||
// ModuleInfo describes a location and Project of a maven module.
|
||||
type ModuleInfo struct {
|
||||
PomXMLPath string
|
||||
Project *Project
|
||||
}
|
||||
|
||||
type visitUtils interface {
|
||||
FileExists(path string) (bool, error)
|
||||
FileRead(path string) ([]byte, error)
|
||||
}
|
||||
|
||||
// VisitAllMavenModules ...
|
||||
func VisitAllMavenModules(path string, utils visitUtils, excludes []string, callback func(info ModuleInfo) error) error {
|
||||
pomXMLPath := filepath.Join(path, "pom.xml")
|
||||
if piperutils.ContainsString(excludes, pomXMLPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
exists, _ := utils.FileExists(pomXMLPath)
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
pomXMLContents, err := utils.FileRead(pomXMLPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file contents of '%s': %w", pomXMLPath, err)
|
||||
}
|
||||
|
||||
project, err := ParsePOM(pomXMLContents)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse file contents of '%s': %w", pomXMLPath, err)
|
||||
}
|
||||
|
||||
err = callback(ModuleInfo{PomXMLPath: pomXMLPath, Project: project})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(project.Modules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, module := range project.Modules {
|
||||
subPomPath := filepath.Join(path, module)
|
||||
err = VisitAllMavenModules(subPomPath, utils, excludes, callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
65
pkg/maven/model_test.go
Normal file
65
pkg/maven/model_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package maven
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const aggregatorPomXML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>artifact</artifactId>
|
||||
<groupId>group</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>project-aggregator</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>sub1</module>
|
||||
<module>sub2</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
`
|
||||
|
||||
func TestParsePOM(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("no XML data provided gives error", func(t *testing.T) {
|
||||
project, err := ParsePOM(nil)
|
||||
assert.EqualError(t, err, "failed to parse POM data: EOF")
|
||||
assert.Nil(t, project)
|
||||
})
|
||||
|
||||
t.Run("modules evaluated", func(t *testing.T) {
|
||||
project, err := ParsePOM([]byte(aggregatorPomXML))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, project)
|
||||
require.Len(t, project.Modules, 2)
|
||||
assert.Equal(t, project.Modules[0], "sub1")
|
||||
assert.Equal(t, project.Modules[1], "sub2")
|
||||
})
|
||||
|
||||
t.Run("artifact coordinates", func(t *testing.T) {
|
||||
project, err := ParsePOM([]byte(aggregatorPomXML))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, project)
|
||||
assert.Equal(t, project.ArtifactID, "project-aggregator")
|
||||
assert.Equal(t, project.Packaging, "pom")
|
||||
})
|
||||
|
||||
t.Run("parent coordinates", func(t *testing.T) {
|
||||
project, err := ParsePOM([]byte(aggregatorPomXML))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, project)
|
||||
assert.Equal(t, project.Parent.Version, "1.0-SNAPSHOT")
|
||||
assert.Equal(t, project.Parent.GroupID, "group")
|
||||
assert.Equal(t, project.Parent.ArtifactID, "artifact")
|
||||
})
|
||||
}
|
@ -403,3 +403,77 @@ func (f *FilesMock) Abs(path string) (string, error) {
|
||||
f.init()
|
||||
return f.toAbsPath(path), nil
|
||||
}
|
||||
|
||||
// FileMock can be used in places where a io.Closer, io.StringWriter or io.Writer is expected.
|
||||
// It is the concrete type returned from FilesMock.Open()
|
||||
type FileMock struct {
|
||||
absPath string
|
||||
files *FilesMock
|
||||
content []byte
|
||||
}
|
||||
|
||||
// Close mocks freeing the associated OS resources.
|
||||
func (f *FileMock) Close() error {
|
||||
f.files = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteString converts the passed string to a byte array and forwards to Write().
|
||||
func (f *FileMock) WriteString(s string) (n int, err error) {
|
||||
return f.Write([]byte(s))
|
||||
}
|
||||
|
||||
// Write appends the provided byte array to the end of the current virtual file contents.
|
||||
// It fails if the FileMock has been closed already, but it does not fail in case the path
|
||||
// has already been removed from the FilesMock instance that created this FileMock.
|
||||
// In this situation, the written contents will not become visible in the FilesMock.
|
||||
func (f *FileMock) Write(p []byte) (n int, err error) {
|
||||
if f.files == nil {
|
||||
return 0, fmt.Errorf("file is closed")
|
||||
}
|
||||
|
||||
f.content = append(f.content, p...)
|
||||
|
||||
// It is not an error to write to a file that has been removed.
|
||||
// The kernel does reference counting, as long as someone has the file still opened,
|
||||
// it can be written to (and that entity can also still read it).
|
||||
properties, exists := f.files.files[f.absPath]
|
||||
if exists && properties.content != &dirContent {
|
||||
properties.content = &f.content
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Open mimics the behavior os.Open(), but it cannot return an instance of the os.File struct.
|
||||
// Instead, it returns a pointer to a FileMock instance, which implements a number of the same methods as os.File.
|
||||
// The flag parameter is checked for os.O_CREATE and os.O_APPEND and behaves accordingly.
|
||||
func (f *FilesMock) Open(path string, flag int, perm os.FileMode) (*FileMock, error) {
|
||||
if f.files == nil && flag&os.O_CREATE == 0 {
|
||||
return nil, fmt.Errorf("the file '%s' does not exist: %w", path, os.ErrNotExist)
|
||||
}
|
||||
f.init()
|
||||
absPath := f.toAbsPath(path)
|
||||
properties, exists := f.files[absPath]
|
||||
if exists && properties.content == &dirContent {
|
||||
return nil, fmt.Errorf("opening directory not supported")
|
||||
}
|
||||
if !exists && flag&os.O_CREATE != 0 {
|
||||
f.associateContentAbs(absPath, &[]byte{}, perm)
|
||||
properties, _ = f.files[absPath]
|
||||
}
|
||||
|
||||
file := FileMock{
|
||||
absPath: absPath,
|
||||
files: f,
|
||||
content: []byte{},
|
||||
}
|
||||
|
||||
if flag&os.O_APPEND != 0 {
|
||||
file.content = *properties.content
|
||||
} else if flag&os.O_TRUNC != 0 {
|
||||
properties.content = &file.content
|
||||
}
|
||||
|
||||
return &file, nil
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package mock
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@ -377,18 +378,29 @@ func TestFilesMockGlob(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStat(t *testing.T) {
|
||||
files := FilesMock{}
|
||||
files.AddFile("tmp/dummy.txt", []byte("Hello SAP"))
|
||||
explicitMode := os.FileMode(0700)
|
||||
files.AddDirWithMode("bin", explicitMode)
|
||||
|
||||
t.Parallel()
|
||||
t.Run("non existing file (no init)", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
// test
|
||||
_, err := files.Stat("doesNotExist.txt")
|
||||
assert.EqualError(t, err, "stat doesNotExist.txt: no such file or directory")
|
||||
})
|
||||
t.Run("non existing file", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
files.AddFile("tmp/dummy.txt", []byte("Hello SAP"))
|
||||
// test
|
||||
_, err := files.Stat("doesNotExist.txt")
|
||||
assert.EqualError(t, err, "stat doesNotExist.txt: no such file or directory")
|
||||
})
|
||||
t.Run("check file info", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
files.AddFile("tmp/dummy.txt", []byte("Hello SAP"))
|
||||
// test
|
||||
info, err := files.Stat("tmp/dummy.txt")
|
||||
|
||||
// assert
|
||||
if assert.NoError(t, err) {
|
||||
// only the base name is returned.
|
||||
assert.Equal(t, "dummy.txt", info.Name())
|
||||
@ -398,14 +410,25 @@ func TestStat(t *testing.T) {
|
||||
}
|
||||
})
|
||||
t.Run("check implicit dir", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
files.AddFile("tmp/dummy.txt", []byte("Hello SAP"))
|
||||
// test
|
||||
info, err := files.Stat("tmp")
|
||||
// assert
|
||||
if assert.NoError(t, err) {
|
||||
assert.True(t, info.IsDir())
|
||||
assert.Equal(t, defaultDirMode, info.Mode())
|
||||
}
|
||||
})
|
||||
t.Run("check explicit dir", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
explicitMode := os.FileMode(0700)
|
||||
files.AddDirWithMode("bin", explicitMode)
|
||||
// test
|
||||
info, err := files.Stat("bin")
|
||||
// assert
|
||||
if assert.NoError(t, err) {
|
||||
assert.True(t, info.IsDir())
|
||||
assert.Equal(t, explicitMode, info.Mode())
|
||||
@ -506,3 +529,93 @@ func TestRelativePaths(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
filePath := filepath.Join("some", "file")
|
||||
t.Parallel()
|
||||
t.Run("no init without O_CREATE", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
// test
|
||||
file, err := files.Open(filePath, 0, 0)
|
||||
// assert
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "does not exist")
|
||||
assert.Nil(t, file)
|
||||
}
|
||||
})
|
||||
t.Run("no init with O_CREATE", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
// test
|
||||
file, err := files.Open(filePath, os.O_CREATE, 0644)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.NotNil(t, file) {
|
||||
assert.Equal(t, &files, file.files)
|
||||
assert.Equal(t, files.Separator+filePath, file.absPath)
|
||||
assert.NotNil(t, file.content)
|
||||
}
|
||||
})
|
||||
t.Run("content is replaced without O_APPEND", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
files.AddFile(filePath, []byte("initial-content"))
|
||||
// test
|
||||
file, _ := files.Open(filePath, os.O_CREATE, 0644)
|
||||
written, err := file.WriteString("hello")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, written, len("hello"))
|
||||
content, err := files.FileRead(filePath)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, []byte("hello"), content)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("content is truncated with O_TRUNC an nothing written", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
files.AddFile(filePath, []byte("initial-content"))
|
||||
// test
|
||||
file, err := files.Open(filePath, os.O_CREATE|os.O_TRUNC, 0644)
|
||||
require.NoError(t, err)
|
||||
err = file.Close()
|
||||
assert.NoError(t, err)
|
||||
content, err := files.FileRead(filePath)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Len(t, content, 0)
|
||||
}
|
||||
})
|
||||
t.Run("content is appended", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
files.AddFile(filePath, []byte("initial-content"))
|
||||
// test
|
||||
file, _ := files.Open(filePath, os.O_APPEND, 0644)
|
||||
written1, err1 := file.WriteString("-hel")
|
||||
written2, err2 := file.WriteString("lo")
|
||||
if assert.NoError(t, err1) && assert.NoError(t, err2) {
|
||||
assert.Equal(t, written1+written2, len("-hello"))
|
||||
content, err := files.FileRead(filePath)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, []byte("initial-content-hello"), content)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("cannot write to closed file", func(t *testing.T) {
|
||||
// init
|
||||
files := FilesMock{}
|
||||
files.AddFile(filePath, []byte("initial-content"))
|
||||
// test
|
||||
file, _ := files.Open(filePath, os.O_APPEND, 0644)
|
||||
_, err := file.WriteString("-hello")
|
||||
assert.NoError(t, err)
|
||||
err = file.Close()
|
||||
assert.NoError(t, err)
|
||||
_, err = file.WriteString("-more")
|
||||
assert.Error(t, err)
|
||||
content, err := files.FileRead(filePath)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, []byte("initial-content-hello"), content)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
//ContainsInt check whether the element is part of the slice
|
||||
//ContainsInt checks whether the element is part of the slice
|
||||
func ContainsInt(s []int, e int) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
@ -14,7 +14,7 @@ func ContainsInt(s []int, e int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
//ContainsString check whether the element is part of the slice
|
||||
//ContainsString checks whether the element is part of the slice
|
||||
func ContainsString(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
@ -24,7 +24,7 @@ func ContainsString(s []string, e string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
//ContainsStringPart check wether the element is contained as part of one of the elements of the slice
|
||||
//ContainsStringPart checks whether the element is contained as part of one of the elements of the slice
|
||||
func ContainsStringPart(s []string, part string) bool {
|
||||
for _, a := range s {
|
||||
if strings.Contains(a, part) {
|
||||
@ -34,6 +34,18 @@ func ContainsStringPart(s []string, part string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveAll removes all instances of element from the slice and returns a truncated slice as well as
|
||||
// a boolean to indicate whether at least one element was found and removed.
|
||||
func RemoveAll(s []string, e string) ([]string, bool) {
|
||||
var r []string
|
||||
for _, a := range s {
|
||||
if a != e {
|
||||
r = append(r, a)
|
||||
}
|
||||
}
|
||||
return r, len(s) != len(r)
|
||||
}
|
||||
|
||||
//Prefix adds a prefix to each element of the slice
|
||||
func Prefix(in []string, prefix string) []string {
|
||||
return _prefix(in, prefix, true)
|
||||
|
@ -29,6 +29,40 @@ func TestContainsString(t *testing.T) {
|
||||
assert.False(t, ContainsString(stringList, "baz"))
|
||||
}
|
||||
|
||||
func TestRemoveAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("empty array", func(t *testing.T) {
|
||||
result, removed := RemoveAll([]string{}, "A")
|
||||
assert.Len(t, result, 0)
|
||||
assert.False(t, removed)
|
||||
})
|
||||
t.Run("two As", func(t *testing.T) {
|
||||
result, removed := RemoveAll([]string{"A", "B", "C", "A", "C", "", "D"}, "A")
|
||||
assert.Equal(t, []string{"B", "C", "C", "", "D"}, result)
|
||||
assert.True(t, removed)
|
||||
})
|
||||
t.Run("one B", func(t *testing.T) {
|
||||
result, removed := RemoveAll([]string{"A", "B", "C", "A", "C", "", "D"}, "B")
|
||||
assert.Equal(t, []string{"A", "C", "A", "C", "", "D"}, result)
|
||||
assert.True(t, removed)
|
||||
})
|
||||
t.Run("empty e", func(t *testing.T) {
|
||||
result, removed := RemoveAll([]string{"A", "B", "C", "A", "C", "", "D"}, "")
|
||||
assert.Equal(t, []string{"A", "B", "C", "A", "C", "D"}, result)
|
||||
assert.True(t, removed)
|
||||
})
|
||||
t.Run("one D", func(t *testing.T) {
|
||||
result, removed := RemoveAll([]string{"A", "B", "C", "A", "C", "", "D"}, "D")
|
||||
assert.Equal(t, []string{"A", "B", "C", "A", "C", ""}, result)
|
||||
assert.True(t, removed)
|
||||
})
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
result, removed := RemoveAll([]string{"A", "B", "C", "A", "C", "", "D"}, "X")
|
||||
assert.Equal(t, []string{"A", "B", "C", "A", "C", "", "D"}, result)
|
||||
assert.False(t, removed)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrefix(t *testing.T) {
|
||||
// init
|
||||
s := []string{"tree", "pie", "applejuice"}
|
||||
|
@ -137,7 +137,11 @@ func GetArtifact(buildTool, buildDescriptorFilePath string, opts *Options, execR
|
||||
}
|
||||
case "sbt":
|
||||
if len(buildDescriptorFilePath) == 0 {
|
||||
buildDescriptorFilePath = "sbtDescriptor.json"
|
||||
var err error
|
||||
buildDescriptorFilePath, err = searchDescriptor([]string{"sbtDescriptor.json", "build.sbt"}, fileExists)
|
||||
if err != nil {
|
||||
return artifact, err
|
||||
}
|
||||
}
|
||||
artifact = &JSONfile{
|
||||
path: buildDescriptorFilePath,
|
||||
|
@ -126,6 +126,7 @@ func TestGetArtifact(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("sbt", func(t *testing.T) {
|
||||
fileExists = func(string) (bool, error) { return true, nil }
|
||||
sbt, err := GetArtifact("sbt", "", &Options{VersionField: "theversion"}, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
// YAMLDescriptor holds the unique identifier combination for an artifact
|
||||
type YAMLDescriptor struct {
|
||||
GroupID string
|
||||
ArtifactID string
|
||||
Version string
|
||||
}
|
||||
|
@ -86,6 +86,10 @@ stages:
|
||||
whitesourceScan:
|
||||
onlyProductiveBranch: true
|
||||
stepConditions:
|
||||
whitesourceExecuteScan:
|
||||
configKeys:
|
||||
- 'product'
|
||||
- 'orgAdminUserTokenCredentialsId'
|
||||
whitesourceScan:
|
||||
configKeys:
|
||||
- 'product'
|
||||
|
@ -41,7 +41,6 @@ spec:
|
||||
- STEPS
|
||||
- STAGES
|
||||
- PARAMETERS
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: maven/projectSettingsFile
|
||||
- name: globalSettingsFile
|
||||
@ -52,7 +51,6 @@ spec:
|
||||
- STEPS
|
||||
- STAGES
|
||||
- PARAMETERS
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: maven/globalSettingsFile
|
||||
- name: m2Path
|
||||
@ -63,7 +61,6 @@ spec:
|
||||
- STEPS
|
||||
- STAGES
|
||||
- PARAMETERS
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: maven/m2Path
|
||||
- name: logSuccessfulMavenTransfers
|
||||
@ -75,7 +72,6 @@ spec:
|
||||
- STAGES
|
||||
- PARAMETERS
|
||||
default: false
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: maven/logSuccessfulMavenTransfers
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user