mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +02:00
301 lines
11 KiB
Go
301 lines
11 KiB
Go
package whitesource
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/SAP/jenkins-library/pkg/log"
|
|
"github.com/SAP/jenkins-library/pkg/maven"
|
|
"github.com/magiconair/properties"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// ConfigOption defines a dedicated WhiteSource config which can be enforced if required
|
|
type ConfigOption struct {
|
|
Name string
|
|
Value interface{}
|
|
OmitIfPresent string
|
|
Force bool
|
|
Append bool
|
|
}
|
|
|
|
// ConfigOptions contains a list of config options (ConfigOption)
|
|
type ConfigOptions []ConfigOption
|
|
|
|
// RewriteUAConfigurationFile updates the user's Unified Agent configuration with configuration which should be enforced or just eases the overall configuration
|
|
// It then returns the path to the file containing the updated configuration
|
|
func (s *ScanOptions) RewriteUAConfigurationFile(utils Utils, projectName string) (string, error) {
|
|
|
|
uaContent, err := utils.FileRead(s.ConfigFilePath)
|
|
uaConfig, propErr := properties.Load(uaContent, properties.UTF8)
|
|
uaConfigMap := map[string]string{}
|
|
if err != nil || propErr != nil {
|
|
log.Entry().Warningf("Failed to load configuration file '%v'. Creating a configuration file from scratch.", s.ConfigFilePath)
|
|
} else {
|
|
uaConfigMap = uaConfig.Map()
|
|
}
|
|
|
|
cOptions := ConfigOptions{}
|
|
cOptions.addGeneralDefaults(s, utils, projectName)
|
|
cOptions.addBuildToolDefaults(s, utils)
|
|
|
|
newConfigMap := cOptions.updateConfig(&uaConfigMap)
|
|
newConfig := properties.LoadMap(newConfigMap)
|
|
|
|
now := time.Now().Format("20060102150405")
|
|
newConfigFilePath := fmt.Sprintf("%v.%v", s.ConfigFilePath, now)
|
|
|
|
var configContent bytes.Buffer
|
|
_, err = newConfig.Write(&configContent, properties.UTF8)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to write properties")
|
|
}
|
|
|
|
err = utils.FileWrite(newConfigFilePath, configContent.Bytes(), 0666)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to write file")
|
|
}
|
|
|
|
return newConfigFilePath, nil
|
|
}
|
|
|
|
func (c *ConfigOptions) updateConfig(originalConfig *map[string]string) map[string]string {
|
|
newConfig := map[string]string{}
|
|
for k, v := range *originalConfig {
|
|
newConfig[k] = v
|
|
}
|
|
|
|
for _, cOpt := range *c {
|
|
//omit default if value present
|
|
var dependentValue string
|
|
if len(cOpt.OmitIfPresent) > 0 {
|
|
dependentValue = newConfig[cOpt.OmitIfPresent]
|
|
}
|
|
|
|
if len(dependentValue) == 0 {
|
|
if cOpt.Append {
|
|
if len(newConfig[cOpt.Name]) > 0 {
|
|
newConfig[cOpt.Name] = fmt.Sprintf("%v %v", newConfig[cOpt.Name], cOpt.Value)
|
|
} else {
|
|
newConfig[cOpt.Name] = fmt.Sprint(cOpt.Value)
|
|
}
|
|
} else if cOpt.Force || len(newConfig[cOpt.Name]) == 0 {
|
|
newConfig[cOpt.Name] = fmt.Sprint(cOpt.Value)
|
|
}
|
|
}
|
|
}
|
|
return newConfig
|
|
}
|
|
|
|
func (c *ConfigOptions) addGeneralDefaults(config *ScanOptions, utils Utils, projectName string) {
|
|
cOptions := ConfigOptions{}
|
|
if strings.HasPrefix(config.ProductName, "DIST - ") {
|
|
cOptions = append(cOptions, []ConfigOption{
|
|
{Name: "checkPolicies", Value: false, Force: true},
|
|
{Name: "forceCheckAllDependencies", Value: false, Force: true},
|
|
}...)
|
|
} else {
|
|
cOptions = append(cOptions, []ConfigOption{
|
|
{Name: "checkPolicies", Value: true, Force: true},
|
|
{Name: "forceCheckAllDependencies", Value: true, Force: true},
|
|
}...)
|
|
}
|
|
|
|
if config.Verbose {
|
|
cOptions = append(cOptions, []ConfigOption{
|
|
{Name: "log.level", Value: "debug"},
|
|
{Name: "log.files.level", Value: "debug"},
|
|
}...)
|
|
}
|
|
|
|
if len(config.Excludes) > 0 {
|
|
cOptions = append(cOptions, ConfigOption{Name: "excludes", Value: strings.Join(config.Excludes, " "), Force: true})
|
|
}
|
|
|
|
if len(config.Includes) > 0 {
|
|
cOptions = append(cOptions, ConfigOption{Name: "includes", Value: strings.Join(config.Includes, " "), Force: true})
|
|
}
|
|
|
|
// might need some refactoring later
|
|
if len(projectName) == 0 {
|
|
projectName = config.ProjectName
|
|
}
|
|
|
|
cOptions = append(cOptions, []ConfigOption{
|
|
{Name: "apiKey", Value: config.OrgToken, Force: true},
|
|
{Name: "productName", Value: config.ProductName, Force: true},
|
|
{Name: "productVersion", Value: config.ProductVersion, Force: true},
|
|
{Name: "projectName", Value: projectName, Force: true},
|
|
{Name: "projectVersion", Value: config.ProductVersion, Force: true},
|
|
{Name: "productToken", Value: config.ProductToken, OmitIfPresent: "projectToken", Force: true},
|
|
{Name: "userKey", Value: config.UserToken, Force: true},
|
|
{Name: "forceUpdate", Value: true, Force: true},
|
|
{Name: "offline", Value: false, Force: true},
|
|
{Name: "resolveAllDependencies", Value: false, Force: true},
|
|
{Name: "failErrorLevel", Value: "ALL", Force: true},
|
|
{Name: "case.sensitive.glob", Value: false},
|
|
{Name: "followSymbolicLinks", Value: true},
|
|
}...)
|
|
|
|
for _, cOpt := range cOptions {
|
|
*c = append(*c, cOpt)
|
|
}
|
|
}
|
|
|
|
func (c *ConfigOptions) addBuildToolDefaults(config *ScanOptions, utils Utils) error {
|
|
buildToolDefaults := map[string]ConfigOptions{
|
|
"docker": {
|
|
{Name: "docker.scanImages", Value: true, Force: true},
|
|
{Name: "docker.scanTarFiles", Value: true, Force: true},
|
|
{Name: "docker.includes", Value: ".*.tar", Force: true},
|
|
{Name: "ignoreSourceFiles", Value: false},
|
|
{Name: "python.resolveGlobalPackages", Value: true, Force: false},
|
|
{Name: "resolveAllDependencies", Value: true, Force: false},
|
|
{Name: "updateType", Value: "OVERRIDE", Force: true},
|
|
{Name: "docker.excludeBaseImage", Value: "true", Force: false},
|
|
},
|
|
"dub": {
|
|
{Name: "ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "includes", Value: "**/*.d **/*.di"},
|
|
},
|
|
//ToDo: rename to go?
|
|
//ToDo: switch to gomod as dependency manager
|
|
"golang": {
|
|
{Name: "ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "go.ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "go.collectDependenciesAtRuntime", Value: false},
|
|
{Name: "go.modules.resolveDependencies", Value: true, Force: true},
|
|
{Name: "go.modules.ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "includes", Value: "**/*.lock **/*.y*ml **/*.json **/*.tsv"},
|
|
},
|
|
"gradle": {
|
|
{Name: "ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "gradle.localRepositoryPath", Value: ".gradle", Force: false},
|
|
},
|
|
"maven": {
|
|
{Name: "ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "updateEmptyProject", Value: true, Force: true},
|
|
{Name: "maven.resolveDependencies", Value: true, Force: true},
|
|
{Name: "maven.ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "maven.aggregateModules", Value: false},
|
|
{Name: "maven.ignoredScopes", Value: "test provided"},
|
|
{Name: "maven.ignorePomModules", Value: false},
|
|
{Name: "maven.runPreStep", Value: true},
|
|
// ToDo: check with Klaus since when set to true name will not include groupId any longer
|
|
{Name: "maven.projectNameFromDependencyFile", Value: false},
|
|
{Name: "includes", Value: "**/*.jar"},
|
|
{Name: "excludes", Value: "**/*sources.jar **/*javadoc.jar"},
|
|
},
|
|
"npm": {
|
|
{Name: "ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "npm.resolveDependencies", Value: true, Force: true},
|
|
{Name: "npm.ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "npm.ignoreNpmLsErrors", Value: true},
|
|
{Name: "npm.failOnNpmLsErrors", Value: false},
|
|
{Name: "npm.runPreStep", Value: true},
|
|
{Name: "npm.projectNameFromDependencyFile", Value: true},
|
|
{Name: "npm.resolveLockFile", Value: true},
|
|
},
|
|
"pip": {
|
|
{Name: "ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "python.resolveDependencies", Value: true, Force: true},
|
|
{Name: "python.ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "python.ignorePipInstallErrors", Value: false},
|
|
{Name: "python.installVirtualEnv", Value: true},
|
|
{Name: "python.resolveHierarchyTree", Value: true},
|
|
{Name: "python.requirementsFileIncludes", Value: "requirements.txt"},
|
|
{Name: "python.resolveSetupPyFiles", Value: true},
|
|
{Name: "python.runPipenvPreStep", Value: true},
|
|
{Name: "python.pipenvDevDependencies", Value: true},
|
|
{Name: "python.IgnorePipenvInstallErrors", Value: false},
|
|
{Name: "includes", Value: "**/*.py **/*.txt"},
|
|
{Name: "excludes", Value: "**/*sources.jar **/*javadoc.jar"},
|
|
},
|
|
"sbt": {
|
|
{Name: "ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "sbt.resolveDependencies", Value: true, Force: true},
|
|
{Name: "sbt.ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "sbt.aggregateModules", Value: false, Force: true},
|
|
{Name: "sbt.runPreStep", Value: true},
|
|
{Name: "includes", Value: "**/*.jar"},
|
|
{Name: "excludes", Value: "**/*sources.jar **/*javadoc.jar"},
|
|
},
|
|
"yarn": {
|
|
{Name: "ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "npm.resolveDependencies", Value: true, Force: true},
|
|
{Name: "npm.ignoreSourceFiles", Value: true, Force: true},
|
|
{Name: "npm.yarnProject", Value: true, Force: true},
|
|
},
|
|
}
|
|
|
|
if config.BuildTool == "maven" {
|
|
if len(config.M2Path) > 0 {
|
|
*c = append(*c, ConfigOption{Name: "maven.m2RepositoryPath", Value: config.M2Path, Force: true})
|
|
}
|
|
|
|
mvnAdditionalArguments, _ := maven.DownloadAndGetMavenParameters(config.GlobalSettingsFile, config.ProjectSettingsFile, utils)
|
|
mvnAdditionalArguments = append(mvnAdditionalArguments, mvnProjectExcludes(config.BuildDescriptorExcludeList, utils)...)
|
|
|
|
if len(mvnAdditionalArguments) > 0 {
|
|
*c = append(*c, ConfigOption{Name: "maven.additionalArguments", Value: strings.Join(mvnAdditionalArguments, " "), Append: true})
|
|
}
|
|
|
|
}
|
|
|
|
if config.BuildTool == "docker" {
|
|
// for now only support default name of Dockerfile
|
|
// ToDo: evaluate possibilities to allow also non-default Dockerfile names
|
|
dockerFile := "Dockerfile"
|
|
if exists, _ := utils.FileExists("Dockerfile"); exists {
|
|
*c = append(*c, ConfigOption{Name: "docker.dockerfilePath", Value: dockerFile, Force: false})
|
|
}
|
|
|
|
}
|
|
|
|
if cOptions := buildToolDefaults[config.BuildTool]; cOptions != nil {
|
|
for _, cOpt := range cOptions {
|
|
*c = append(*c, cOpt)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//ToDo: Do we want to auto generate the config via autoGenerateWhitesourceConfig() here?
|
|
// -> try to load original config file -> if not available generate?
|
|
|
|
log.Entry().Infof("Configuration for buildTool: '%v' is not yet hardened, please do a quality assessment of your scan results.", config.BuildTool)
|
|
return fmt.Errorf("configuration not hardened")
|
|
}
|
|
|
|
// handle modules to exclude based on buildDescriptorExcludeList returning e.g. --projects !integration-tests
|
|
func mvnProjectExcludes(buildDescriptorExcludeList []string, utils Utils) []string {
|
|
projectExcludes := []string{}
|
|
for _, buildDescriptor := range buildDescriptorExcludeList {
|
|
exists, _ := utils.FileExists(buildDescriptor)
|
|
if strings.Contains(buildDescriptor, "pom.xml") && exists {
|
|
module, _ := filepath.Split(buildDescriptor)
|
|
projectExcludes = append(projectExcludes, fmt.Sprintf("!%v", strings.TrimSuffix(module, "/")))
|
|
}
|
|
}
|
|
if len(projectExcludes) > 0 {
|
|
return []string{"--projects", strings.Join(projectExcludes, ",")}
|
|
}
|
|
return []string{}
|
|
}
|
|
|
|
//ToDo: Check if we want to optionally allow auto generation for unknown projects
|
|
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
|
|
}
|
|
return nil
|
|
}
|