mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
whitesourceExecuteScan: Re-organize code between step and whitesource package (#2207)
This commit is contained in:
parent
586044192c
commit
260ca2c5a5
@ -1,13 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/maven"
|
||||
"github.com/SAP/jenkins-library/pkg/npm"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@ -41,37 +36,12 @@ type whitesource interface {
|
||||
GetProjectLibraryLocations(projectToken string) ([]ws.Library, error)
|
||||
}
|
||||
|
||||
// wsFile defines the method subset we use from os.File
|
||||
type wsFile interface {
|
||||
io.Writer
|
||||
io.StringWriter
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type whitesourceUtils interface {
|
||||
Stdout(out io.Writer)
|
||||
Stderr(err io.Writer)
|
||||
RunExecutable(executable string, params ...string) error
|
||||
|
||||
DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
|
||||
|
||||
Chdir(path string) error
|
||||
Getwd() (string, error)
|
||||
MkdirAll(path string, perm os.FileMode) error
|
||||
FileExists(path string) (bool, error)
|
||||
FileRead(path string) ([]byte, error)
|
||||
FileWrite(path string, content []byte, perm os.FileMode) error
|
||||
FileRemove(path string) error
|
||||
FileRename(oldPath, newPath string) error
|
||||
RemoveAll(path string) error
|
||||
FileOpen(name string, flag int, perm os.FileMode) (wsFile, error)
|
||||
ws.Utils
|
||||
|
||||
GetArtifactCoordinates(buildTool, buildDescriptorFile string,
|
||||
options *versioning.Options) (versioning.Coordinates, error)
|
||||
|
||||
FindPackageJSONFiles(config *ScanOptions) ([]string, error)
|
||||
InstallAllNPMDependencies(config *ScanOptions, packageJSONFiles []string) error
|
||||
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
@ -82,7 +52,7 @@ type whitesourceUtilsBundle struct {
|
||||
npmExecutor npm.Executor
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsBundle) FileOpen(name string, flag int, perm os.FileMode) (wsFile, error) {
|
||||
func (w *whitesourceUtilsBundle) FileOpen(name string, flag int, perm os.FileMode) (ws.File, error) {
|
||||
return os.OpenFile(name, flag, perm)
|
||||
}
|
||||
|
||||
@ -95,18 +65,18 @@ func (w *whitesourceUtilsBundle) GetArtifactCoordinates(buildTool, buildDescript
|
||||
return artifact.GetCoordinates()
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsBundle) getNpmExecutor(config *ScanOptions) npm.Executor {
|
||||
func (w *whitesourceUtilsBundle) getNpmExecutor(config *ws.ScanOptions) npm.Executor {
|
||||
if w.npmExecutor == nil {
|
||||
w.npmExecutor = npm.NewExecutor(npm.ExecutorOptions{DefaultNpmRegistry: config.DefaultNpmRegistry})
|
||||
}
|
||||
return w.npmExecutor
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsBundle) FindPackageJSONFiles(config *ScanOptions) ([]string, error) {
|
||||
func (w *whitesourceUtilsBundle) FindPackageJSONFiles(config *ws.ScanOptions) ([]string, error) {
|
||||
return w.getNpmExecutor(config).FindPackageJSONFilesWithExcludes(config.BuildDescriptorExcludeList)
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsBundle) InstallAllNPMDependencies(config *ScanOptions, packageJSONFiles []string) error {
|
||||
func (w *whitesourceUtilsBundle) InstallAllNPMDependencies(config *ws.ScanOptions, packageJSONFiles []string) error {
|
||||
return w.getNpmExecutor(config).InstallAllDependencies(packageJSONFiles)
|
||||
}
|
||||
|
||||
@ -126,69 +96,10 @@ func newWhitesourceUtils() *whitesourceUtilsBundle {
|
||||
return &utils
|
||||
}
|
||||
|
||||
// whitesourceScan stores information about scanned projects
|
||||
type whitesourceScan struct {
|
||||
productToken string
|
||||
aggregateProjectName string
|
||||
productVersion string
|
||||
scannedProjects map[string]ws.Project
|
||||
scanTimes map[string]time.Time
|
||||
}
|
||||
|
||||
func (s *whitesourceScan) init() {
|
||||
if s.scannedProjects == nil {
|
||||
s.scannedProjects = make(map[string]ws.Project)
|
||||
}
|
||||
if s.scanTimes == nil {
|
||||
s.scanTimes = make(map[string]time.Time)
|
||||
}
|
||||
}
|
||||
|
||||
// appendScannedProject checks that no whitesource.Project is already contained in the list of scanned projects,
|
||||
// and appends a new whitesource.Project with the given name.
|
||||
func (s *whitesourceScan) appendScannedProject(moduleName string) error {
|
||||
s.init()
|
||||
projectName := moduleName + " - " + s.productVersion
|
||||
_, exists := s.scannedProjects[projectName]
|
||||
if exists {
|
||||
log.Entry().Errorf("A module with the name '%s' was already scanned. "+
|
||||
"Your project's modules must have unique names.", moduleName)
|
||||
return fmt.Errorf("project with name '%s' was already scanned", moduleName)
|
||||
}
|
||||
s.scannedProjects[projectName] = ws.Project{Name: projectName}
|
||||
s.scanTimes[projectName] = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *whitesourceScan) updateProjects(sys whitesource) error {
|
||||
s.init()
|
||||
projects, err := sys.GetProjectsMetaInfo(s.productToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve WhiteSource projects meta info: %w", err)
|
||||
}
|
||||
|
||||
var projectsToUpdate []string
|
||||
for projectName := range s.scannedProjects {
|
||||
projectsToUpdate = append(projectsToUpdate, projectName)
|
||||
}
|
||||
|
||||
for _, project := range projects {
|
||||
_, exists := s.scannedProjects[project.Name]
|
||||
if exists {
|
||||
s.scannedProjects[project.Name] = project
|
||||
projectsToUpdate, _ = piperutils.RemoveAll(projectsToUpdate, project.Name)
|
||||
}
|
||||
}
|
||||
if len(projectsToUpdate) != 0 {
|
||||
log.Entry().Warnf("Could not fetch metadata for projects %v", projectsToUpdate)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newWhitesourceScan(config *ScanOptions) *whitesourceScan {
|
||||
return &whitesourceScan{
|
||||
aggregateProjectName: config.ProjectName,
|
||||
productVersion: config.ProductVersion,
|
||||
func newWhitesourceScan(config *ScanOptions) *ws.Scan {
|
||||
return &ws.Scan{
|
||||
AggregateProjectName: config.ProjectName,
|
||||
ProductVersion: config.ProductVersion,
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,7 +113,7 @@ func whitesourceExecuteScan(config ScanOptions, _ *telemetry.CustomData) {
|
||||
}
|
||||
}
|
||||
|
||||
func runWhitesourceExecuteScan(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils, sys whitesource) error {
|
||||
func runWhitesourceExecuteScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource) error {
|
||||
if err := resolveProjectIdentifiers(config, scan, utils, sys); err != nil {
|
||||
return fmt.Errorf("failed to resolve project identifiers: %w", err)
|
||||
}
|
||||
@ -226,7 +137,7 @@ func runWhitesourceExecuteScan(config *ScanOptions, scan *whitesourceScan, utils
|
||||
return nil
|
||||
}
|
||||
|
||||
func runWhitesourceScan(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils, sys whitesource) error {
|
||||
func runWhitesourceScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource) error {
|
||||
// Start the scan
|
||||
if err := executeScan(config, scan, utils); err != nil {
|
||||
return err
|
||||
@ -240,7 +151,7 @@ func runWhitesourceScan(config *ScanOptions, scan *whitesourceScan, utils whites
|
||||
log.Entry().Info("-----------------------------------------------------")
|
||||
log.Entry().Infof("Product Version: '%s'", config.ProductVersion)
|
||||
log.Entry().Info("Scanned projects:")
|
||||
for _, project := range scan.scannedProjects {
|
||||
for _, project := range scan.ScannedProjects() {
|
||||
log.Entry().Infof(" Name: '%s', token: %s", project.Name, project.Token)
|
||||
}
|
||||
log.Entry().Info("-----------------------------------------------------")
|
||||
@ -256,7 +167,7 @@ func runWhitesourceScan(config *ScanOptions, scan *whitesourceScan, utils whites
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkAndReportScanResults(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils, sys whitesource) error {
|
||||
func checkAndReportScanResults(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource) error {
|
||||
if !config.Reporting && !config.SecurityVulnerabilities {
|
||||
return nil
|
||||
}
|
||||
@ -264,7 +175,10 @@ func checkAndReportScanResults(config *ScanOptions, scan *whitesourceScan, utils
|
||||
return err
|
||||
}
|
||||
if config.Reporting {
|
||||
paths, err := downloadReports(config, scan, utils, sys)
|
||||
paths, err := scan.DownloadReports(ws.ReportOptions{
|
||||
ReportDirectory: config.ReportDirectoryName,
|
||||
VulnerabilityReportFormat: config.VulnerabilityReportFormat,
|
||||
}, utils, sys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -278,8 +192,8 @@ func checkAndReportScanResults(config *ScanOptions, scan *whitesourceScan, utils
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveProjectIdentifiers(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils, sys whitesource) error {
|
||||
if scan.aggregateProjectName == "" || config.ProductVersion == "" {
|
||||
func resolveProjectIdentifiers(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource) error {
|
||||
if scan.AggregateProjectName == "" || config.ProductVersion == "" {
|
||||
options := &versioning.Options{
|
||||
ProjectSettingsFile: config.ProjectSettingsFile,
|
||||
GlobalSettingsFile: config.GlobalSettingsFile,
|
||||
@ -292,9 +206,9 @@ func resolveProjectIdentifiers(config *ScanOptions, scan *whitesourceScan, utils
|
||||
|
||||
nameTmpl := `{{list .GroupID .ArtifactID | join "-" | trimAll "-"}}`
|
||||
name, version := versioning.DetermineProjectCoordinates(nameTmpl, config.VersioningModel, coordinates)
|
||||
if scan.aggregateProjectName == "" {
|
||||
if scan.AggregateProjectName == "" {
|
||||
log.Entry().Infof("Resolved project name '%s' from descriptor file", name)
|
||||
scan.aggregateProjectName = name
|
||||
scan.AggregateProjectName = name
|
||||
}
|
||||
if config.ProductVersion == "" {
|
||||
log.Entry().Infof("Resolved product version '%s' from descriptor file with versioning '%s'",
|
||||
@ -302,7 +216,7 @@ func resolveProjectIdentifiers(config *ScanOptions, scan *whitesourceScan, utils
|
||||
config.ProductVersion = version
|
||||
}
|
||||
}
|
||||
scan.productVersion = config.ProductVersion
|
||||
scan.ProductVersion = validateProductVersion(config.ProductVersion)
|
||||
|
||||
// Get product token if user did not specify one at runtime
|
||||
if config.ProductToken == "" {
|
||||
@ -314,7 +228,6 @@ func resolveProjectIdentifiers(config *ScanOptions, scan *whitesourceScan, utils
|
||||
log.Entry().Infof("Resolved product token: '%s'..", product.Token)
|
||||
config.ProductToken = product.Token
|
||||
}
|
||||
scan.productToken = config.ProductToken
|
||||
|
||||
// Get project token if user did not specify one at runtime
|
||||
if config.ProjectToken == "" && config.ProjectName != "" {
|
||||
@ -334,400 +247,81 @@ func resolveProjectIdentifiers(config *ScanOptions, scan *whitesourceScan, utils
|
||||
}
|
||||
}
|
||||
|
||||
return scan.updateProjects(sys)
|
||||
return scan.UpdateProjects(config.ProductToken, sys)
|
||||
}
|
||||
|
||||
// validateProductVersion makes sure that the version does not contain a dash "-".
|
||||
func validateProductVersion(version string) string {
|
||||
// TrimLeft() removes all "-" from the beginning, unlike TrimPrefix()!
|
||||
version = strings.TrimLeft(version, "-")
|
||||
if strings.Contains(version, "-") {
|
||||
version = strings.SplitN(version, "-", 1)[0]
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
func wsScanOptions(config *ScanOptions) *ws.ScanOptions {
|
||||
return &ws.ScanOptions{
|
||||
ScanType: config.ScanType,
|
||||
OrgToken: config.OrgToken,
|
||||
UserToken: config.UserToken,
|
||||
ProductName: config.ProductName,
|
||||
ProductToken: config.ProductToken,
|
||||
ProjectName: config.ProjectName,
|
||||
BuildDescriptorExcludeList: config.BuildDescriptorExcludeList,
|
||||
PomPath: config.BuildDescriptorFile,
|
||||
M2Path: config.M2Path,
|
||||
GlobalSettingsFile: config.GlobalSettingsFile,
|
||||
ProjectSettingsFile: config.ProjectSettingsFile,
|
||||
DefaultNpmRegistry: config.DefaultNpmRegistry,
|
||||
AgentDownloadURL: config.AgentDownloadURL,
|
||||
AgentFileName: config.AgentFileName,
|
||||
ConfigFilePath: config.ConfigFilePath,
|
||||
Includes: config.Includes,
|
||||
Excludes: config.Excludes,
|
||||
}
|
||||
}
|
||||
|
||||
// executeScan executes different types of scans depending on the scanType parameter.
|
||||
// The default is to download the Unified Agent and use it to perform the scan.
|
||||
func executeScan(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils) error {
|
||||
func executeScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils) error {
|
||||
if config.ScanType == "" {
|
||||
config.ScanType = config.BuildTool
|
||||
}
|
||||
|
||||
options := wsScanOptions(config)
|
||||
|
||||
switch config.ScanType {
|
||||
case "mta":
|
||||
// Execute scan for maven and all npm modules
|
||||
if err := executeMTAScan(config, scan, utils); err != nil {
|
||||
if err := scan.ExecuteMTAScan(options, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
case "maven":
|
||||
// Execute scan with maven plugin goal
|
||||
if err := executeMavenScan(config, scan, utils); err != nil {
|
||||
if err := scan.ExecuteMavenScan(options, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
case "npm":
|
||||
// Execute scan with in each npm module using npm.Executor
|
||||
if err := executeNpmScan(config, scan, utils); err != nil {
|
||||
if err := scan.ExecuteNpmScan(options, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
case "yarn":
|
||||
// Execute scan with whitesource yarn plugin
|
||||
if err := executeYarnScan(config, scan, utils); err != nil {
|
||||
if err := scan.ExecuteYarnScan(options, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// Execute scan with Unified Agent jar file
|
||||
if err := executeUAScan(config, scan, utils); err != nil {
|
||||
if err := scan.ExecuteUAScan(options, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeUAScan executes a scan with the Whitesource Unified Agent.
|
||||
func executeUAScan(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils) error {
|
||||
// Download the unified agent jar file if one does not exist
|
||||
if err := downloadAgent(config, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Auto generate a config file based on the working directory's contents.
|
||||
// TODO/NOTE: Currently this scans the UA jar file as a dependency since it is downloaded beforehand
|
||||
if err := autoGenerateWhitesourceConfig(config, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.RunExecutable("java", "-jar", config.AgentFileName, "-d", ".", "-c", config.ConfigFilePath,
|
||||
"-apiKey", config.OrgToken, "-userKey", config.UserToken, "-project", scan.aggregateProjectName,
|
||||
"-product", config.ProductName, "-productVersion", config.ProductVersion)
|
||||
}
|
||||
|
||||
// executeMTAScan executes a scan for the Java part with maven, and performs a scan for each NPM module.
|
||||
func executeMTAScan(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils) error {
|
||||
log.Entry().Infof("Executing Whitesource scan for MTA project")
|
||||
pomExists, _ := utils.FileExists("pom.xml")
|
||||
if pomExists {
|
||||
if err := executeMavenScanForPomFile(config, scan, utils, "pom.xml"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
modules, err := utils.FindPackageJSONFiles(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(modules) > 0 {
|
||||
if err := executeNpmScan(config, scan, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !pomExists && len(modules) == 0 {
|
||||
return fmt.Errorf("neither Maven nor NPM modules found, no scan performed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeMavenScan constructs maven parameters from the given configuration, and executes the maven goal
|
||||
// "org.whitesource:whitesource-maven-plugin:19.5.1:update".
|
||||
func executeMavenScan(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils) error {
|
||||
log.Entry().Infof("Using Whitesource scan for Maven project")
|
||||
pomPath := config.BuildDescriptorFile
|
||||
if pomPath == "" {
|
||||
pomPath = "pom.xml"
|
||||
}
|
||||
return executeMavenScanForPomFile(config, scan, utils, pomPath)
|
||||
}
|
||||
|
||||
func executeMavenScanForPomFile(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils, pomPath string) error {
|
||||
pomExists, _ := utils.FileExists(pomPath)
|
||||
if !pomExists {
|
||||
return fmt.Errorf("for scanning with type '%s', the file '%s' must exist in the project root",
|
||||
config.ScanType, pomPath)
|
||||
}
|
||||
|
||||
defines := generateMavenWhitesourceDefines(config)
|
||||
flags, excludes := generateMavenWhitesourceFlags(config, utils)
|
||||
err := appendModulesThatWillBeScanned(scan, utils, excludes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to determine maven modules which will be scanned: %w", err)
|
||||
}
|
||||
|
||||
_, err = maven.Execute(&maven.ExecuteOptions{
|
||||
PomPath: pomPath,
|
||||
M2Path: config.M2Path,
|
||||
GlobalSettingsFile: config.GlobalSettingsFile,
|
||||
ProjectSettingsFile: config.ProjectSettingsFile,
|
||||
Defines: defines,
|
||||
Flags: flags,
|
||||
Goals: []string{"org.whitesource:whitesource-maven-plugin:19.5.1:update"},
|
||||
}, utils)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func generateMavenWhitesourceDefines(config *ScanOptions) []string {
|
||||
defines := []string{
|
||||
"-Dorg.whitesource.orgToken=" + config.OrgToken,
|
||||
"-Dorg.whitesource.product=" + config.ProductName,
|
||||
"-Dorg.whitesource.checkPolicies=true",
|
||||
"-Dorg.whitesource.failOnError=true",
|
||||
}
|
||||
|
||||
// Aggregate all modules into one WhiteSource project, if user specified the 'projectName' parameter.
|
||||
if config.ProjectName != "" {
|
||||
defines = append(defines, "-Dorg.whitesource.aggregateProjectName="+config.ProjectName)
|
||||
defines = append(defines, "-Dorg.whitesource.aggregateModules=true")
|
||||
}
|
||||
|
||||
if config.UserToken != "" {
|
||||
defines = append(defines, "-Dorg.whitesource.userKey="+config.UserToken)
|
||||
}
|
||||
|
||||
if config.ProductVersion != "" {
|
||||
defines = append(defines, "-Dorg.whitesource.productVersion="+config.ProductVersion)
|
||||
}
|
||||
|
||||
return defines
|
||||
}
|
||||
|
||||
func generateMavenWhitesourceFlags(config *ScanOptions, utils whitesourceUtils) (flags []string, excludes []string) {
|
||||
excludes = config.BuildDescriptorExcludeList
|
||||
|
||||
// From the documentation, these are file paths to a module's pom.xml.
|
||||
// For MTA projects, we want to support mixing paths to package.json files and pom.xml files.
|
||||
for _, exclude := range excludes {
|
||||
if !strings.HasSuffix(exclude, "pom.xml") {
|
||||
continue
|
||||
}
|
||||
exists, _ := utils.FileExists(exclude)
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
moduleName := filepath.Dir(exclude)
|
||||
if moduleName != "" {
|
||||
flags = append(flags, "-pl", "!"+moduleName)
|
||||
}
|
||||
}
|
||||
return flags, excludes
|
||||
}
|
||||
|
||||
func appendModulesThatWillBeScanned(scan *whitesourceScan, utils whitesourceUtils, excludes []string) error {
|
||||
return maven.VisitAllMavenModules(".", utils, excludes, func(info maven.ModuleInfo) error {
|
||||
project := info.Project
|
||||
if project.Packaging != "pom" {
|
||||
if project.ArtifactID == "" {
|
||||
return fmt.Errorf("artifactId missing from '%s'", info.PomXMLPath)
|
||||
}
|
||||
|
||||
err := scan.appendScannedProject(project.ArtifactID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
const whiteSourceConfig = "whitesource.config.json"
|
||||
|
||||
func setValueAndLogChange(config map[string]interface{}, key string, value interface{}) {
|
||||
oldValue, exists := config[key]
|
||||
if exists && oldValue != value {
|
||||
log.Entry().Infof("overwriting '%s' in %s: %v -> %v", key, whiteSourceConfig, oldValue, value)
|
||||
}
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
func setValueOmitIfPresent(config map[string]interface{}, key, omitIfPresent string, value interface{}) {
|
||||
_, exists := config[omitIfPresent]
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
setValueAndLogChange(config, key, value)
|
||||
}
|
||||
|
||||
func writeWhitesourceConfigJSON(config *ScanOptions, utils whitesourceUtils, devDep, ignoreLsErrors bool) error {
|
||||
var npmConfig = make(map[string]interface{})
|
||||
|
||||
exists, _ := utils.FileExists(whiteSourceConfig)
|
||||
if exists {
|
||||
fileContents, err := utils.FileRead(whiteSourceConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("file '%s' already exists, but could not be read: %w", whiteSourceConfig, err)
|
||||
}
|
||||
err = json.Unmarshal(fileContents, &npmConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("file '%s' already exists, but could not be parsed: %w", whiteSourceConfig, err)
|
||||
}
|
||||
log.Entry().Infof("The file '%s' already exists in the project. Changed config details will be logged.",
|
||||
whiteSourceConfig)
|
||||
}
|
||||
|
||||
npmConfig["apiKey"] = config.OrgToken
|
||||
npmConfig["userKey"] = config.UserToken
|
||||
setValueAndLogChange(npmConfig, "checkPolicies", true)
|
||||
setValueAndLogChange(npmConfig, "productName", config.ProductName)
|
||||
setValueAndLogChange(npmConfig, "productVer", config.ProductVersion)
|
||||
setValueOmitIfPresent(npmConfig, "productToken", "projectToken", config.ProductToken)
|
||||
if config.ProjectName != "" {
|
||||
// In case there are other modules (i.e. maven modules in MTA projects),
|
||||
// or more than one NPM module, setting the project name will lead to
|
||||
// overwriting any previous scan results with the one from this module!
|
||||
// If this is not provided, the WhiteSource project name will be generated
|
||||
// from "name" in package.json plus " - " plus productVersion.
|
||||
setValueAndLogChange(npmConfig, "projectName", config.ProjectName)
|
||||
}
|
||||
setValueAndLogChange(npmConfig, "devDep", devDep)
|
||||
setValueAndLogChange(npmConfig, "ignoreNpmLsErrors", ignoreLsErrors)
|
||||
|
||||
jsonBuffer, err := json.Marshal(npmConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate '%s': %w", whiteSourceConfig, err)
|
||||
}
|
||||
|
||||
err = utils.FileWrite(whiteSourceConfig, jsonBuffer, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write '%s': %w", whiteSourceConfig, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeNpmScan iterates over all found npm modules and performs a scan in each one.
|
||||
func executeNpmScan(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils) error {
|
||||
modules, err := utils.FindPackageJSONFiles(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find package.json files with excludes: %w", err)
|
||||
}
|
||||
if len(modules) == 0 {
|
||||
return fmt.Errorf("found no NPM modules to scan. Configured excludes: %v",
|
||||
config.BuildDescriptorExcludeList)
|
||||
}
|
||||
for _, module := range modules {
|
||||
err := executeNpmScanForModule(module, config, scan, utils)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan NPM module '%s': %w", module, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeNpmScanForModule generates a configuration file whitesource.config.json with appropriate values from config,
|
||||
// installs all dependencies if necessary, and executes the scan via "npx whitesource run".
|
||||
func executeNpmScanForModule(modulePath string, config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils) error {
|
||||
log.Entry().Infof("Executing Whitesource scan for NPM module '%s'", modulePath)
|
||||
|
||||
resetDir, err := utils.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to obtain current directory: %w", err)
|
||||
}
|
||||
|
||||
dir := filepath.Dir(modulePath)
|
||||
if err := utils.Chdir(dir); err != nil {
|
||||
return fmt.Errorf("failed to change into directory '%s': %w", dir, err)
|
||||
}
|
||||
defer func() {
|
||||
err = utils.Chdir(resetDir)
|
||||
if err != nil {
|
||||
log.Entry().Errorf("Failed to reset into directory '%s': %v", resetDir, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := writeWhitesourceConfigJSON(config, utils, false, true); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = utils.FileRemove(whiteSourceConfig) }()
|
||||
|
||||
projectName, err := getNpmProjectName(dir, utils)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := reinstallNodeModulesIfLsFails(config, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := scan.appendScannedProject(projectName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.RunExecutable("npx", "whitesource", "run")
|
||||
}
|
||||
|
||||
func getNpmProjectName(modulePath string, utils whitesourceUtils) (string, error) {
|
||||
fileContents, err := utils.FileRead("package.json")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read package.json: %w", err)
|
||||
}
|
||||
var packageJSON = make(map[string]interface{})
|
||||
err = json.Unmarshal(fileContents, &packageJSON)
|
||||
|
||||
projectNameEntry, exists := packageJSON["name"]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("the file '%s' must configure a name",
|
||||
filepath.Join(modulePath, "package.json"))
|
||||
}
|
||||
|
||||
projectName, isString := projectNameEntry.(string)
|
||||
if !isString {
|
||||
return "", fmt.Errorf("the file '%s' must configure a name",
|
||||
filepath.Join(modulePath, "package.json"))
|
||||
}
|
||||
|
||||
return projectName, nil
|
||||
}
|
||||
|
||||
// reinstallNodeModulesIfLsFails tests running of "npm ls".
|
||||
// If that fails, the node_modules directory is cleared and the file "package-lock.json" is removed.
|
||||
// Then "npm install" is performed. Without this, the npm whitesource plugin will consistently hang,
|
||||
// when encountering npm ls errors, even with "ignoreNpmLsErrors:true" in the configuration.
|
||||
// The consequence is that what was scanned is not guaranteed to be identical to what was built & deployed.
|
||||
// This hack/work-around that should be removed once scanning it consistently performed using the Unified Agent.
|
||||
// A possible reason for encountering "npm ls" errors in the first place is that a different node version
|
||||
// is used for whitesourceExecuteScan due to a different docker image being used compared to the build stage.
|
||||
func reinstallNodeModulesIfLsFails(config *ScanOptions, utils whitesourceUtils) error {
|
||||
// No need to have output from "npm ls" in the log
|
||||
utils.Stdout(ioutil.Discard)
|
||||
defer utils.Stdout(log.Writer())
|
||||
|
||||
err := utils.RunExecutable("npm", "ls")
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
log.Entry().Warnf("'npm ls' failed. Re-installing NPM Node Modules")
|
||||
err = utils.RemoveAll("node_modules")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove node_modules directory: %w", err)
|
||||
}
|
||||
err = utils.MkdirAll("node_modules", os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to recreate node_modules directory: %w", err)
|
||||
}
|
||||
exists, _ := utils.FileExists("package-lock.json")
|
||||
if exists {
|
||||
err = utils.FileRemove("package-lock.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove package-lock.json: %w", err)
|
||||
}
|
||||
}
|
||||
// Passing only "package.json", because we are already inside the module's directory.
|
||||
return utils.InstallAllNPMDependencies(config, []string{"package.json"})
|
||||
}
|
||||
|
||||
// executeYarnScan generates a configuration file whitesource.config.json with appropriate values from config,
|
||||
// installs whitesource yarn plugin and executes the scan.
|
||||
func executeYarnScan(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils) error {
|
||||
// To stay compatible with what the step was doing before, trigger aggregation, although
|
||||
// there is a great chance that it doesn't work with yarn the same way it doesn't with npm.
|
||||
// Maybe the yarn code-path should be removed, and only npm stays.
|
||||
config.ProjectName = scan.aggregateProjectName
|
||||
if err := writeWhitesourceConfigJSON(config, utils, true, false); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = utils.FileRemove(whiteSourceConfig) }()
|
||||
if err := utils.RunExecutable("yarn", "global", "add", "whitesource"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := utils.RunExecutable("yarn", "install"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := utils.RunExecutable("whitesource", "yarn"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkSecurityViolations(config *ScanOptions, scan *whitesourceScan, sys whitesource) error {
|
||||
func checkSecurityViolations(config *ScanOptions, scan *ws.Scan, sys whitesource) error {
|
||||
// Check for security vulnerabilities and fail the build if cvssSeverityLimit threshold is crossed
|
||||
// convert config.CvssSeverityLimit to float64
|
||||
cvssSeverityLimit, err := strconv.ParseFloat(config.CvssSeverityLimit, 64)
|
||||
@ -742,7 +336,7 @@ func checkSecurityViolations(config *ScanOptions, scan *whitesourceScan, sys whi
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
for _, project := range scan.scannedProjects {
|
||||
for _, project := range scan.ScannedProjects() {
|
||||
if err := checkProjectSecurityViolations(cvssSeverityLimit, project, sys); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -793,7 +387,7 @@ func checkProjectSecurityViolations(cvssSeverityLimit float64, project ws.Projec
|
||||
return nil
|
||||
}
|
||||
|
||||
func blockUntilReportsAreaReady(config *ScanOptions, scan *whitesourceScan, sys whitesource) error {
|
||||
func blockUntilReportsAreaReady(config *ScanOptions, scan *ws.Scan, sys whitesource) error {
|
||||
// Project was scanned. We need to wait for WhiteSource backend to propagate the changes
|
||||
// before downloading any reports or check security vulnerabilities.
|
||||
if config.ProjectToken != "" {
|
||||
@ -803,8 +397,8 @@ func blockUntilReportsAreaReady(config *ScanOptions, scan *whitesourceScan, sys
|
||||
}
|
||||
} else {
|
||||
// Poll status of all scanned projects
|
||||
for key, project := range scan.scannedProjects {
|
||||
if err := pollProjectStatus(project.Token, scan.scanTimes[key], sys); err != nil {
|
||||
for _, project := range scan.ScannedProjects() {
|
||||
if err := pollProjectStatus(project.Token, scan.ScanTime(project.Name), sys); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -854,140 +448,6 @@ func blockUntilProjectIsUpdated(projectToken string, sys whitesource, currentTim
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadReports downloads a project's risk and vulnerability reports
|
||||
func downloadReports(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils, sys whitesource) ([]piperutils.Path, error) {
|
||||
if err := utils.MkdirAll(config.ReportDirectoryName, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var paths []piperutils.Path
|
||||
if config.ProjectName != "" {
|
||||
aggregateProject := ws.Project{Token: config.ProjectToken, Name: config.ProjectName}
|
||||
vulnPath, err := downloadVulnerabilityReport(config, aggregateProject, utils, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
riskPath, err := downloadRiskReport(config, aggregateProject, utils, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, *vulnPath, *riskPath)
|
||||
} else {
|
||||
for _, project := range scan.scannedProjects {
|
||||
vulnPath, err := downloadVulnerabilityReport(config, project, utils, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
riskPath, err := downloadRiskReport(config, project, utils, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, *vulnPath, *riskPath)
|
||||
}
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func downloadVulnerabilityReport(config *ScanOptions, project ws.Project, utils whitesourceUtils, sys whitesource) (*piperutils.Path, error) {
|
||||
reportBytes, err := sys.GetProjectVulnerabilityReport(project.Token, config.VulnerabilityReportFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write report to file
|
||||
rptFileName := fmt.Sprintf("%s-vulnerability-report.%s", project.Name, config.VulnerabilityReportFormat)
|
||||
rptFileName = filepath.Join(config.ReportDirectoryName, rptFileName)
|
||||
if err := utils.FileWrite(rptFileName, reportBytes, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Entry().Infof("Successfully downloaded vulnerability report to %s", rptFileName)
|
||||
pathName := fmt.Sprintf("%s Vulnerability Report", project.Name)
|
||||
return &piperutils.Path{Name: pathName, Target: rptFileName}, nil
|
||||
}
|
||||
|
||||
func downloadRiskReport(config *ScanOptions, project ws.Project, utils whitesourceUtils, sys whitesource) (*piperutils.Path, error) {
|
||||
reportBytes, err := sys.GetProjectRiskReport(project.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rptFileName := fmt.Sprintf("%s-risk-report.pdf", project.Name)
|
||||
rptFileName = filepath.Join(config.ReportDirectoryName, rptFileName)
|
||||
if err := utils.FileWrite(rptFileName, reportBytes, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Entry().Infof("Successfully downloaded risk report to %s", rptFileName)
|
||||
pathName := fmt.Sprintf("%s PDF Risk Report", project.Name)
|
||||
return &piperutils.Path{Name: pathName, Target: rptFileName}, nil
|
||||
}
|
||||
|
||||
// downloadAgent downloads the unified agent jar file if one does not exist
|
||||
func downloadAgent(config *ScanOptions, utils whitesourceUtils) error {
|
||||
agentFile := config.AgentFileName
|
||||
exists, err := utils.FileExists(agentFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not check whether the file '%s' exists: %w", agentFile, err)
|
||||
}
|
||||
if !exists {
|
||||
err := utils.DownloadFile(config.AgentDownloadURL, agentFile, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download unified agent from URL '%s' to file '%s': %w",
|
||||
config.AgentDownloadURL, agentFile, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// autoGenerateWhitesourceConfig
|
||||
// Auto generate a config file based on the current directory structure, renames it to user specified configFilePath
|
||||
// Generated file name will be 'wss-generated-file.config'
|
||||
func autoGenerateWhitesourceConfig(config *ScanOptions, utils whitesourceUtils) error {
|
||||
// TODO: Should we rely on -detect, or set the parameters manually?
|
||||
if err := utils.RunExecutable("java", "-jar", config.AgentFileName, "-d", ".", "-detect"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename generated config file to config.ConfigFilePath parameter
|
||||
if err := utils.FileRename("wss-generated-file.config", config.ConfigFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Append aggregateModules=true parameter to config file (consolidates multi-module projects into one)
|
||||
f, err := utils.FileOpen(config.ConfigFilePath, os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
// Append additional config parameters to prevent multiple projects being generated
|
||||
m2Path := config.M2Path
|
||||
if m2Path == "" {
|
||||
m2Path = ".m2"
|
||||
}
|
||||
cfg := fmt.Sprintf("\ngradle.aggregateModules=true\nmaven.aggregateModules=true\ngradle.localRepositoryPath=.gradle\nmaven.m2RepositoryPath=%s\nexcludes=%s",
|
||||
m2Path,
|
||||
config.Excludes)
|
||||
if _, err = f.WriteString(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// archiveExtractionDepth=0
|
||||
if err := utils.RunExecutable("sed", "-ir", `s/^[#]*\s*archiveExtractionDepth=.*/archiveExtractionDepth=0/`,
|
||||
config.ConfigFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// config.Includes defaults to "**/*.java **/*.jar **/*.py **/*.go **/*.js **/*.ts"
|
||||
regex := fmt.Sprintf(`s/^[#]*\s*includes=.*/includes="%s"/`, config.Includes)
|
||||
if err := utils.RunExecutable("sed", "-ir", regex, config.ConfigFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func aggregateVersionWideLibraries(config *ScanOptions, utils whitesourceUtils, sys whitesource) error {
|
||||
log.Entry().Infof("Aggregating list of libraries used for all projects with version: %s", config.ProductVersion)
|
||||
|
||||
@ -1144,13 +604,13 @@ func newLibraryCSVReport(libraries map[string][]ws.Library, config *ScanOptions,
|
||||
|
||||
// persistScannedProjects writes all actually scanned WhiteSource project names as comma separated
|
||||
// string into the Common Pipeline Environment, from where it can be used by sub-sequent steps.
|
||||
func persistScannedProjects(config *ScanOptions, scan *whitesourceScan, utils whitesourceUtils) error {
|
||||
func persistScannedProjects(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils) error {
|
||||
var projectNames []string
|
||||
if config.ProjectName != "" {
|
||||
projectNames = []string{config.ProjectName + " - " + config.ProductVersion}
|
||||
} else {
|
||||
for projectName := range scan.scannedProjects {
|
||||
projectNames = append(projectNames, projectName)
|
||||
for _, project := range scan.ScannedProjects() {
|
||||
projectNames = append(projectNames, project.Name)
|
||||
}
|
||||
// Sorting helps the list become stable across pipeline runs (and in the unit tests),
|
||||
// as the order in which we travers map keys is not deterministic.
|
||||
|
@ -1,164 +1,27 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"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"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type whitesourceSystemMock struct {
|
||||
productName string
|
||||
products []ws.Product
|
||||
projects []ws.Project
|
||||
alerts []ws.Alert
|
||||
libraries []ws.Library
|
||||
riskReport []byte
|
||||
vulnerabilityReport []byte
|
||||
}
|
||||
|
||||
func (m *whitesourceSystemMock) GetProductByName(productName string) (ws.Product, error) {
|
||||
for _, product := range m.products {
|
||||
if product.Name == productName {
|
||||
return product, nil
|
||||
}
|
||||
}
|
||||
return ws.Product{}, fmt.Errorf("no product with name '%s' found in Whitesource", productName)
|
||||
}
|
||||
|
||||
func (m *whitesourceSystemMock) GetProjectsMetaInfo(productToken string) ([]ws.Project, error) {
|
||||
return m.projects, nil
|
||||
}
|
||||
|
||||
func (m *whitesourceSystemMock) GetProjectToken(productToken, projectName string) (string, error) {
|
||||
return "mock-project-token", nil
|
||||
}
|
||||
|
||||
func (m *whitesourceSystemMock) GetProjectByToken(projectToken string) (ws.Project, error) {
|
||||
for _, project := range m.projects {
|
||||
if project.Token == projectToken {
|
||||
return project, nil
|
||||
}
|
||||
}
|
||||
return ws.Project{}, fmt.Errorf("no project with token '%s' found in Whitesource", projectToken)
|
||||
}
|
||||
|
||||
func (m *whitesourceSystemMock) GetProjectRiskReport(projectToken string) ([]byte, error) {
|
||||
return m.riskReport, nil
|
||||
}
|
||||
|
||||
func (m *whitesourceSystemMock) GetProjectVulnerabilityReport(projectToken string, format string) ([]byte, error) {
|
||||
_, err := m.GetProjectByToken(projectToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if m.vulnerabilityReport == nil {
|
||||
return nil, fmt.Errorf("no report available")
|
||||
}
|
||||
return m.vulnerabilityReport, nil
|
||||
}
|
||||
|
||||
func (m *whitesourceSystemMock) GetProjectAlerts(projectToken string) ([]ws.Alert, error) {
|
||||
return m.alerts, nil
|
||||
}
|
||||
|
||||
func (m *whitesourceSystemMock) GetProjectLibraryLocations(projectToken string) ([]ws.Library, error) {
|
||||
return m.libraries, nil
|
||||
}
|
||||
|
||||
var mockLibrary = ws.Library{
|
||||
Name: "mock-library",
|
||||
Filename: "mock-library-file",
|
||||
Version: "mock-library-version",
|
||||
Project: "mock-project - 1",
|
||||
}
|
||||
|
||||
func newWhitesourceSystemMock(lastUpdateDate string) *whitesourceSystemMock {
|
||||
return &whitesourceSystemMock{
|
||||
productName: "mock-product",
|
||||
products: []ws.Product{
|
||||
{
|
||||
Name: "mock-product",
|
||||
Token: "mock-product-token",
|
||||
CreationDate: "last-thursday",
|
||||
LastUpdateDate: lastUpdateDate,
|
||||
},
|
||||
},
|
||||
projects: []ws.Project{
|
||||
{
|
||||
ID: 42,
|
||||
Name: "mock-project - 1",
|
||||
PluginName: "mock-plugin-name",
|
||||
Token: "mock-project-token",
|
||||
UploadedBy: "MrBean",
|
||||
CreationDate: "last-thursday",
|
||||
LastUpdateDate: lastUpdateDate,
|
||||
},
|
||||
},
|
||||
alerts: []ws.Alert{
|
||||
{
|
||||
Vulnerability: ws.Vulnerability{
|
||||
Name: "something severe",
|
||||
Score: 5,
|
||||
},
|
||||
Library: mockLibrary,
|
||||
Project: "mock-project - 1",
|
||||
CreationDate: "last-thursday",
|
||||
},
|
||||
},
|
||||
libraries: []ws.Library{mockLibrary},
|
||||
riskReport: []byte("mock-risk-report"),
|
||||
vulnerabilityReport: []byte("mock-vulnerability-report"),
|
||||
}
|
||||
}
|
||||
|
||||
type whitesourceCoordinatesMock struct {
|
||||
GroupID string
|
||||
ArtifactID string
|
||||
Version string
|
||||
}
|
||||
|
||||
type downloadedFile struct {
|
||||
sourceURL string
|
||||
filePath string
|
||||
}
|
||||
|
||||
type npmInstall struct {
|
||||
currentDir string
|
||||
packageJSON []string
|
||||
}
|
||||
|
||||
type whitesourceUtilsMock struct {
|
||||
*mock.FilesMock
|
||||
*mock.ExecMockRunner
|
||||
*ws.ScanUtilsMock
|
||||
coordinates whitesourceCoordinatesMock
|
||||
usedBuildTool string
|
||||
usedBuildDescriptorFile string
|
||||
usedOptions versioning.Options
|
||||
downloadedFiles []downloadedFile
|
||||
npmInstalledModules []npmInstall
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsMock) DownloadFile(url, filename string, _ http.Header, _ []*http.Cookie) error {
|
||||
w.downloadedFiles = append(w.downloadedFiles, downloadedFile{sourceURL: url, filePath: filename})
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
// TODO: Implement in FS Mock
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsMock) GetArtifactCoordinates(buildTool, buildDescriptorFile string,
|
||||
@ -169,19 +32,6 @@ func (w *whitesourceUtilsMock) GetArtifactCoordinates(buildTool, buildDescriptor
|
||||
return w.coordinates, nil
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsMock) FindPackageJSONFiles(_ *ScanOptions) ([]string, error) {
|
||||
matches, _ := w.Glob("**/package.json")
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func (w *whitesourceUtilsMock) InstallAllNPMDependencies(_ *ScanOptions, packageJSONs []string) error {
|
||||
w.npmInstalledModules = append(w.npmInstalledModules, npmInstall{
|
||||
currentDir: w.CurrentDir,
|
||||
packageJSON: packageJSONs,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
const wsTimeNow = "2010-05-10 00:15:42"
|
||||
|
||||
func (w *whitesourceUtilsMock) Now() time.Time {
|
||||
@ -191,8 +41,10 @@ func (w *whitesourceUtilsMock) Now() time.Time {
|
||||
|
||||
func newWhitesourceUtilsMock() *whitesourceUtilsMock {
|
||||
return &whitesourceUtilsMock{
|
||||
FilesMock: &mock.FilesMock{},
|
||||
ExecMockRunner: &mock.ExecMockRunner{},
|
||||
ScanUtilsMock: &ws.ScanUtilsMock{
|
||||
FilesMock: &mock.FilesMock{},
|
||||
ExecMockRunner: &mock.ExecMockRunner{},
|
||||
},
|
||||
coordinates: whitesourceCoordinatesMock{
|
||||
GroupID: "mock-group-id",
|
||||
ArtifactID: "mock-artifact-id",
|
||||
@ -215,13 +67,13 @@ func TestResolveProjectIdentifiers(t *testing.T) {
|
||||
GlobalSettingsFile: "global-settings.xml",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
systemMock := newWhitesourceSystemMock("ignored")
|
||||
systemMock := ws.NewSystemMock("ignored")
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock)
|
||||
// assert
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, "mock-group-id-mock-artifact-id", scan.aggregateProjectName)
|
||||
assert.Equal(t, "mock-group-id-mock-artifact-id", scan.AggregateProjectName)
|
||||
assert.Equal(t, "1", config.ProductVersion)
|
||||
assert.Equal(t, "mock-product-token", config.ProductToken)
|
||||
assert.Equal(t, "mta", utilsMock.usedBuildTool)
|
||||
@ -238,16 +90,16 @@ func TestResolveProjectIdentifiers(t *testing.T) {
|
||||
BuildDescriptorFile: "my-mta.yml",
|
||||
VersioningModel: "major",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project - 1",
|
||||
ProjectName: "mock-project",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
systemMock := newWhitesourceSystemMock("ignored")
|
||||
systemMock := ws.NewSystemMock("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, "mock-project", scan.AggregateProjectName)
|
||||
assert.Equal(t, "1", config.ProductVersion)
|
||||
assert.Equal(t, "mock-product-token", config.ProductToken)
|
||||
assert.Equal(t, "mta", utilsMock.usedBuildTool)
|
||||
@ -263,7 +115,7 @@ func TestResolveProjectIdentifiers(t *testing.T) {
|
||||
ProductName: "does-not-exist",
|
||||
}
|
||||
utilsMock := newWhitesourceUtilsMock()
|
||||
systemMock := newWhitesourceSystemMock("ignored")
|
||||
systemMock := ws.NewSystemMock("ignored")
|
||||
scan := newWhitesourceScan(&config)
|
||||
// test
|
||||
err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock)
|
||||
@ -272,451 +124,6 @@ func TestResolveProjectIdentifiers(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
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' 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)
|
||||
expectedNpmInstalls := []npmInstall{
|
||||
{currentDir: "app", packageJSON: []string{"package.json"}},
|
||||
{currentDir: "", packageJSON: []string{"package.json"}},
|
||||
}
|
||||
assert.Equal(t, expectedNpmInstalls, 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) {
|
||||
@ -727,9 +134,9 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
lastUpdatedDate := "2010-05-30 00:15:01 +0100"
|
||||
systemMock := newWhitesourceSystemMock(lastUpdatedDate)
|
||||
systemMock := ws.NewSystemMock(lastUpdatedDate)
|
||||
// test
|
||||
err = blockUntilProjectIsUpdated(systemMock.projects[0].Token, 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)
|
||||
})
|
||||
@ -741,9 +148,9 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
lastUpdatedDate := "2010-05-30 00:07:00 +0100"
|
||||
systemMock := newWhitesourceSystemMock(lastUpdatedDate)
|
||||
systemMock := ws.NewSystemMock(lastUpdatedDate)
|
||||
// test
|
||||
err = blockUntilProjectIsUpdated(systemMock.projects[0].Token, 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")
|
||||
@ -756,9 +163,9 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
systemMock := newWhitesourceSystemMock("")
|
||||
systemMock := ws.NewSystemMock("")
|
||||
// test
|
||||
err = blockUntilProjectIsUpdated(systemMock.projects[0].Token, 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")
|
||||
@ -766,176 +173,7 @@ func TestBlockUntilProjectIsUpdated(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDownloadReports(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
// init
|
||||
config := &ScanOptions{
|
||||
ProjectToken: "mock-project-token",
|
||||
ProjectName: "mock-project",
|
||||
ReportDirectoryName: "report-dir",
|
||||
VulnerabilityReportFormat: "txt",
|
||||
}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock("2010-05-30 00:15:00 +0100")
|
||||
scan := newWhitesourceScan(config)
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
t.Run("invalid project token", func(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
|
||||
paths, err := downloadReports(config, scan, utils, system)
|
||||
// assert
|
||||
assert.EqualError(t, err, "no project with token '<invalid>' found in Whitesource")
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWriteWhitesourceConfigJSON(t *testing.T) {
|
||||
config := &ScanOptions{
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project",
|
||||
ProductToken: "mock-product-token",
|
||||
ProductVersion: "42",
|
||||
}
|
||||
|
||||
expected := make(map[string]interface{})
|
||||
expected["apiKey"] = "org-token"
|
||||
expected["userKey"] = "user-token"
|
||||
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
|
||||
|
||||
t.Parallel()
|
||||
|
||||
t.Run("write config from scratch", func(t *testing.T) {
|
||||
// init
|
||||
utils := newWhitesourceUtilsMock()
|
||||
// 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)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("extend and merge config", func(t *testing.T) {
|
||||
// init
|
||||
initial := make(map[string]interface{})
|
||||
initial["checkPolicies"] = false
|
||||
initial["productName"] = "mock-product"
|
||||
initial["productVer"] = "41"
|
||||
initial["unknown"] = "preserved"
|
||||
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"
|
||||
|
||||
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) {
|
||||
func TestPersistScannedProjects(t *testing.T) {
|
||||
resource := filepath.Join(".pipeline", "commonPipelineEnvironment", "custom", "whitesourceProjectNames")
|
||||
|
||||
t.Parallel()
|
||||
@ -944,7 +182,7 @@ func TestPersisScannedProjects(t *testing.T) {
|
||||
config := &ScanOptions{ProductVersion: "1"}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
scan := newWhitesourceScan(config)
|
||||
_ = scan.appendScannedProject("project")
|
||||
_ = scan.AppendScannedProject("project")
|
||||
// test
|
||||
err := persistScannedProjects(config, scan, utils)
|
||||
// assert
|
||||
@ -958,8 +196,8 @@ func TestPersisScannedProjects(t *testing.T) {
|
||||
config := &ScanOptions{ProductVersion: "1"}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
scan := newWhitesourceScan(config)
|
||||
_ = scan.appendScannedProject("project-app")
|
||||
_ = scan.appendScannedProject("project-db")
|
||||
_ = scan.AppendScannedProject("project-app")
|
||||
_ = scan.AppendScannedProject("project-db")
|
||||
// test
|
||||
err := persistScannedProjects(config, scan, utils)
|
||||
// assert
|
||||
@ -1006,7 +244,7 @@ func TestAggregateVersionWideLibraries(t *testing.T) {
|
||||
ReportDirectoryName: "mock-reports",
|
||||
}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock("2010-05-30 00:15:00 +0100")
|
||||
system := ws.NewSystemMock("2010-05-30 00:15:00 +0100")
|
||||
// test
|
||||
err := aggregateVersionWideLibraries(config, utils, system)
|
||||
// assert
|
||||
@ -1029,7 +267,7 @@ func TestAggregateVersionWideVulnerabilities(t *testing.T) {
|
||||
ReportDirectoryName: "mock-reports",
|
||||
}
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock("2010-05-30 00:15:00 +0100")
|
||||
system := ws.NewSystemMock("2010-05-30 00:15:00 +0100")
|
||||
// test
|
||||
err := aggregateVersionWideVulnerabilities(config, utils, system)
|
||||
// assert
|
||||
@ -1059,7 +297,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
system := ws.NewSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
@ -1077,7 +315,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
system := ws.NewSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
@ -1095,7 +333,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
system := ws.NewSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
@ -1114,7 +352,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
|
||||
}
|
||||
scan := newWhitesourceScan(config)
|
||||
utils := newWhitesourceUtilsMock()
|
||||
system := newWhitesourceSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
system := ws.NewSystemMock(time.Now().Format(whitesourceDateTimeLayout))
|
||||
// test
|
||||
err := checkAndReportScanResults(config, scan, utils, system)
|
||||
// assert
|
||||
|
110
pkg/whitesource/scan.go
Normal file
110
pkg/whitesource/scan.go
Normal file
@ -0,0 +1,110 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Scan stores information about scanned WhiteSource projects (modules).
|
||||
type Scan struct {
|
||||
// AggregateProjectName stores the name of the WhiteSource project where scans shall be aggregated.
|
||||
// It does not include the ProductVersion.
|
||||
AggregateProjectName string
|
||||
// ProductVersion is the global version that is used across all Projects (modules) during the scan.
|
||||
ProductVersion string
|
||||
scannedProjects map[string]Project
|
||||
scanTimes map[string]time.Time
|
||||
}
|
||||
|
||||
func (s *Scan) init() {
|
||||
if s.scannedProjects == nil {
|
||||
s.scannedProjects = make(map[string]Project)
|
||||
}
|
||||
if s.scanTimes == nil {
|
||||
s.scanTimes = make(map[string]time.Time)
|
||||
}
|
||||
}
|
||||
|
||||
// AppendScannedProject checks that no Project with the same name is already contained in the list of scanned projects,
|
||||
// and appends a new Project with the given name. The global product version is appended to the name.
|
||||
func (s *Scan) AppendScannedProject(projectName string) error {
|
||||
return s.AppendScannedProjectVersion(projectName + " - " + s.ProductVersion)
|
||||
}
|
||||
|
||||
// AppendScannedProjectVersion checks that no Project with the same name is already contained in the list of scanned
|
||||
// projects, and appends a new Project with the given name (which is expected to include the product version).
|
||||
func (s *Scan) AppendScannedProjectVersion(projectName string) error {
|
||||
if !strings.HasSuffix(projectName, " - "+s.ProductVersion) {
|
||||
return fmt.Errorf("projectName is expected to include the product version")
|
||||
}
|
||||
s.init()
|
||||
_, exists := s.scannedProjects[projectName]
|
||||
if exists {
|
||||
log.Entry().Errorf("A module with the name '%s' was already scanned. "+
|
||||
"Your project's modules must have unique names.", projectName)
|
||||
return fmt.Errorf("project with name '%s' was already scanned", projectName)
|
||||
}
|
||||
s.scannedProjects[projectName] = Project{Name: projectName}
|
||||
s.scanTimes[projectName] = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProjectByName returns a WhiteSource Project previously established via AppendScannedProject().
|
||||
func (s *Scan) ProjectByName(projectName string) (Project, bool) {
|
||||
project, exists := s.scannedProjects[projectName]
|
||||
return project, exists
|
||||
}
|
||||
|
||||
// ScannedProjects returns the WhiteSource projects that have been added via AppendScannedProject() as a slice.
|
||||
func (s *Scan) ScannedProjects() []Project {
|
||||
var projects []Project
|
||||
for _, project := range s.scannedProjects {
|
||||
projects = append(projects, project)
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
// ScanTime returns the time at which the respective WhiteSource Project was scanned, or the the
|
||||
// zero value of time.Time, if AppendScannedProject() was not called with that name.
|
||||
func (s *Scan) ScanTime(projectName string) time.Time {
|
||||
if s.scanTimes == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return s.scanTimes[projectName]
|
||||
}
|
||||
|
||||
type whitesource interface {
|
||||
GetProjectsMetaInfo(productToken string) ([]Project, error)
|
||||
GetProjectRiskReport(projectToken string) ([]byte, error)
|
||||
GetProjectVulnerabilityReport(projectToken string, format string) ([]byte, error)
|
||||
}
|
||||
|
||||
// UpdateProjects pulls the current backend metadata for all WhiteSource projects in the product with
|
||||
// the given productToken, and updates all scanned projects with the obtained information.
|
||||
func (s *Scan) UpdateProjects(productToken string, sys whitesource) error {
|
||||
s.init()
|
||||
projects, err := sys.GetProjectsMetaInfo(productToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve WhiteSource projects meta info: %w", err)
|
||||
}
|
||||
|
||||
var projectsToUpdate []string
|
||||
for projectName := range s.scannedProjects {
|
||||
projectsToUpdate = append(projectsToUpdate, projectName)
|
||||
}
|
||||
|
||||
for _, project := range projects {
|
||||
_, exists := s.scannedProjects[project.Name]
|
||||
if exists {
|
||||
s.scannedProjects[project.Name] = project
|
||||
projectsToUpdate, _ = piperutils.RemoveAll(projectsToUpdate, project.Name)
|
||||
}
|
||||
}
|
||||
if len(projectsToUpdate) != 0 {
|
||||
log.Entry().Warnf("Could not fetch metadata for projects %v", projectsToUpdate)
|
||||
}
|
||||
return nil
|
||||
}
|
32
pkg/whitesource/scanMTA.go
Normal file
32
pkg/whitesource/scanMTA.go
Normal file
@ -0,0 +1,32 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
)
|
||||
|
||||
// ExecuteMTAScan executes a scan for the Java part with maven, and performs a scan for each NPM module.
|
||||
func (s *Scan) ExecuteMTAScan(config *ScanOptions, utils Utils) error {
|
||||
log.Entry().Infof("Executing Whitesource scan for MTA project")
|
||||
pomExists, _ := utils.FileExists("pom.xml")
|
||||
if pomExists {
|
||||
if err := s.ExecuteMavenScanForPomFile(config, utils, "pom.xml"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
modules, err := utils.FindPackageJSONFiles(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(modules) > 0 {
|
||||
if err := s.ExecuteNpmScan(config, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !pomExists && len(modules) == 0 {
|
||||
return fmt.Errorf("neither Maven nor NPM modules found, no scan performed")
|
||||
}
|
||||
return nil
|
||||
}
|
148
pkg/whitesource/scanMTA_test.go
Normal file
148
pkg/whitesource/scanMTA_test.go
Normal file
@ -0,0 +1,148 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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{
|
||||
ScanType: "mta",
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project",
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
t.Run("happy path MTA", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := NewScanUtilsMock()
|
||||
utilsMock.AddFile("pom.xml", []byte(pomXML))
|
||||
utilsMock.AddFile("package.json", []byte(`{"name":"my-module-name"}`))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteMTAScan(&config, 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 := NewScanUtilsMock()
|
||||
utilsMock.AddFile("pom.xml", []byte(pomXML))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteMTAScan(&config, 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 := NewScanUtilsMock()
|
||||
utilsMock.AddFile("package.json", []byte(`{"name":"my-module-name"}`))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteMTAScan(&config, 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 := NewScanUtilsMock()
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteMTAScan(&config, utilsMock)
|
||||
// assert
|
||||
assert.EqualError(t, err, "neither Maven nor NPM modules found, no scan performed")
|
||||
})
|
||||
}
|
111
pkg/whitesource/scanMaven.go
Normal file
111
pkg/whitesource/scanMaven.go
Normal file
@ -0,0 +1,111 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/maven"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ExecuteMavenScan constructs maven parameters from the given configuration, and executes the maven goal
|
||||
// "org.whitesource:whitesource-maven-plugin:19.5.1:update".
|
||||
func (s *Scan) ExecuteMavenScan(config *ScanOptions, utils Utils) error {
|
||||
log.Entry().Infof("Using Whitesource scan for Maven project")
|
||||
pomPath := config.PomPath
|
||||
if pomPath == "" {
|
||||
pomPath = "pom.xml"
|
||||
}
|
||||
return s.ExecuteMavenScanForPomFile(config, utils, pomPath)
|
||||
}
|
||||
|
||||
// ExecuteMavenScanForPomFile constructs maven parameters from the given configuration, and executes the maven goal
|
||||
// "org.whitesource:whitesource-maven-plugin:19.5.1:update" for the given pom file.
|
||||
func (s *Scan) ExecuteMavenScanForPomFile(config *ScanOptions, utils Utils, pomPath string) error {
|
||||
pomExists, _ := utils.FileExists(pomPath)
|
||||
if !pomExists {
|
||||
return fmt.Errorf("for scanning with type '%s', the file '%s' must exist in the project root",
|
||||
config.ScanType, pomPath)
|
||||
}
|
||||
|
||||
defines := s.generateMavenWhitesourceDefines(config)
|
||||
flags, excludes := generateMavenWhitesourceFlags(config, utils)
|
||||
err := s.appendModulesThatWillBeScanned(utils, excludes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to determine maven modules which will be scanned: %w", err)
|
||||
}
|
||||
|
||||
_, err = maven.Execute(&maven.ExecuteOptions{
|
||||
PomPath: pomPath,
|
||||
M2Path: config.M2Path,
|
||||
GlobalSettingsFile: config.GlobalSettingsFile,
|
||||
ProjectSettingsFile: config.ProjectSettingsFile,
|
||||
Defines: defines,
|
||||
Flags: flags,
|
||||
Goals: []string{"org.whitesource:whitesource-maven-plugin:19.5.1:update"},
|
||||
}, utils)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Scan) generateMavenWhitesourceDefines(config *ScanOptions) []string {
|
||||
defines := []string{
|
||||
"-Dorg.whitesource.orgToken=" + config.OrgToken,
|
||||
"-Dorg.whitesource.product=" + config.ProductName,
|
||||
"-Dorg.whitesource.checkPolicies=true",
|
||||
"-Dorg.whitesource.failOnError=true",
|
||||
}
|
||||
|
||||
// Aggregate all modules into one WhiteSource project, if user specified the 'projectName' parameter.
|
||||
if config.ProjectName != "" {
|
||||
defines = append(defines, "-Dorg.whitesource.aggregateProjectName="+config.ProjectName)
|
||||
defines = append(defines, "-Dorg.whitesource.aggregateModules=true")
|
||||
}
|
||||
|
||||
if config.UserToken != "" {
|
||||
defines = append(defines, "-Dorg.whitesource.userKey="+config.UserToken)
|
||||
}
|
||||
|
||||
if s.ProductVersion != "" {
|
||||
defines = append(defines, "-Dorg.whitesource.productVersion="+s.ProductVersion)
|
||||
}
|
||||
|
||||
return defines
|
||||
}
|
||||
|
||||
func generateMavenWhitesourceFlags(config *ScanOptions, utils Utils) (flags []string, excludes []string) {
|
||||
excludes = config.BuildDescriptorExcludeList
|
||||
// From the documentation, these are file paths to a module's pom.xml.
|
||||
// For MTA projects, we want to support mixing paths to package.json files and pom.xml files.
|
||||
for _, exclude := range excludes {
|
||||
if !strings.HasSuffix(exclude, "pom.xml") {
|
||||
continue
|
||||
}
|
||||
exists, _ := utils.FileExists(exclude)
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
moduleName := filepath.Dir(exclude)
|
||||
if moduleName != "" {
|
||||
flags = append(flags, "-pl", "!"+moduleName)
|
||||
}
|
||||
}
|
||||
return flags, excludes
|
||||
}
|
||||
|
||||
func (s *Scan) appendModulesThatWillBeScanned(utils Utils, excludes []string) error {
|
||||
return maven.VisitAllMavenModules(".", utils, excludes, func(info maven.ModuleInfo) error {
|
||||
project := info.Project
|
||||
if project.Packaging != "pom" {
|
||||
if project.ArtifactID == "" {
|
||||
return fmt.Errorf("artifactId missing from '%s'", info.PomXMLPath)
|
||||
}
|
||||
|
||||
err := s.AppendScannedProject(project.ArtifactID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
139
pkg/whitesource/scanMaven_test.go
Normal file
139
pkg/whitesource/scanMaven_test.go
Normal file
@ -0,0 +1,139 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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",
|
||||
}
|
||||
utilsMock := NewScanUtilsMock()
|
||||
utilsMock.AddFile("pom.xml", []byte(pomXML))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteMavenScan(&config, 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",
|
||||
}
|
||||
utilsMock := NewScanUtilsMock()
|
||||
utilsMock.AddFile("pom.xml", []byte(rootPomXML))
|
||||
utilsMock.AddFile(filepath.Join("sub", "pom.xml"), []byte(modulePomXML))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteMavenScan(&config, 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.ProjectByName("my-artifact-id - product-version")
|
||||
_, existsModule := scan.ProjectByName("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",
|
||||
}
|
||||
utilsMock := NewScanUtilsMock()
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteMavenScan(&config, 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)
|
||||
})
|
||||
}
|
220
pkg/whitesource/scanNPM.go
Normal file
220
pkg/whitesource/scanNPM.go
Normal file
@ -0,0 +1,220 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const whiteSourceConfig = "whitesource.config.json"
|
||||
|
||||
func setValueAndLogChange(config map[string]interface{}, key string, value interface{}) {
|
||||
oldValue, exists := config[key]
|
||||
if exists && oldValue != value {
|
||||
log.Entry().Infof("overwriting '%s' in %s: %v -> %v", key, whiteSourceConfig, oldValue, value)
|
||||
}
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
func setValueOmitIfPresent(config map[string]interface{}, key, omitIfPresent string, value interface{}) {
|
||||
_, exists := config[omitIfPresent]
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
setValueAndLogChange(config, key, value)
|
||||
}
|
||||
|
||||
// writeWhitesourceConfigJSON creates or merges the file whitesource.config.json in the current
|
||||
// directory from the given NPMScanOptions.
|
||||
func (s *Scan) writeWhitesourceConfigJSON(config *ScanOptions, utils Utils, devDep, ignoreLsErrors bool) error {
|
||||
var npmConfig = make(map[string]interface{})
|
||||
|
||||
exists, _ := utils.FileExists(whiteSourceConfig)
|
||||
if exists {
|
||||
fileContents, err := utils.FileRead(whiteSourceConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("file '%s' already exists, but could not be read: %w", whiteSourceConfig, err)
|
||||
}
|
||||
err = json.Unmarshal(fileContents, &npmConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("file '%s' already exists, but could not be parsed: %w", whiteSourceConfig, err)
|
||||
}
|
||||
log.Entry().Infof("The file '%s' already exists in the project. Changed config details will be logged.",
|
||||
whiteSourceConfig)
|
||||
}
|
||||
|
||||
npmConfig["apiKey"] = config.OrgToken
|
||||
npmConfig["userKey"] = config.UserToken
|
||||
setValueAndLogChange(npmConfig, "checkPolicies", true)
|
||||
setValueAndLogChange(npmConfig, "productName", config.ProductName)
|
||||
setValueAndLogChange(npmConfig, "productVer", s.ProductVersion)
|
||||
setValueOmitIfPresent(npmConfig, "productToken", "projectToken", config.ProductToken)
|
||||
if config.ProjectName != "" {
|
||||
// In case there are other modules (i.e. maven modules in MTA projects),
|
||||
// or more than one NPM module, setting the project name will lead to
|
||||
// overwriting any previous scan results with the one from this module!
|
||||
// If this is not provided, the WhiteSource project name will be generated
|
||||
// from "name" in package.json plus " - " plus productVersion.
|
||||
setValueAndLogChange(npmConfig, "projectName", config.ProjectName)
|
||||
}
|
||||
setValueAndLogChange(npmConfig, "devDep", devDep)
|
||||
setValueAndLogChange(npmConfig, "ignoreNpmLsErrors", ignoreLsErrors)
|
||||
|
||||
jsonBuffer, err := json.Marshal(npmConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate '%s': %w", whiteSourceConfig, err)
|
||||
}
|
||||
|
||||
err = utils.FileWrite(whiteSourceConfig, jsonBuffer, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write '%s': %w", whiteSourceConfig, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteNpmScan iterates over all found npm modules and performs a scan in each one.
|
||||
func (s *Scan) ExecuteNpmScan(config *ScanOptions, utils Utils) error {
|
||||
modules, err := utils.FindPackageJSONFiles(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find package.json files with excludes: %w", err)
|
||||
}
|
||||
if len(modules) == 0 {
|
||||
return fmt.Errorf("found no NPM modules to scan. Configured excludes: %v",
|
||||
config.BuildDescriptorExcludeList)
|
||||
}
|
||||
for _, module := range modules {
|
||||
err := s.executeNpmScanForModule(module, config, utils)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan NPM module '%s': %w", module, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeNpmScanForModule generates a configuration file whitesource.config.json with appropriate values from config,
|
||||
// installs all dependencies if necessary, and executes the scan via "npx whitesource run".
|
||||
func (s *Scan) executeNpmScanForModule(modulePath string, config *ScanOptions, utils Utils) error {
|
||||
log.Entry().Infof("Executing Whitesource scan for NPM module '%s'", modulePath)
|
||||
|
||||
resetDir, err := utils.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to obtain current directory: %w", err)
|
||||
}
|
||||
|
||||
dir := filepath.Dir(modulePath)
|
||||
if err := utils.Chdir(dir); err != nil {
|
||||
return fmt.Errorf("failed to change into directory '%s': %w", dir, err)
|
||||
}
|
||||
defer func() {
|
||||
err = utils.Chdir(resetDir)
|
||||
if err != nil {
|
||||
log.Entry().Errorf("Failed to reset into directory '%s': %v", resetDir, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := s.writeWhitesourceConfigJSON(config, utils, false, true); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = utils.FileRemove(whiteSourceConfig) }()
|
||||
|
||||
projectName, err := getNpmProjectName(modulePath, utils)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := reinstallNodeModulesIfLsFails(config, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.AppendScannedProject(projectName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.RunExecutable("npx", "whitesource", "run")
|
||||
}
|
||||
|
||||
// getNpmProjectName tries to read a property "name" of type string from the
|
||||
// package.json file in the current directory and returns an error, if this is not possible.
|
||||
func getNpmProjectName(modulePath string, utils Utils) (string, error) {
|
||||
fileContents, err := utils.FileRead("package.json")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read %s: %w", modulePath, err)
|
||||
}
|
||||
var packageJSON = make(map[string]interface{})
|
||||
err = json.Unmarshal(fileContents, &packageJSON)
|
||||
|
||||
projectNameEntry, exists := packageJSON["name"]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("the file '%s' must configure a name", modulePath)
|
||||
}
|
||||
|
||||
projectName, isString := projectNameEntry.(string)
|
||||
if !isString {
|
||||
return "", fmt.Errorf("the file '%s' must configure a name", modulePath)
|
||||
}
|
||||
|
||||
return projectName, nil
|
||||
}
|
||||
|
||||
// reinstallNodeModulesIfLsFails tests running of "npm ls".
|
||||
// If that fails, the node_modules directory is cleared and the file "package-lock.json" is removed.
|
||||
// Then "npm install" is performed. Without this, the npm whitesource plugin will consistently hang,
|
||||
// when encountering npm ls errors, even with "ignoreNpmLsErrors:true" in the configuration.
|
||||
// The consequence is that what was scanned is not guaranteed to be identical to what was built & deployed.
|
||||
// This hack/work-around that should be removed once scanning it consistently performed using the Unified Agent.
|
||||
// A possible reason for encountering "npm ls" errors in the first place is that a different node version
|
||||
// is used for whitesourceExecuteScan due to a different docker image being used compared to the build stage.
|
||||
func reinstallNodeModulesIfLsFails(config *ScanOptions, utils Utils) error {
|
||||
// No need to have output from "npm ls" in the log
|
||||
utils.Stdout(ioutil.Discard)
|
||||
defer utils.Stdout(log.Writer())
|
||||
|
||||
err := utils.RunExecutable("npm", "ls")
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
log.Entry().Warnf("'npm ls' failed. Re-installing NPM Node Modules")
|
||||
err = utils.RemoveAll("node_modules")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove node_modules directory: %w", err)
|
||||
}
|
||||
err = utils.MkdirAll("node_modules", os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to recreate node_modules directory: %w", err)
|
||||
}
|
||||
exists, _ := utils.FileExists("package-lock.json")
|
||||
if exists {
|
||||
err = utils.FileRemove("package-lock.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove package-lock.json: %w", err)
|
||||
}
|
||||
}
|
||||
// Passing only "package.json", because we are already inside the module's directory.
|
||||
return utils.InstallAllNPMDependencies(config, []string{"package.json"})
|
||||
}
|
||||
|
||||
// ExecuteYarnScan generates a configuration file whitesource.config.json with appropriate values from config,
|
||||
// installs whitesource yarn plugin and executes the scan.
|
||||
func (s *Scan) ExecuteYarnScan(config *ScanOptions, utils Utils) error {
|
||||
// To stay compatible with what the step was doing before, trigger aggregation, although
|
||||
// there is a great chance that it doesn't work with yarn the same way it doesn't with npm.
|
||||
// Maybe the yarn code-path should be removed, and only npm stays.
|
||||
config.ProjectName = s.AggregateProjectName
|
||||
if err := s.writeWhitesourceConfigJSON(config, utils, true, false); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = utils.FileRemove(whiteSourceConfig) }()
|
||||
if err := utils.RunExecutable("yarn", "global", "add", "whitesource"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := utils.RunExecutable("yarn", "install"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := utils.RunExecutable("whitesource", "yarn"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
193
pkg/whitesource/scanNPM_test.go
Normal file
193
pkg/whitesource/scanNPM_test.go
Normal file
@ -0,0 +1,193 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExecuteScanNPM(t *testing.T) {
|
||||
config := ScanOptions{
|
||||
ScanType: "npm",
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project",
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
t.Run("happy path NPM", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := NewScanUtilsMock()
|
||||
utilsMock.AddFile("package.json", []byte(`{"name":"my-module-name"}`))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteNpmScan(&config, 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 := NewScanUtilsMock()
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteNpmScan(&config, 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 := NewScanUtilsMock()
|
||||
utilsMock.AddFile("package.json", []byte(`{"key":"value"}`))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteNpmScan(&config, utilsMock)
|
||||
// assert
|
||||
assert.EqualError(t, err, "failed to scan NPM module 'package.json': the file 'package.json' must configure a name")
|
||||
})
|
||||
t.Run("npm ls fails", func(t *testing.T) {
|
||||
// init
|
||||
utilsMock := NewScanUtilsMock()
|
||||
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 := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteNpmScan(&config, utilsMock)
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
expectedNpmInstalls := []NpmInstall{
|
||||
{currentDir: "app", packageJSON: []string{"package.json"}},
|
||||
{currentDir: "", packageJSON: []string{"package.json"}},
|
||||
}
|
||||
assert.Equal(t, expectedNpmInstalls, utilsMock.NpmInstalledModules)
|
||||
assert.True(t, utilsMock.HasRemovedFile("package-lock.json"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestWriteWhitesourceConfigJSON(t *testing.T) {
|
||||
config := &ScanOptions{
|
||||
OrgToken: "org-token",
|
||||
UserToken: "user-token",
|
||||
ProductName: "mock-product",
|
||||
ProjectName: "mock-project",
|
||||
ProductToken: "mock-product-token",
|
||||
}
|
||||
|
||||
expected := make(map[string]interface{})
|
||||
expected["apiKey"] = "org-token"
|
||||
expected["userKey"] = "user-token"
|
||||
expected["checkPolicies"] = true
|
||||
expected["productName"] = "mock-product"
|
||||
expected["projectName"] = "mock-project"
|
||||
expected["productToken"] = "mock-product-token"
|
||||
expected["productVer"] = "product-version"
|
||||
expected["devDep"] = true
|
||||
expected["ignoreNpmLsErrors"] = true
|
||||
|
||||
t.Parallel()
|
||||
|
||||
t.Run("write config from scratch", func(t *testing.T) {
|
||||
// init
|
||||
utils := NewScanUtilsMock()
|
||||
scan := newTestScan(config)
|
||||
// test
|
||||
err := scan.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)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("extend and merge config", func(t *testing.T) {
|
||||
// init
|
||||
initial := make(map[string]interface{})
|
||||
initial["checkPolicies"] = false
|
||||
initial["productName"] = "mock-product"
|
||||
initial["productVer"] = "41"
|
||||
initial["unknown"] = "preserved"
|
||||
encoded, _ := json.Marshal(initial)
|
||||
|
||||
utils := NewScanUtilsMock()
|
||||
utils.AddFile(whiteSourceConfig, encoded)
|
||||
|
||||
scan := newTestScan(config)
|
||||
|
||||
// test
|
||||
err := scan.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"
|
||||
|
||||
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 := NewScanUtilsMock()
|
||||
utils.AddFile(whiteSourceConfig, encoded)
|
||||
|
||||
scan := newTestScan(config)
|
||||
|
||||
// test
|
||||
err := scan.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)
|
||||
}
|
||||
})
|
||||
}
|
33
pkg/whitesource/scanOptions.go
Normal file
33
pkg/whitesource/scanOptions.go
Normal file
@ -0,0 +1,33 @@
|
||||
package whitesource
|
||||
|
||||
// ScanOptions contains parameters needed during the scan.
|
||||
type ScanOptions struct {
|
||||
// ScanType defines the type of scan. Can be "maven" or "mta" for scanning with Maven or "npm"/"yarn".
|
||||
ScanType string
|
||||
OrgToken string
|
||||
UserToken string
|
||||
ProductName string
|
||||
ProductToken string
|
||||
// ProjectName is an optional name for an "aggregator" project.
|
||||
// All scanned maven modules will be reflected in the aggregate project.
|
||||
ProjectName string
|
||||
BuildDescriptorExcludeList []string
|
||||
// PomPath is the path to root build descriptor file.
|
||||
PomPath string
|
||||
// M2Path is the path to the local maven repository.
|
||||
M2Path string
|
||||
// GlobalSettingsFile is an optional path to a global maven settings file.
|
||||
GlobalSettingsFile string
|
||||
// ProjectSettingsFile is an optional path to a local maven settings file.
|
||||
ProjectSettingsFile string
|
||||
|
||||
// DefaultNpmRegistry is an optional default registry for NPM.
|
||||
DefaultNpmRegistry string
|
||||
|
||||
AgentDownloadURL string
|
||||
AgentFileName string
|
||||
ConfigFilePath string
|
||||
|
||||
Includes string
|
||||
Excludes string
|
||||
}
|
77
pkg/whitesource/scanReports.go
Normal file
77
pkg/whitesource/scanReports.go
Normal file
@ -0,0 +1,77 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// ReportOptions defines options for downloading reports after scanning.
|
||||
type ReportOptions struct {
|
||||
// ReportDirectory defines the target directory for downloading reports.
|
||||
ReportDirectory string
|
||||
// VulnerabilityReportFormat defines the requested file format of the vulnerability report (i.e. pdf).
|
||||
VulnerabilityReportFormat string
|
||||
}
|
||||
|
||||
type scanUtils interface {
|
||||
MkdirAll(path string, perm os.FileMode) error
|
||||
FileWrite(path string, content []byte, perm os.FileMode) error
|
||||
}
|
||||
|
||||
// DownloadReports downloads a Project's risk and vulnerability reports
|
||||
func (s *Scan) DownloadReports(options ReportOptions, utils scanUtils, sys whitesource) ([]piperutils.Path, error) {
|
||||
if err := utils.MkdirAll(options.ReportDirectory, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var paths []piperutils.Path
|
||||
for _, project := range s.scannedProjects {
|
||||
vulnPath, err := downloadVulnerabilityReport(options, project, utils, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
riskPath, err := downloadRiskReport(options, project, utils, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, *vulnPath, *riskPath)
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func downloadVulnerabilityReport(options ReportOptions, project Project, utils scanUtils, sys whitesource) (*piperutils.Path, error) {
|
||||
reportBytes, err := sys.GetProjectVulnerabilityReport(project.Token, options.VulnerabilityReportFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rptFileName := fmt.Sprintf("%s-vulnerability-report.%s", project.Name, options.VulnerabilityReportFormat)
|
||||
rptFileName = filepath.Join(options.ReportDirectory, rptFileName)
|
||||
if err := utils.FileWrite(rptFileName, reportBytes, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Entry().Infof("Successfully downloaded vulnerability report to %s", rptFileName)
|
||||
pathName := fmt.Sprintf("%s Vulnerability Report", project.Name)
|
||||
return &piperutils.Path{Name: pathName, Target: rptFileName}, nil
|
||||
}
|
||||
|
||||
func downloadRiskReport(options ReportOptions, project Project, utils scanUtils, sys whitesource) (*piperutils.Path, error) {
|
||||
reportBytes, err := sys.GetProjectRiskReport(project.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rptFileName := fmt.Sprintf("%s-risk-report.pdf", project.Name)
|
||||
rptFileName = filepath.Join(options.ReportDirectory, rptFileName)
|
||||
if err := utils.FileWrite(rptFileName, reportBytes, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Entry().Infof("Successfully downloaded risk report to %s", rptFileName)
|
||||
pathName := fmt.Sprintf("%s PDF Risk Report", project.Name)
|
||||
return &piperutils.Path{Name: pathName, Target: rptFileName}, nil
|
||||
}
|
83
pkg/whitesource/scanReports_test.go
Normal file
83
pkg/whitesource/scanReports_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDownloadReports(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
// init
|
||||
options := ReportOptions{
|
||||
ReportDirectory: "report-dir",
|
||||
VulnerabilityReportFormat: "txt",
|
||||
}
|
||||
utils := &mock.FilesMock{}
|
||||
system := NewSystemMock("2010-05-30 00:15:00 +0100")
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
_ = scan.AppendScannedProject("mock-project")
|
||||
_ = scan.UpdateProjects("mock-product-token", system)
|
||||
// test
|
||||
paths, err := scan.DownloadReports(options, utils, system)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.Len(t, paths, 2) {
|
||||
vPath := filepath.Join("report-dir", "mock-project - 1-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 - 1-risk-report.pdf")
|
||||
assert.True(t, utils.HasWrittenFile(rPath))
|
||||
rContent, _ := utils.FileRead(rPath)
|
||||
assert.Equal(t, []byte("mock-risk-report"), rContent)
|
||||
}
|
||||
})
|
||||
t.Run("invalid project token", func(t *testing.T) {
|
||||
// init
|
||||
options := ReportOptions{
|
||||
ReportDirectory: "report-dir",
|
||||
VulnerabilityReportFormat: "txt",
|
||||
}
|
||||
utils := &mock.FilesMock{}
|
||||
system := NewSystemMock("2010-05-30 00:15:00 +0100")
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
_ = scan.AppendScannedProject("no-such-project")
|
||||
_ = scan.UpdateProjects("mock-product-token", system)
|
||||
// test
|
||||
paths, err := scan.DownloadReports(options, utils, system)
|
||||
// assert
|
||||
assert.EqualError(t, err, "no project with token '' found in Whitesource")
|
||||
assert.Nil(t, paths)
|
||||
})
|
||||
t.Run("multiple scanned projects", func(t *testing.T) {
|
||||
// init
|
||||
options := ReportOptions{
|
||||
ReportDirectory: "report-dir",
|
||||
VulnerabilityReportFormat: "txt",
|
||||
}
|
||||
utils := &mock.FilesMock{}
|
||||
system := NewSystemMock("2010-05-30 00:15:00 +0100")
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
err := scan.AppendScannedProjectVersion("mock-project - 1")
|
||||
require.NoError(t, err)
|
||||
_ = scan.UpdateProjects("mock-product-token", system)
|
||||
// test
|
||||
paths, err := scan.DownloadReports(options, utils, system)
|
||||
// assert
|
||||
if assert.NoError(t, err) && assert.Len(t, paths, 2) {
|
||||
vPath := filepath.Join("report-dir", "mock-project - 1-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 - 1-risk-report.pdf")
|
||||
assert.True(t, utils.HasWrittenFile(rPath))
|
||||
rContent, _ := utils.FileRead(rPath)
|
||||
assert.Equal(t, []byte("mock-risk-report"), rContent)
|
||||
}
|
||||
})
|
||||
}
|
89
pkg/whitesource/scanUA.go
Normal file
89
pkg/whitesource/scanUA.go
Normal file
@ -0,0 +1,89 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ExecuteUAScan executes a scan with the Whitesource Unified Agent.
|
||||
func (s *Scan) ExecuteUAScan(config *ScanOptions, utils Utils) error {
|
||||
// Download the unified agent jar file if one does not exist
|
||||
if err := downloadAgent(config, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Auto generate a config file based on the working directory's contents.
|
||||
// TODO/NOTE: Currently this scans the UA jar file as a dependency since it is downloaded beforehand
|
||||
if err := autoGenerateWhitesourceConfig(config, utils); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.RunExecutable("java", "-jar", config.AgentFileName, "-d", ".", "-c", config.ConfigFilePath,
|
||||
"-apiKey", config.OrgToken, "-userKey", config.UserToken, "-project", s.AggregateProjectName,
|
||||
"-product", config.ProductName, "-productVersion", s.ProductVersion)
|
||||
}
|
||||
|
||||
// downloadAgent downloads the unified agent jar file if one does not exist
|
||||
func downloadAgent(config *ScanOptions, utils Utils) error {
|
||||
agentFile := config.AgentFileName
|
||||
exists, err := utils.FileExists(agentFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not check whether the file '%s' exists: %w", agentFile, err)
|
||||
}
|
||||
if !exists {
|
||||
err := utils.DownloadFile(config.AgentDownloadURL, agentFile, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download unified agent from URL '%s' to file '%s': %w",
|
||||
config.AgentDownloadURL, agentFile, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// autoGenerateWhitesourceConfig
|
||||
// Auto generate a config file based on the current directory structure, renames it to user specified configFilePath
|
||||
// Generated file name will be 'wss-generated-file.config'
|
||||
func autoGenerateWhitesourceConfig(config *ScanOptions, utils Utils) error {
|
||||
// TODO: Should we rely on -detect, or set the parameters manually?
|
||||
if err := utils.RunExecutable("java", "-jar", config.AgentFileName, "-d", ".", "-detect"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename generated config file to config.ConfigFilePath parameter
|
||||
if err := utils.FileRename("wss-generated-file.config", config.ConfigFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Append aggregateModules=true parameter to config file (consolidates multi-module projects into one)
|
||||
f, err := utils.FileOpen(config.ConfigFilePath, os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
// Append additional config parameters to prevent multiple projects being generated
|
||||
m2Path := config.M2Path
|
||||
if m2Path == "" {
|
||||
m2Path = ".m2"
|
||||
}
|
||||
cfg := fmt.Sprintf("\ngradle.aggregateModules=true\nmaven.aggregateModules=true\ngradle.localRepositoryPath=.gradle\nmaven.m2RepositoryPath=%s\nexcludes=%s",
|
||||
m2Path,
|
||||
config.Excludes)
|
||||
if _, err = f.WriteString(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// archiveExtractionDepth=0
|
||||
if err := utils.RunExecutable("sed", "-ir", `s/^[#]*\s*archiveExtractionDepth=.*/archiveExtractionDepth=0/`,
|
||||
config.ConfigFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// config.Includes defaults to "**/*.java **/*.jar **/*.py **/*.go **/*.js **/*.ts"
|
||||
regex := fmt.Sprintf(`s/^[#]*\s*includes=.*/includes="%s"/`, config.Includes)
|
||||
if err := utils.RunExecutable("sed", "-ir", regex, config.ConfigFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
96
pkg/whitesource/scanUA_test.go
Normal file
96
pkg/whitesource/scanUA_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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",
|
||||
AgentDownloadURL: "https://download.ua.org/agent.jar",
|
||||
AgentFileName: "unified-agent.jar",
|
||||
ConfigFilePath: "ua.cfg",
|
||||
M2Path: ".pipeline/m2",
|
||||
}
|
||||
utilsMock := NewScanUtilsMock()
|
||||
utilsMock.AddFile("wss-generated-file.config", []byte("key=value"))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteUAScan(&config, 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", scan.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 := NewScanUtilsMock()
|
||||
utilsMock.AddFile("wss-generated-file.config", []byte("dummy"))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteUAScan(&config, 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 := NewScanUtilsMock()
|
||||
utilsMock.AddFile("wss-generated-file.config", []byte("dummy"))
|
||||
utilsMock.AddFile("unified-agent.jar", []byte("dummy"))
|
||||
scan := newTestScan(&config)
|
||||
// test
|
||||
err := scan.ExecuteUAScan(&config, utilsMock)
|
||||
// many assert
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, utilsMock.DownloadedFiles, 0)
|
||||
})
|
||||
}
|
238
pkg/whitesource/scan_test.go
Normal file
238
pkg/whitesource/scan_test.go
Normal file
@ -0,0 +1,238 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAppendScannedProjectVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("single module", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
// test
|
||||
err := scan.AppendScannedProjectVersion("module-a - 1")
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
expected := make(map[string]Project)
|
||||
expected["module-a - 1"] = Project{Name: "module-a - 1"}
|
||||
assert.Equal(t, expected, scan.scannedProjects)
|
||||
_, exists := scan.scanTimes["module-a - 1"]
|
||||
assert.True(t, exists)
|
||||
})
|
||||
t.Run("two modules", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
// test
|
||||
err1 := scan.AppendScannedProjectVersion("module-a - 1")
|
||||
err2 := scan.AppendScannedProjectVersion("module-b - 1")
|
||||
// assert
|
||||
assert.NoError(t, err1)
|
||||
assert.NoError(t, err2)
|
||||
expected := make(map[string]Project)
|
||||
expected["module-a - 1"] = Project{Name: "module-a - 1"}
|
||||
expected["module-b - 1"] = Project{Name: "module-b - 1"}
|
||||
assert.Equal(t, expected, scan.scannedProjects)
|
||||
_, exists := scan.scanTimes["module-b - 1"]
|
||||
assert.True(t, exists)
|
||||
})
|
||||
t.Run("module without version", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
// test
|
||||
err := scan.AppendScannedProjectVersion("module-a")
|
||||
// assert
|
||||
assert.EqualError(t, err, "projectName is expected to include the product version")
|
||||
assert.Len(t, scan.scannedProjects, 0)
|
||||
})
|
||||
t.Run("duplicate module", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
// test
|
||||
err1 := scan.AppendScannedProjectVersion("module-a - 1")
|
||||
err2 := scan.AppendScannedProjectVersion("module-a - 1")
|
||||
// assert
|
||||
assert.NoError(t, err1)
|
||||
assert.EqualError(t, err2, "project with name 'module-a - 1' was already scanned")
|
||||
expected := make(map[string]Project)
|
||||
expected["module-a - 1"] = Project{Name: "module-a - 1"}
|
||||
assert.Equal(t, expected, scan.scannedProjects)
|
||||
assert.Len(t, scan.scanTimes, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppendScannedProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("product version is appended", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
// test
|
||||
err := scan.AppendScannedProject("module-a")
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
expected := make(map[string]Project)
|
||||
expected["module-a - 1"] = Project{Name: "module-a - 1"}
|
||||
assert.Equal(t, expected, scan.scannedProjects)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProjectByName(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("no init", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
// test
|
||||
project, exists := scan.ProjectByName("not there")
|
||||
// assert
|
||||
assert.False(t, exists)
|
||||
assert.Equal(t, Project{}, project)
|
||||
})
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
err := scan.AppendScannedProject("module-a")
|
||||
require.NoError(t, err)
|
||||
// test
|
||||
project, exists := scan.ProjectByName("module-a - 1")
|
||||
// assert
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, Project{Name: "module-a - 1"}, project)
|
||||
})
|
||||
t.Run("no such project", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
err := scan.AppendScannedProject("module-a")
|
||||
require.NoError(t, err)
|
||||
// test
|
||||
project, exists := scan.ProjectByName("not there")
|
||||
// assert
|
||||
assert.False(t, exists)
|
||||
assert.Equal(t, Project{}, project)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScannedProjects(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("no init", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
// test
|
||||
projects := scan.ScannedProjects()
|
||||
// assert
|
||||
assert.Len(t, projects, 0)
|
||||
})
|
||||
t.Run("single module", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
_ = scan.AppendScannedProject("module-a")
|
||||
// test
|
||||
projects := scan.ScannedProjects()
|
||||
// assert
|
||||
assert.Len(t, projects, 1)
|
||||
assert.Contains(t, projects, Project{Name: "module-a - 1"})
|
||||
})
|
||||
t.Run("two modules", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
_ = scan.AppendScannedProject("module-a")
|
||||
_ = scan.AppendScannedProject("module-b")
|
||||
// test
|
||||
projects := scan.ScannedProjects()
|
||||
// assert
|
||||
assert.Len(t, projects, 2)
|
||||
assert.Contains(t, projects, Project{Name: "module-a - 1"})
|
||||
assert.Contains(t, projects, Project{Name: "module-b - 1"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestScanTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("no init", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
// test
|
||||
timeStamp := scan.ScanTime("module-b - 1")
|
||||
// assert
|
||||
assert.Equal(t, time.Time{}, timeStamp)
|
||||
})
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
_ = scan.AppendScannedProject("module-a")
|
||||
// test
|
||||
timeStamp := scan.ScanTime("module-a - 1")
|
||||
// assert
|
||||
assert.NotEqual(t, time.Time{}, timeStamp)
|
||||
})
|
||||
t.Run("project not scanned", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
_ = scan.AppendScannedProject("module-a")
|
||||
// test
|
||||
timeStamp := scan.ScanTime("module-b - 1")
|
||||
// assert
|
||||
assert.Equal(t, time.Time{}, timeStamp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScanUpdateProjects(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("update single project which exists", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
_ = scan.AppendScannedProject("mock-project")
|
||||
mockSystem := NewSystemMock("just-now")
|
||||
// test
|
||||
err := scan.UpdateProjects("mock-product-token", mockSystem)
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
expected := make(map[string]Project)
|
||||
expected["mock-project - 1"] = Project{
|
||||
Name: "mock-project - 1",
|
||||
ID: 42,
|
||||
PluginName: "mock-plugin-name",
|
||||
Token: "mock-project-token",
|
||||
UploadedBy: "MrBean",
|
||||
CreationDate: "last-thursday",
|
||||
LastUpdateDate: "just-now",
|
||||
}
|
||||
assert.Equal(t, expected, scan.scannedProjects)
|
||||
})
|
||||
t.Run("update two projects, one of which exist", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
_ = scan.AppendScannedProject("mock-project")
|
||||
_ = scan.AppendScannedProject("unknown-project")
|
||||
mockSystem := NewSystemMock("just-now")
|
||||
// test
|
||||
err := scan.UpdateProjects("mock-product-token", mockSystem)
|
||||
// assert
|
||||
assert.NoError(t, err, "no error expected if not all projects exist (yet)")
|
||||
expected := make(map[string]Project)
|
||||
expected["mock-project - 1"] = Project{
|
||||
Name: "mock-project - 1",
|
||||
ID: 42,
|
||||
PluginName: "mock-plugin-name",
|
||||
Token: "mock-project-token",
|
||||
UploadedBy: "MrBean",
|
||||
CreationDate: "last-thursday",
|
||||
LastUpdateDate: "just-now",
|
||||
}
|
||||
expected["unknown-project - 1"] = Project{
|
||||
Name: "unknown-project - 1",
|
||||
}
|
||||
assert.Equal(t, expected, scan.scannedProjects)
|
||||
})
|
||||
t.Run("update single project which does not exist", func(t *testing.T) {
|
||||
// init
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
_ = scan.AppendScannedProject("mock-project")
|
||||
mockSystem := &SystemMock{} // empty mock with no products
|
||||
// test
|
||||
err := scan.UpdateProjects("mock-product-token", mockSystem)
|
||||
// assert
|
||||
assert.EqualError(t, err, "failed to retrieve WhiteSource projects meta info: no product with that token")
|
||||
})
|
||||
}
|
131
pkg/whitesource/sytemMock.go
Normal file
131
pkg/whitesource/sytemMock.go
Normal file
@ -0,0 +1,131 @@
|
||||
// +build !release
|
||||
|
||||
package whitesource
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SystemMock stores a number of WhiteSource objects and, based on that, mocks the behavior of System.
|
||||
type SystemMock struct {
|
||||
ProductName string
|
||||
Products []Product
|
||||
Projects []Project
|
||||
Alerts []Alert
|
||||
Libraries []Library
|
||||
RiskReport []byte
|
||||
VulnerabilityReport []byte
|
||||
}
|
||||
|
||||
// GetProductByName mimics retrieving a Product by name. It returns an error of no Product is stored in the mock.
|
||||
func (m *SystemMock) GetProductByName(productName string) (Product, error) {
|
||||
for _, product := range m.Products {
|
||||
if product.Name == productName {
|
||||
return product, nil
|
||||
}
|
||||
}
|
||||
return Product{}, fmt.Errorf("no product with name '%s' found in Whitesource", productName)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if product.Token == productToken {
|
||||
return m.Projects, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no product with that token")
|
||||
}
|
||||
|
||||
// GetProjectToken checks the Projects stored in the mock and returns a valid token, or an empty token and no error.
|
||||
func (m *SystemMock) GetProjectToken(productToken, projectName string) (string, error) {
|
||||
for _, project := range m.Projects {
|
||||
if project.Name == projectName {
|
||||
return project.Token, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetProjectByToken checks the Projects stored in the mock and returns the one with the given token or an error.
|
||||
func (m *SystemMock) GetProjectByToken(projectToken string) (Project, error) {
|
||||
for _, project := range m.Projects {
|
||||
if project.Token == projectToken {
|
||||
return project, nil
|
||||
}
|
||||
}
|
||||
return Project{}, fmt.Errorf("no project with token '%s' found in Whitesource", projectToken)
|
||||
}
|
||||
|
||||
// GetProjectRiskReport mocks retrieving a risc report.
|
||||
func (m *SystemMock) GetProjectRiskReport(projectToken string) ([]byte, error) {
|
||||
return m.RiskReport, nil
|
||||
}
|
||||
|
||||
// GetProjectVulnerabilityReport mocks retrieving a vulnerability report.
|
||||
// Behavior depends on what is stored in the mock.
|
||||
func (m *SystemMock) GetProjectVulnerabilityReport(projectToken string, format string) ([]byte, error) {
|
||||
_, err := m.GetProjectByToken(projectToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if m.VulnerabilityReport == nil {
|
||||
return nil, fmt.Errorf("no report available")
|
||||
}
|
||||
return m.VulnerabilityReport, nil
|
||||
}
|
||||
|
||||
// GetProjectAlerts returns the alerts stored in the SystemMock.
|
||||
func (m *SystemMock) GetProjectAlerts(projectToken string) ([]Alert, error) {
|
||||
return m.Alerts, nil
|
||||
}
|
||||
|
||||
// GetProjectLibraryLocations returns the libraries stored in the SystemMock.
|
||||
func (m *SystemMock) GetProjectLibraryLocations(projectToken string) ([]Library, error) {
|
||||
return m.Libraries, nil
|
||||
}
|
||||
|
||||
// NewSystemMock returns a pointer to a new instance of SystemMock.
|
||||
func NewSystemMock(lastUpdateDate string) *SystemMock {
|
||||
const projectName = "mock-project - 1"
|
||||
mockLibrary := Library{
|
||||
Name: "mock-library",
|
||||
Filename: "mock-library-file",
|
||||
Version: "mock-library-version",
|
||||
Project: projectName,
|
||||
}
|
||||
return &SystemMock{
|
||||
ProductName: "mock-product",
|
||||
Products: []Product{
|
||||
{
|
||||
Name: "mock-product",
|
||||
Token: "mock-product-token",
|
||||
CreationDate: "last-thursday",
|
||||
LastUpdateDate: lastUpdateDate,
|
||||
},
|
||||
},
|
||||
Projects: []Project{
|
||||
{
|
||||
ID: 42,
|
||||
Name: projectName,
|
||||
PluginName: "mock-plugin-name",
|
||||
Token: "mock-project-token",
|
||||
UploadedBy: "MrBean",
|
||||
CreationDate: "last-thursday",
|
||||
LastUpdateDate: lastUpdateDate,
|
||||
},
|
||||
},
|
||||
Alerts: []Alert{
|
||||
{
|
||||
Vulnerability: Vulnerability{
|
||||
Name: "something severe",
|
||||
Score: 5,
|
||||
},
|
||||
Library: mockLibrary,
|
||||
Project: projectName,
|
||||
CreationDate: "last-thursday",
|
||||
},
|
||||
},
|
||||
Libraries: []Library{mockLibrary},
|
||||
RiskReport: []byte("mock-risk-report"),
|
||||
VulnerabilityReport: []byte("mock-vulnerability-report"),
|
||||
}
|
||||
}
|
37
pkg/whitesource/utils.go
Normal file
37
pkg/whitesource/utils.go
Normal file
@ -0,0 +1,37 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// File defines the method subset we use from os.File
|
||||
type File interface {
|
||||
io.Writer
|
||||
io.StringWriter
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// Utils captures all external functionality that needs to be exchangeable in tests.
|
||||
type Utils interface {
|
||||
Stdout(out io.Writer)
|
||||
Stderr(err io.Writer)
|
||||
RunExecutable(executable string, params ...string) error
|
||||
|
||||
DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
|
||||
|
||||
Chdir(path string) error
|
||||
Getwd() (string, error)
|
||||
MkdirAll(path string, perm os.FileMode) error
|
||||
FileExists(path string) (bool, error)
|
||||
FileRead(path string) ([]byte, error)
|
||||
FileWrite(path string, content []byte, perm os.FileMode) error
|
||||
FileRemove(path string) error
|
||||
FileRename(oldPath, newPath string) error
|
||||
RemoveAll(path string) error
|
||||
FileOpen(name string, flag int, perm os.FileMode) (File, error)
|
||||
|
||||
FindPackageJSONFiles(config *ScanOptions) ([]string, error)
|
||||
InstallAllNPMDependencies(config *ScanOptions, packageJSONFiles []string) error
|
||||
}
|
76
pkg/whitesource/utilsMock.go
Normal file
76
pkg/whitesource/utilsMock.go
Normal file
@ -0,0 +1,76 @@
|
||||
// +build !release
|
||||
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func newTestScan(config *ScanOptions) *Scan {
|
||||
return &Scan{
|
||||
AggregateProjectName: config.ProjectName,
|
||||
ProductVersion: "product-version",
|
||||
}
|
||||
}
|
||||
|
||||
// NpmInstall records in which directory "npm install" has been invoked and for which package.json files.
|
||||
type NpmInstall struct {
|
||||
currentDir string
|
||||
packageJSON []string
|
||||
}
|
||||
|
||||
// DownloadedFile records what URL has been downloaded to which file.
|
||||
type DownloadedFile struct {
|
||||
sourceURL string
|
||||
filePath string
|
||||
}
|
||||
|
||||
// ScanUtilsMock is an implementation of the Utils interface that can be used during tests.
|
||||
type ScanUtilsMock struct {
|
||||
*mock.FilesMock
|
||||
*mock.ExecMockRunner
|
||||
NpmInstalledModules []NpmInstall
|
||||
DownloadedFiles []DownloadedFile
|
||||
}
|
||||
|
||||
// RemoveAll mimics os.RemoveAll().
|
||||
func (m *ScanUtilsMock) RemoveAll(_ string) error {
|
||||
// Can be removed once implemented in mock.FilesMock.
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindPackageJSONFiles mimics npm.FindPackageJSONFiles() based on the FilesMock setup.
|
||||
func (m *ScanUtilsMock) FindPackageJSONFiles(_ *ScanOptions) ([]string, error) {
|
||||
matches, _ := m.Glob("**/package.json")
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// InstallAllNPMDependencies mimics npm.InstallAllNPMDependencies() and records the "npm install".
|
||||
func (m *ScanUtilsMock) InstallAllNPMDependencies(_ *ScanOptions, packageJSONs []string) error {
|
||||
m.NpmInstalledModules = append(m.NpmInstalledModules, NpmInstall{
|
||||
currentDir: m.CurrentDir,
|
||||
packageJSON: packageJSONs,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadFile mimics http.Downloader and records the downloaded file.
|
||||
func (m *ScanUtilsMock) DownloadFile(url, filename string, _ http.Header, _ []*http.Cookie) error {
|
||||
m.DownloadedFiles = append(m.DownloadedFiles, DownloadedFile{sourceURL: url, filePath: filename})
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileOpen mimics os.FileOpen() based on FilesMock Open().
|
||||
func (m *ScanUtilsMock) FileOpen(name string, flag int, perm os.FileMode) (File, error) {
|
||||
return m.Open(name, flag, perm)
|
||||
}
|
||||
|
||||
// NewScanUtilsMock returns an initialized ScanUtilsMock instance.
|
||||
func NewScanUtilsMock() *ScanUtilsMock {
|
||||
return &ScanUtilsMock{
|
||||
FilesMock: &mock.FilesMock{},
|
||||
ExecMockRunner: &mock.ExecMockRunner{},
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user