1
0
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:
Stephan Aßmus 2020-09-29 12:44:31 +02:00 committed by GitHub
parent e2aea73022
commit 54ea3ed51a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1861 additions and 181 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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
View 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")
})
}

View File

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

View File

@ -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)
}
})
}

View File

@ -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)

View File

@ -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"}

View File

@ -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,

View File

@ -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)

View File

@ -12,6 +12,7 @@ import (
// YAMLDescriptor holds the unique identifier combination for an artifact
type YAMLDescriptor struct {
GroupID string
ArtifactID string
Version string
}

View File

@ -86,6 +86,10 @@ stages:
whitesourceScan:
onlyProductiveBranch: true
stepConditions:
whitesourceExecuteScan:
configKeys:
- 'product'
- 'orgAdminUserTokenCredentialsId'
whitesourceScan:
configKeys:
- 'product'

View File

@ -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