mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-16 05:16:08 +02:00
whitesourceExecuteScan-go: Implement parameters "timeout", "createProductFromPipeline" (#2246)
This commit is contained in:
parent
f5df553dae
commit
86f335811c
@ -27,6 +27,8 @@ type ScanOptions = whitesourceExecuteScanOptions
|
||||
// be available from the whitesource system.
|
||||
type whitesource interface {
|
||||
GetProductByName(productName string) (ws.Product, error)
|
||||
CreateProduct(productName string) (string, error)
|
||||
SetProductAssignments(productToken string, membership, admins, alertReceivers *ws.Assignment) error
|
||||
GetProjectsMetaInfo(productToken string) ([]ws.Project, error)
|
||||
GetProjectToken(productToken, projectName string) (string, error)
|
||||
GetProjectByToken(projectToken string) (ws.Project, error)
|
||||
@ -84,7 +86,7 @@ func (w *whitesourceUtilsBundle) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func newWhitesourceUtils() *whitesourceUtilsBundle {
|
||||
func newWhitesourceUtils(config *ScanOptions) *whitesourceUtilsBundle {
|
||||
utils := whitesourceUtilsBundle{
|
||||
Client: &piperhttp.Client{},
|
||||
Command: &command.Command{},
|
||||
@ -93,6 +95,8 @@ func newWhitesourceUtils() *whitesourceUtilsBundle {
|
||||
// Reroute cmd output to logging framework
|
||||
utils.Stdout(log.Writer())
|
||||
utils.Stderr(log.Writer())
|
||||
// Configure HTTP Client
|
||||
utils.SetOptions(piperhttp.ClientOptions{TransportTimeout: time.Duration(config.Timeout) * time.Second})
|
||||
return &utils
|
||||
}
|
||||
|
||||
@ -104,9 +108,10 @@ func newWhitesourceScan(config *ScanOptions) *ws.Scan {
|
||||
}
|
||||
|
||||
func whitesourceExecuteScan(config ScanOptions, _ *telemetry.CustomData) {
|
||||
utils := newWhitesourceUtils()
|
||||
utils := newWhitesourceUtils(&config)
|
||||
scan := newWhitesourceScan(&config)
|
||||
sys := ws.NewSystem(config.ServiceURL, config.OrgToken, config.UserToken)
|
||||
sys := ws.NewSystem(config.ServiceURL, config.OrgToken, config.UserToken,
|
||||
time.Duration(config.Timeout)*time.Second)
|
||||
err := runWhitesourceExecuteScan(&config, scan, utils, sys)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatal("step execution failed")
|
||||
@ -192,6 +197,26 @@ func checkAndReportScanResults(config *ScanOptions, scan *ws.Scan, utils whiteso
|
||||
return nil
|
||||
}
|
||||
|
||||
func createWhiteSourceProduct(config *ScanOptions, sys whitesource) (string, error) {
|
||||
log.Entry().Infof("Attempting to create new WhiteSource product for '%s'..", config.ProductName)
|
||||
productToken, err := sys.CreateProduct(config.ProductName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create WhiteSource product: %w", err)
|
||||
}
|
||||
|
||||
var admins ws.Assignment
|
||||
for _, address := range config.EmailAddressesOfInitialProductAdmins {
|
||||
admins.UserAssignments = append(admins.UserAssignments, ws.UserAssignment{Email: address})
|
||||
}
|
||||
|
||||
err = sys.SetProductAssignments(productToken, nil, &admins, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to set admins on new WhiteSource product: %w", err)
|
||||
}
|
||||
|
||||
return productToken, nil
|
||||
}
|
||||
|
||||
func resolveProjectIdentifiers(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource) error {
|
||||
if scan.AggregateProjectName == "" || config.ProductVersion == "" {
|
||||
options := &versioning.Options{
|
||||
@ -218,38 +243,66 @@ func resolveProjectIdentifiers(config *ScanOptions, scan *ws.Scan, utils whiteso
|
||||
}
|
||||
scan.ProductVersion = validateProductVersion(config.ProductVersion)
|
||||
|
||||
// Get product token if user did not specify one at runtime
|
||||
if config.ProductToken == "" {
|
||||
log.Entry().Infof("Attempting to resolve product token for product '%s'..", config.ProductName)
|
||||
product, err := sys.GetProductByName(config.ProductName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Entry().Infof("Resolved product token: '%s'..", product.Token)
|
||||
config.ProductToken = product.Token
|
||||
if err := resolveProductToken(config, sys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get project token if user did not specify one at runtime
|
||||
if config.ProjectToken == "" && config.ProjectName != "" {
|
||||
log.Entry().Infof("Attempting to resolve project token for project '%s'..", config.ProjectName)
|
||||
fullProjName := fmt.Sprintf("%s - %s", config.ProjectName, config.ProductVersion)
|
||||
projectToken, err := sys.GetProjectToken(config.ProductToken, fullProjName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// A project may not yet exist for this project name-version combo
|
||||
// It will be created by the scan, we retrieve the token again after scanning.
|
||||
if projectToken != "" {
|
||||
log.Entry().Infof("Resolved project token: '%s'..", projectToken)
|
||||
config.ProjectToken = projectToken
|
||||
} else {
|
||||
log.Entry().Infof("Project '%s' not yet present in WhiteSource", fullProjName)
|
||||
}
|
||||
if err := resolveAggregateProjectToken(config, sys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return scan.UpdateProjects(config.ProductToken, sys)
|
||||
}
|
||||
|
||||
// resolveProductToken resolves the token of the WhiteSource Product specified by config.ProductName,
|
||||
// unless the user provided a token in config.ProductToken already, or it was previously resolved.
|
||||
// If no Product can be found for the given config.ProductName, and the parameter
|
||||
// config.CreatePipelineFromProduct is set, an attempt will be made to create the product and
|
||||
// configure the initial product admins.
|
||||
func resolveProductToken(config *ScanOptions, sys whitesource) error {
|
||||
if config.ProductToken != "" {
|
||||
return nil
|
||||
}
|
||||
log.Entry().Infof("Attempting to resolve product token for product '%s'..", config.ProductName)
|
||||
product, err := sys.GetProductByName(config.ProductName)
|
||||
if err != nil && config.CreateProductFromPipeline {
|
||||
product = ws.Product{}
|
||||
product.Token, err = createWhiteSourceProduct(config, sys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Entry().Infof("Resolved product token: '%s'..", product.Token)
|
||||
config.ProductToken = product.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveAggregateProjectToken fetches the token of the WhiteSource Project specified by config.ProjectName
|
||||
// and stores it in config.ProjectToken.
|
||||
// The user can configure a projectName or projectToken of the project to be used as for aggregation of scan results.
|
||||
func resolveAggregateProjectToken(config *ScanOptions, sys whitesource) error {
|
||||
if config.ProjectToken != "" || config.ProjectName == "" {
|
||||
return nil
|
||||
}
|
||||
log.Entry().Infof("Attempting to resolve project token for project '%s'..", config.ProjectName)
|
||||
fullProjName := fmt.Sprintf("%s - %s", config.ProjectName, config.ProductVersion)
|
||||
projectToken, err := sys.GetProjectToken(config.ProductToken, fullProjName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// A project may not yet exist for this project name-version combo.
|
||||
// It will be created by the scan, we retrieve the token again after scanning.
|
||||
if projectToken != "" {
|
||||
log.Entry().Infof("Resolved project token: '%s'..", projectToken)
|
||||
config.ProjectToken = projectToken
|
||||
} else {
|
||||
log.Entry().Infof("Project '%s' not yet present in WhiteSource", fullProjName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateProductVersion makes sure that the version does not contain a dash "-".
|
||||
func validateProductVersion(version string) string {
|
||||
// TrimLeft() removes all "-" from the beginning, unlike TrimPrefix()!
|
||||
@ -411,8 +464,6 @@ func pollProjectStatus(projectToken string, scanTime time.Time, sys whitesource)
|
||||
return blockUntilProjectIsUpdated(projectToken, sys, scanTime, 20*time.Second, 20*time.Second, 15*time.Minute)
|
||||
}
|
||||
|
||||
const whitesourceDateTimeLayout = "2006-01-02 15:04:05 -0700"
|
||||
|
||||
// blockUntilProjectIsUpdated polls the project LastUpdateDate until it is newer than the given time stamp
|
||||
// or no older than maxAge relative to the given time stamp.
|
||||
func blockUntilProjectIsUpdated(projectToken string, sys whitesource, currentTime time.Time, maxAge, timeBetweenPolls, maxWaitTime time.Duration) error {
|
||||
@ -426,7 +477,7 @@ func blockUntilProjectIsUpdated(projectToken string, sys whitesource, currentTim
|
||||
if project.LastUpdateDate == "" {
|
||||
log.Entry().Infof("last updated time missing from project metadata, retrying")
|
||||
} else {
|
||||
lastUpdatedTime, err := time.Parse(whitesourceDateTimeLayout, project.LastUpdateDate)
|
||||
lastUpdatedTime, err := time.Parse(ws.DateTimeLayout, project.LastUpdateDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse last updated time (%s) of Whitesource project: %w",
|
||||
project.LastUpdateDate, err)
|
||||
|
@ -19,7 +19,7 @@ type whitesourceExecuteScanOptions struct {
|
||||
VersioningModel string `json:"versioningModel,omitempty"`
|
||||
CreateProductFromPipeline bool `json:"createProductFromPipeline,omitempty"`
|
||||
SecurityVulnerabilities bool `json:"securityVulnerabilities,omitempty"`
|
||||
Timeout string `json:"timeout,omitempty"`
|
||||
Timeout int `json:"timeout,omitempty"`
|
||||
AgentDownloadURL string `json:"agentDownloadUrl,omitempty"`
|
||||
ConfigFilePath string `json:"configFilePath,omitempty"`
|
||||
ReportDirectoryName string `json:"reportDirectoryName,omitempty"`
|
||||
@ -131,34 +131,34 @@ func addWhitesourceExecuteScanFlags(cmd *cobra.Command, stepConfig *whitesourceE
|
||||
cmd.Flags().StringVar(&stepConfig.VersioningModel, "versioningModel", `major`, "The default project versioning model used in case `projectVersion` parameter is empty for creating the version based on the build descriptor version to report results in Whitesource, can be one of `'major'`, `'major-minor'`, `'semantic'`, `'full'`")
|
||||
cmd.Flags().BoolVar(&stepConfig.CreateProductFromPipeline, "createProductFromPipeline", true, "Whether to create the related WhiteSource product on the fly based on the supplied pipeline configuration.")
|
||||
cmd.Flags().BoolVar(&stepConfig.SecurityVulnerabilities, "securityVulnerabilities", true, "Whether security compliance is considered and reported as part of the assessment.")
|
||||
cmd.Flags().StringVar(&stepConfig.Timeout, "timeout", `0`, "Timeout in seconds until a HTTP call is forcefully terminated.")
|
||||
cmd.Flags().IntVar(&stepConfig.Timeout, "timeout", 900, "Timeout in seconds until an HTTP call is forcefully terminated.")
|
||||
cmd.Flags().StringVar(&stepConfig.AgentDownloadURL, "agentDownloadUrl", `https://github.com/whitesource/unified-agent-distribution/releases/latest/download/wss-unified-agent.jar`, "URL used to download the latest version of the WhiteSource Unified Agent.")
|
||||
cmd.Flags().StringVar(&stepConfig.ConfigFilePath, "configFilePath", `./wss-generated-file.config`, "Explicit path to the WhiteSource Unified Agent configuration file.")
|
||||
cmd.Flags().StringVar(&stepConfig.ReportDirectoryName, "reportDirectoryName", `whitesource-reports`, "Name of the directory to save vulnerability/risk reports to")
|
||||
cmd.Flags().BoolVar(&stepConfig.AggregateVersionWideReport, "aggregateVersionWideReport", false, "This does not run a scan, instead just generated a report for all projects with projectVersion = config.ProductVersion")
|
||||
cmd.Flags().StringVar(&stepConfig.VulnerabilityReportFormat, "vulnerabilityReportFormat", `xlsx`, "Format of the file the vulnerability report is written to.")
|
||||
cmd.Flags().StringVar(&stepConfig.ParallelLimit, "parallelLimit", `15`, "Limit of parallel jobs being run at once in case of `scanType: 'mta'` based scenarios, defaults to `15`.")
|
||||
cmd.Flags().StringVar(&stepConfig.ParallelLimit, "parallelLimit", `15`, "[NOT IMPLEMENTED] Limit of parallel jobs being run at once in case of `scanType: 'mta'` based scenarios, defaults to `15`.")
|
||||
cmd.Flags().BoolVar(&stepConfig.Reporting, "reporting", true, "Whether assessment is being done at all, defaults to `true`")
|
||||
cmd.Flags().StringVar(&stepConfig.ServiceURL, "serviceUrl", `https://saas.whitesourcesoftware.com/api`, "URL to the WhiteSource server API used for communication.")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.BuildDescriptorExcludeList, "buildDescriptorExcludeList", []string{`unit-tests/pom.xml`, `integration-tests/pom.xml`}, "List of build descriptors and therefore modules to exclude from the scan and assessment activities.")
|
||||
cmd.Flags().StringVar(&stepConfig.OrgToken, "orgToken", os.Getenv("PIPER_orgToken"), "WhiteSource token identifying your organization.")
|
||||
cmd.Flags().StringVar(&stepConfig.UserToken, "userToken", os.Getenv("PIPER_userToken"), "WhiteSource token identifying the user executing the scan")
|
||||
cmd.Flags().BoolVar(&stepConfig.LicensingVulnerabilities, "licensingVulnerabilities", true, "Whether license compliance is considered and reported as part of the assessment.")
|
||||
cmd.Flags().StringVar(&stepConfig.UserToken, "userToken", os.Getenv("PIPER_userToken"), "WhiteSource token identifying the user executing the scan.")
|
||||
cmd.Flags().BoolVar(&stepConfig.LicensingVulnerabilities, "licensingVulnerabilities", true, "[NOT IMPLEMENTED] Whether license compliance is considered and reported as part of the assessment.")
|
||||
cmd.Flags().StringVar(&stepConfig.AgentFileName, "agentFileName", `wss-unified-agent.jar`, "Locally used name for the Unified Agent jar file after download.")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.EmailAddressesOfInitialProductAdmins, "emailAddressesOfInitialProductAdmins", []string{}, "The list of email addresses to assign as product admins for newly created WhiteSource products.")
|
||||
cmd.Flags().StringVar(&stepConfig.ProductVersion, "productVersion", os.Getenv("PIPER_productVersion"), "Version of the WhiteSource product to be created and used for results aggregation, usually determined automatically.")
|
||||
cmd.Flags().StringVar(&stepConfig.JreDownloadURL, "jreDownloadUrl", os.Getenv("PIPER_jreDownloadUrl"), "URL used for downloading the Java Runtime Environment (JRE) required to run the WhiteSource Unified Agent.")
|
||||
cmd.Flags().StringVar(&stepConfig.ProductName, "productName", os.Getenv("PIPER_productName"), "Name of the WhiteSource product to be created and used for results aggregation.")
|
||||
cmd.Flags().StringVar(&stepConfig.ProjectName, "projectName", os.Getenv("PIPER_projectName"), "The project used for reporting results in Whitesource")
|
||||
cmd.Flags().StringVar(&stepConfig.ProjectToken, "projectToken", os.Getenv("PIPER_projectToken"), "Project token to execute scan on")
|
||||
cmd.Flags().StringVar(&stepConfig.JreDownloadURL, "jreDownloadUrl", os.Getenv("PIPER_jreDownloadUrl"), "[NOT IMPLEMENTED] URL used for downloading the Java Runtime Environment (JRE) required to run the WhiteSource Unified Agent.")
|
||||
cmd.Flags().StringVar(&stepConfig.ProductName, "productName", os.Getenv("PIPER_productName"), "Name of the WhiteSource product used for results aggregation. This parameter is mandatory if the parameter `createProductFromPipeline` is set to `true` and the WhiteSource product does not yet exist. It is also mandatory if the parameter `productToken` is not provided.")
|
||||
cmd.Flags().StringVar(&stepConfig.ProjectName, "projectName", os.Getenv("PIPER_projectName"), "The project name used for reporting results in WhiteSource. When provided, all source modules will be scanned into one aggregated WhiteSource project. For scan types `maven`, `mta`, `npm`, the default is to generate one WhiteSource project per module, whereas the project name is derived from the module's build descriptor. For NPM modules, project aggregation is not supported, the last scanned NPM module will override all previously aggregated scan results!")
|
||||
cmd.Flags().StringVar(&stepConfig.ProjectToken, "projectToken", os.Getenv("PIPER_projectToken"), "Project token to execute scan on. Ignored for scan types `maven`, `mta` and `npm`. Used for project aggregation when scanning with the Unified Agent and can be provided as an alternative to `projectName`.")
|
||||
cmd.Flags().StringVar(&stepConfig.VulnerabilityReportTitle, "vulnerabilityReportTitle", `WhiteSource Security Vulnerability Report`, "Title of vulnerability report written during the assessment phase.")
|
||||
cmd.Flags().StringVar(&stepConfig.InstallCommand, "installCommand", os.Getenv("PIPER_installCommand"), "Install command that can be used to populate the default docker image for some scenarios.")
|
||||
cmd.Flags().StringVar(&stepConfig.ScanType, "scanType", os.Getenv("PIPER_scanType"), "Type of development stack used to implement the solution.")
|
||||
cmd.Flags().StringVar(&stepConfig.InstallCommand, "installCommand", os.Getenv("PIPER_installCommand"), "[NOT IMPLEMENTED] Install command that can be used to populate the default docker image for some scenarios.")
|
||||
cmd.Flags().StringVar(&stepConfig.ScanType, "scanType", os.Getenv("PIPER_scanType"), "Type of development stack used to implement the solution. For scan types other than `mta`, `maven`, and `npm`, the WhiteSource Unified Agent is downloaded and used to perform the scan. If the parameter is not provided, it is derived from the parameter `buildTool`, which is usually configured in the general section of the pipeline config file.")
|
||||
cmd.Flags().StringVar(&stepConfig.CvssSeverityLimit, "cvssSeverityLimit", `-1`, "Limit of tolerable CVSS v3 score upon assessment and in consequence fails the build, defaults to `-1`.")
|
||||
cmd.Flags().StringVar(&stepConfig.Includes, "includes", `**\/src\/main\/**\/*.java **\/*.py **\/*.go **\/*.js **\/*.ts`, "Space separated list of file path patterns to include in the scan, slashes must be escaped for sed.")
|
||||
cmd.Flags().StringVar(&stepConfig.Excludes, "excludes", `tests/**/*.py **/src/test/**/*.java`, "Space separated list of file path patterns to exclude in the scan")
|
||||
cmd.Flags().StringVar(&stepConfig.ProductToken, "productToken", os.Getenv("PIPER_productToken"), "Token of the WhiteSource product to be created and used for results aggregation, usually determined automatically.")
|
||||
cmd.Flags().StringVar(&stepConfig.AgentParameters, "agentParameters", os.Getenv("PIPER_agentParameters"), "Additional parameters passed to the Unified Agent command line.")
|
||||
cmd.Flags().StringVar(&stepConfig.ProductToken, "productToken", os.Getenv("PIPER_productToken"), "Token of the WhiteSource product to be created and used for results aggregation, usually determined automatically. Can optionally be provided as an alternative to `productName`.")
|
||||
cmd.Flags().StringVar(&stepConfig.AgentParameters, "agentParameters", os.Getenv("PIPER_agentParameters"), "[NOT IMPLEMENTED] Additional parameters passed to the Unified Agent command line.")
|
||||
cmd.Flags().StringVar(&stepConfig.ProjectSettingsFile, "projectSettingsFile", os.Getenv("PIPER_projectSettingsFile"), "Path to the mvn settings file that should be used as project settings file.")
|
||||
cmd.Flags().StringVar(&stepConfig.GlobalSettingsFile, "globalSettingsFile", os.Getenv("PIPER_globalSettingsFile"), "Path to the mvn settings file that should be used as global settings file.")
|
||||
cmd.Flags().StringVar(&stepConfig.M2Path, "m2Path", os.Getenv("PIPER_m2Path"), "Path to the location of the local repository that should be used.")
|
||||
@ -229,7 +229,7 @@ func whitesourceExecuteScanMetadata() config.StepData {
|
||||
Name: "timeout",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Type: "int",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
|
@ -122,6 +122,26 @@ func TestResolveProjectIdentifiers(t *testing.T) {
|
||||
// assert
|
||||
assert.EqualError(t, err, "no product with name 'does-not-exist' found in Whitesource")
|
||||
})
|
||||
t.Run("product not found, created from pipeline", func(t *testing.T) {
|
||||
// init
|
||||
config := ScanOptions{
|
||||
BuildTool: "mta",
|
||||
CreateProductFromPipeline: true,
|
||||
EmailAddressesOfInitialProductAdmins: []string{"user1@domain.org", "user2@domain.org"},
|
||||
VersioningModel: "major",
|
||||
ProductName: "created-by-pipeline",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
systemMock := ws.NewSystemMock("ignored")
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock)
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, systemMock.Products, 2)
|
||||
assert.Equal(t, "created-by-pipeline", systemMock.Products[1].Name)
|
||||
assert.Equal(t, "mock-product-token-1", config.ProductToken)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
@ -129,7 +149,7 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
t.Run("already new enough", func(t *testing.T) {
|
||||
// init
|
||||
nowString := "2010-05-30 00:15:00 +0100"
|
||||
now, err := time.Parse(whitesourceDateTimeLayout, nowString)
|
||||
now, err := time.Parse(ws.DateTimeLayout, nowString)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
@ -143,7 +163,7 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
t.Run("timeout while polling", func(t *testing.T) {
|
||||
// init
|
||||
nowString := "2010-05-30 00:15:00 +0100"
|
||||
now, err := time.Parse(whitesourceDateTimeLayout, nowString)
|
||||
now, err := time.Parse(ws.DateTimeLayout, nowString)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
@ -159,7 +179,7 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
t.Run("timeout while polling, no update time", func(t *testing.T) {
|
||||
// init
|
||||
nowString := "2010-05-30 00:15:00 +0100"
|
||||
now, err := time.Parse(whitesourceDateTimeLayout, nowString)
|
||||
now, err := time.Parse(ws.DateTimeLayout, nowString)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
@ -297,14 +317,14 @@ func TestCheckAndReportScanResults(t *testing.T) {
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := ws.NewSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
vPath := filepath.Join("report-dir", "mock-project-vulnerability-report.txt")
|
||||
vPath := filepath.Join("mock-reports", "mock-project-vulnerability-report.txt")
|
||||
assert.False(t, utils.HasWrittenFile(vPath))
|
||||
rPath := filepath.Join("report-dir", "mock-project-risk-report.pdf")
|
||||
rPath := filepath.Join("mock-reports", "mock-project-risk-report.pdf")
|
||||
assert.False(t, utils.HasWrittenFile(rPath))
|
||||
})
|
||||
t.Run("check vulnerabilities - invalid limit", func(t *testing.T) {
|
||||
@ -315,7 +335,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := ws.NewSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
@ -333,7 +353,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := ws.NewSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
@ -352,7 +372,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := ws.NewSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
|
@ -2,7 +2,11 @@
|
||||
|
||||
package whitesource
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SystemMock stores a number of WhiteSource objects and, based on that, mocks the behavior of System.
|
||||
type SystemMock struct {
|
||||
@ -25,6 +29,31 @@ func (m *SystemMock) GetProductByName(productName string) (Product, error) {
|
||||
return Product{}, fmt.Errorf("no product with name '%s' found in Whitesource", productName)
|
||||
}
|
||||
|
||||
// CreateProduct appends a new Product to the system mock and returns its token ("mock-product-token-<index>").
|
||||
func (m *SystemMock) CreateProduct(productName string) (string, error) {
|
||||
now := time.Now().Format(DateTimeLayout)
|
||||
productIndex := len(m.Products)
|
||||
product := Product{
|
||||
Name: productName,
|
||||
Token: "mock-product-token-" + strconv.Itoa(productIndex),
|
||||
CreationDate: now,
|
||||
LastUpdateDate: now,
|
||||
}
|
||||
m.Products = append(m.Products, product)
|
||||
return product.Token, nil
|
||||
}
|
||||
|
||||
// SetProductAssignments checks if the system mock contains a product with the given token and returns
|
||||
// an error depending on that, but otherwise does nothing with the provided arguments.
|
||||
func (m *SystemMock) SetProductAssignments(productToken string, _, _, _ *Assignment) error {
|
||||
for _, product := range m.Products {
|
||||
if product.Token == productToken {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("no product with that token")
|
||||
}
|
||||
|
||||
// GetProjectsMetaInfo returns the list of Projects stored in the mock or an error if token is unknown.
|
||||
func (m *SystemMock) GetProjectsMetaInfo(productToken string) ([]Project, error) {
|
||||
for _, product := range m.Products {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
@ -20,6 +21,23 @@ type Product struct {
|
||||
LastUpdateDate string `json:"lastUpdatedDate,omitempty"`
|
||||
}
|
||||
|
||||
// Assignment describes a list of UserAssignments and GroupAssignments which can be attributed to a WhiteSource Product.
|
||||
type Assignment struct {
|
||||
UserAssignments []UserAssignment `json:"userAssignments,omitempty"`
|
||||
GroupAssignments []GroupAssignment `json:"groupAssignments,omitempty"`
|
||||
}
|
||||
|
||||
// UserAssignment holds an email address for a WhiteSource user
|
||||
// which can be assigned to a WhiteSource Product in a specific role.
|
||||
type UserAssignment struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// GroupAssignment refers to the name of a particular group in WhiteSource.
|
||||
type GroupAssignment struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Alert
|
||||
type Alert struct {
|
||||
Vulnerability Vulnerability `json:"vulnerability"`
|
||||
@ -63,13 +81,18 @@ type Project struct {
|
||||
|
||||
// Request defines a request object to be sent to the WhiteSource system
|
||||
type Request struct {
|
||||
RequestType string `json:"requestType,omitempty"`
|
||||
UserKey string `json:"userKey,omitempty"`
|
||||
ProductToken string `json:"productToken,omitempty"`
|
||||
ProductName string `json:"productName,omitempty"`
|
||||
ProjectToken string `json:"projectToken,omitempty"`
|
||||
OrgToken string `json:"orgToken,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
RequestType string `json:"requestType,omitempty"`
|
||||
UserKey string `json:"userKey,omitempty"`
|
||||
ProductToken string `json:"productToken,omitempty"`
|
||||
ProductName string `json:"productName,omitempty"`
|
||||
ProjectToken string `json:"projectToken,omitempty"`
|
||||
OrgToken string `json:"orgToken,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
ProductAdmins *Assignment `json:"productAdmins,omitempty"`
|
||||
ProductMembership *Assignment `json:"productMembership,omitempty"`
|
||||
AlertsEmailReceivers *Assignment `json:"alertsEmailReceivers,omitempty"`
|
||||
ProductApprovers *Assignment `json:"productApprovers,omitempty"`
|
||||
ProductIntegrators *Assignment `json:"productIntegrators,omitempty"`
|
||||
}
|
||||
|
||||
// System defines a WhiteSource System including respective tokens (e.g. org token, user token)
|
||||
@ -80,13 +103,18 @@ type System struct {
|
||||
userToken string
|
||||
}
|
||||
|
||||
// DateTimeLayout is the layout of the time format used by the WhiteSource API.
|
||||
const DateTimeLayout = "2006-01-02 15:04:05 -0700"
|
||||
|
||||
// NewSystem constructs a new System instance
|
||||
func NewSystem(serverURL, orgToken, userToken string) *System {
|
||||
func NewSystem(serverURL, orgToken, userToken string, timeout time.Duration) *System {
|
||||
httpClient := &piperhttp.Client{}
|
||||
httpClient.SetOptions(piperhttp.ClientOptions{TransportTimeout: timeout})
|
||||
return &System{
|
||||
serverURL: serverURL,
|
||||
orgToken: orgToken,
|
||||
userToken: userToken,
|
||||
httpClient: &piperhttp.Client{},
|
||||
httpClient: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,6 +154,45 @@ func (s *System) GetProductByName(productName string) (Product, error) {
|
||||
return Product{}, fmt.Errorf("product '%v' not found in WhiteSource", productName)
|
||||
}
|
||||
|
||||
// CreateProduct creates a new WhiteSource product and returns its product token.
|
||||
func (s *System) CreateProduct(productName string) (string, error) {
|
||||
wsResponse := struct {
|
||||
ProductToken string `json:"productToken"`
|
||||
}{
|
||||
ProductToken: "",
|
||||
}
|
||||
|
||||
req := Request{
|
||||
RequestType: "createProduct",
|
||||
ProductName: productName,
|
||||
}
|
||||
|
||||
err := s.sendRequestAndDecodeJSON(req, &wsResponse)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "WhiteSource request failed")
|
||||
}
|
||||
|
||||
return wsResponse.ProductToken, nil
|
||||
}
|
||||
|
||||
// SetProductAssignments assigns various types of membership to a WhiteSource Product.
|
||||
func (s *System) SetProductAssignments(productToken string, membership, admins, alertReceivers *Assignment) error {
|
||||
req := Request{
|
||||
RequestType: "setProductAssignments",
|
||||
ProductToken: productToken,
|
||||
ProductMembership: membership,
|
||||
ProductAdmins: admins,
|
||||
AlertsEmailReceivers: alertReceivers,
|
||||
}
|
||||
|
||||
err := s.sendRequestAndDecodeJSON(req, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "WhiteSource request failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProjectsMetaInfo retrieves the registered projects for a specific WhiteSource product
|
||||
func (s *System) GetProjectsMetaInfo(productToken string) ([]Project, error) {
|
||||
wsResponse := struct {
|
||||
@ -343,19 +410,21 @@ func (s *System) sendRequestAndDecodeJSON(req Request, result interface{}) error
|
||||
log.Entry().Debugf("response: %v", string(respBody))
|
||||
|
||||
errorResponse := struct {
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorCode int `json:"errorCode"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal(respBody, &errorResponse)
|
||||
if err == nil && errorResponse.ErrorCode != "" {
|
||||
return fmt.Errorf("invalid request, error code %s, message '%s'",
|
||||
if err == nil && errorResponse.ErrorCode != 0 {
|
||||
return fmt.Errorf("invalid request, error code %v, message '%s'",
|
||||
errorResponse.ErrorCode, errorResponse.ErrorMessage)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(respBody, result)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse WhiteSource response")
|
||||
if result != nil {
|
||||
err = json.Unmarshal(respBody, result)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse WhiteSource response")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ package whitesource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type whitesourceMockClient struct {
|
||||
@ -57,6 +57,42 @@ func TestGetProductsMetaInfo(t *testing.T) {
|
||||
assert.Equal(t, []Product{{Name: "Test Product", Token: "test_product_token", CreationDate: "2020-01-01 00:00:00", LastUpdateDate: "2020-01-01 01:00:00"}}, products)
|
||||
}
|
||||
|
||||
func TestCreateProduct(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("not allowed error", func(t *testing.T) {
|
||||
// init
|
||||
myTestClient := whitesourceMockClient{
|
||||
responseBody: `{"errorCode":5001,"errorMessage":"User is not allowed to perform this action"}`,
|
||||
}
|
||||
expectedRequestBody := `{"requestType":"createProduct","userKey":"test_user_token","productName":"test_product_name","orgToken":"test_org_token"}`
|
||||
sys := System{serverURL: "https://my.test.server", httpClient: &myTestClient, orgToken: "test_org_token", userToken: "test_user_token"}
|
||||
// test
|
||||
productToken, err := sys.CreateProduct("test_product_name")
|
||||
// assert
|
||||
assert.EqualError(t, err, "WhiteSource request failed: invalid request, error code 5001, message 'User is not allowed to perform this action'")
|
||||
requestBody, err := ioutil.ReadAll(myTestClient.requestBody)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "", productToken)
|
||||
assert.Equal(t, expectedRequestBody, string(requestBody))
|
||||
})
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
// init
|
||||
myTestClient := whitesourceMockClient{
|
||||
responseBody: `{"productToken":"test_product_token"}`,
|
||||
}
|
||||
expectedRequestBody := `{"requestType":"createProduct","userKey":"test_user_token","productName":"test_product_name","orgToken":"test_org_token"}`
|
||||
sys := System{serverURL: "https://my.test.server", httpClient: &myTestClient, orgToken: "test_org_token", userToken: "test_user_token"}
|
||||
// test
|
||||
productToken, err := sys.CreateProduct("test_product_name")
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
requestBody, err := ioutil.ReadAll(myTestClient.requestBody)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "test_product_token", productToken)
|
||||
assert.Equal(t, expectedRequestBody, string(requestBody))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetMetaInfoForProduct(t *testing.T) {
|
||||
myTestClient := whitesourceMockClient{
|
||||
responseBody: `{
|
||||
|
@ -77,13 +77,13 @@ spec:
|
||||
- STEPS
|
||||
default: true
|
||||
- name: timeout
|
||||
type: string
|
||||
description: "Timeout in seconds until a HTTP call is forcefully terminated."
|
||||
type: int
|
||||
description: "Timeout in seconds until an HTTP call is forcefully terminated."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: 0
|
||||
default: 900
|
||||
- name: agentDownloadUrl
|
||||
type: string
|
||||
description: "URL used to download the latest version of the WhiteSource Unified Agent."
|
||||
@ -128,7 +128,7 @@ spec:
|
||||
default: xlsx
|
||||
- name: parallelLimit
|
||||
type: string
|
||||
description: 'Limit of parallel jobs being run at once in case of `scanType:
|
||||
description: '[NOT IMPLEMENTED] Limit of parallel jobs being run at once in case of `scanType:
|
||||
''mta''` based scenarios, defaults to `15`.'
|
||||
scope:
|
||||
- PARAMETERS
|
||||
@ -151,7 +151,7 @@ spec:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: https://saas.whitesourcesoftware.com/api
|
||||
default: "https://saas.whitesourcesoftware.com/api"
|
||||
- name: buildDescriptorExcludeList
|
||||
type: "[]string"
|
||||
description: "List of build descriptors and therefore modules to exclude from the scan and assessment activities."
|
||||
@ -175,7 +175,7 @@ spec:
|
||||
type: secret
|
||||
- name: userToken
|
||||
type: string
|
||||
description: "WhiteSource token identifying the user executing the scan"
|
||||
description: "WhiteSource token identifying the user executing the scan."
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
@ -188,7 +188,7 @@ spec:
|
||||
type: secret
|
||||
- name: licensingVulnerabilities
|
||||
type: bool
|
||||
description: "Whether license compliance is considered and reported as part of the assessment."
|
||||
description: "[NOT IMPLEMENTED] Whether license compliance is considered and reported as part of the assessment."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -201,7 +201,7 @@ spec:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: wss-unified-agent.jar
|
||||
default: "wss-unified-agent.jar"
|
||||
- name: emailAddressesOfInitialProductAdmins
|
||||
type: "[]string"
|
||||
description: "The list of email addresses to assign as product admins for newly created WhiteSource products."
|
||||
@ -223,7 +223,7 @@ spec:
|
||||
param: artifactVersion
|
||||
- name: jreDownloadUrl
|
||||
type: string
|
||||
description: "URL used for downloading the Java Runtime Environment (JRE) required to run the
|
||||
description: "[NOT IMPLEMENTED] URL used for downloading the Java Runtime Environment (JRE) required to run the
|
||||
WhiteSource Unified Agent."
|
||||
scope:
|
||||
- GENERAL
|
||||
@ -232,7 +232,10 @@ spec:
|
||||
- STEPS
|
||||
- name: productName
|
||||
type: string
|
||||
description: "Name of the WhiteSource product to be created and used for results aggregation."
|
||||
description: "Name of the WhiteSource product used for results aggregation.
|
||||
This parameter is mandatory if the parameter `createProductFromPipeline` is set to `true`
|
||||
and the WhiteSource product does not yet exist.
|
||||
It is also mandatory if the parameter `productToken` is not provided."
|
||||
mandatory: true
|
||||
scope:
|
||||
- GENERAL
|
||||
@ -243,14 +246,21 @@ spec:
|
||||
aliases:
|
||||
- name: whitesourceProjectName
|
||||
type: string
|
||||
description: "The project used for reporting results in Whitesource"
|
||||
description: "The project name used for reporting results in WhiteSource.
|
||||
When provided, all source modules will be scanned into one aggregated WhiteSource project.
|
||||
For scan types `maven`, `mta`, `npm`, the default is to generate one WhiteSource project per module,
|
||||
whereas the project name is derived from the module's build descriptor.
|
||||
For NPM modules, project aggregation is not supported, the last scanned NPM module will override all
|
||||
previously aggregated scan results!"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: projectToken
|
||||
type: string
|
||||
description: Project token to execute scan on
|
||||
description: "Project token to execute scan on. Ignored for scan types `maven`, `mta` and `npm`.
|
||||
Used for project aggregation when scanning with the Unified Agent and can be provided as an
|
||||
alternative to `projectName`."
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
@ -263,17 +273,21 @@ spec:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: WhiteSource Security Vulnerability Report
|
||||
default: "WhiteSource Security Vulnerability Report"
|
||||
- name: installCommand
|
||||
type: string
|
||||
description: "Install command that can be used to populate the default docker image for some scenarios."
|
||||
description: "[NOT IMPLEMENTED] Install command that can be used to populate the default docker image for some scenarios."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: scanType
|
||||
type: string
|
||||
description: "Type of development stack used to implement the solution."
|
||||
description: "Type of development stack used to implement the solution.
|
||||
For scan types other than `mta`, `maven`, and `npm`,
|
||||
the WhiteSource Unified Agent is downloaded and used to perform the scan.
|
||||
If the parameter is not provided, it is derived from the parameter `buildTool`,
|
||||
which is usually configured in the general section of the pipeline config file."
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
@ -308,7 +322,7 @@ spec:
|
||||
- name: productToken
|
||||
type: string
|
||||
description: "Token of the WhiteSource product to be created and used for results aggregation,
|
||||
usually determined automatically."
|
||||
usually determined automatically. Can optionally be provided as an alternative to `productName`."
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
@ -316,7 +330,7 @@ spec:
|
||||
- STEPS
|
||||
- name: agentParameters
|
||||
type: string
|
||||
description: "Additional parameters passed to the Unified Agent command line."
|
||||
description: "[NOT IMPLEMENTED] Additional parameters passed to the Unified Agent command line."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
|
Loading…
Reference in New Issue
Block a user