github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/whitesource/configHelper.go (about)

     1  package whitesource
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"path/filepath"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/SAP/jenkins-library/pkg/log"
    11  	"github.com/SAP/jenkins-library/pkg/maven"
    12  	"github.com/magiconair/properties"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  // ConfigOption defines a dedicated WhiteSource config which can be enforced if required
    17  type ConfigOption struct {
    18  	Name          string
    19  	Value         interface{}
    20  	OmitIfPresent string
    21  	Force         bool
    22  	Append        bool
    23  }
    24  
    25  // ConfigOptions contains a list of config options (ConfigOption)
    26  type ConfigOptions []ConfigOption
    27  
    28  // RewriteUAConfigurationFile updates the user's Unified Agent configuration with configuration which should be enforced or just eases the overall configuration
    29  // It then returns the path to the file containing the updated configuration
    30  func (s *ScanOptions) RewriteUAConfigurationFile(utils Utils, projectName string) (string, error) {
    31  
    32  	uaContent, err := utils.FileRead(s.ConfigFilePath)
    33  	uaConfig, propErr := properties.Load(uaContent, properties.UTF8)
    34  	uaConfigMap := map[string]string{}
    35  	if err != nil || propErr != nil {
    36  		log.Entry().Warningf("Failed to load configuration file '%v'. Creating a configuration file from scratch.", s.ConfigFilePath)
    37  	} else {
    38  		uaConfigMap = uaConfig.Map()
    39  	}
    40  
    41  	cOptions := ConfigOptions{}
    42  	cOptions.addGeneralDefaults(s, utils, projectName)
    43  	cOptions.addBuildToolDefaults(s, utils)
    44  
    45  	newConfigMap := cOptions.updateConfig(&uaConfigMap)
    46  	newConfig := properties.LoadMap(newConfigMap)
    47  
    48  	now := time.Now().Format("20060102150405")
    49  	newConfigFilePath := fmt.Sprintf("%v.%v", s.ConfigFilePath, now)
    50  
    51  	var configContent bytes.Buffer
    52  	_, err = newConfig.Write(&configContent, properties.UTF8)
    53  	if err != nil {
    54  		return "", errors.Wrap(err, "failed to write properties")
    55  	}
    56  
    57  	err = utils.FileWrite(newConfigFilePath, configContent.Bytes(), 0666)
    58  	if err != nil {
    59  		return "", errors.Wrap(err, "failed to write file")
    60  	}
    61  
    62  	return newConfigFilePath, nil
    63  }
    64  
    65  func (c *ConfigOptions) updateConfig(originalConfig *map[string]string) map[string]string {
    66  	newConfig := map[string]string{}
    67  	for k, v := range *originalConfig {
    68  		newConfig[k] = v
    69  	}
    70  
    71  	for _, cOpt := range *c {
    72  		//omit default if value present
    73  		var dependentValue string
    74  		if len(cOpt.OmitIfPresent) > 0 {
    75  			dependentValue = newConfig[cOpt.OmitIfPresent]
    76  		}
    77  
    78  		if len(dependentValue) == 0 {
    79  			if cOpt.Append {
    80  				if len(newConfig[cOpt.Name]) > 0 {
    81  					newConfig[cOpt.Name] = fmt.Sprintf("%v %v", newConfig[cOpt.Name], cOpt.Value)
    82  				} else {
    83  					newConfig[cOpt.Name] = fmt.Sprint(cOpt.Value)
    84  				}
    85  			} else if cOpt.Force || len(newConfig[cOpt.Name]) == 0 {
    86  				newConfig[cOpt.Name] = fmt.Sprint(cOpt.Value)
    87  			}
    88  		}
    89  	}
    90  	return newConfig
    91  }
    92  
    93  func (c *ConfigOptions) addGeneralDefaults(config *ScanOptions, utils Utils, projectName string) {
    94  	cOptions := ConfigOptions{}
    95  	if strings.HasPrefix(config.ProductName, "DIST - ") {
    96  		cOptions = append(cOptions, []ConfigOption{
    97  			{Name: "checkPolicies", Value: false, Force: true},
    98  			{Name: "forceCheckAllDependencies", Value: false, Force: true},
    99  		}...)
   100  	} else {
   101  		cOptions = append(cOptions, []ConfigOption{
   102  			{Name: "checkPolicies", Value: true, Force: true},
   103  			{Name: "forceCheckAllDependencies", Value: true, Force: true},
   104  		}...)
   105  	}
   106  
   107  	if config.Verbose {
   108  		cOptions = append(cOptions, []ConfigOption{
   109  			{Name: "log.level", Value: "debug"},
   110  			{Name: "log.files.level", Value: "debug"},
   111  		}...)
   112  	}
   113  
   114  	if len(config.Excludes) > 0 {
   115  		cOptions = append(cOptions, ConfigOption{Name: "excludes", Value: strings.Join(config.Excludes, " "), Force: true})
   116  	}
   117  
   118  	if len(config.Includes) > 0 {
   119  		cOptions = append(cOptions, ConfigOption{Name: "includes", Value: strings.Join(config.Includes, " "), Force: true})
   120  	}
   121  
   122  	// might need some refactoring later
   123  	if len(projectName) == 0 {
   124  		projectName = config.ProjectName
   125  	}
   126  
   127  	cOptions = append(cOptions, []ConfigOption{
   128  		{Name: "apiKey", Value: config.OrgToken, Force: true},
   129  		{Name: "productName", Value: config.ProductName, Force: true},
   130  		{Name: "productVersion", Value: config.ProductVersion, Force: true},
   131  		{Name: "projectName", Value: projectName, Force: true},
   132  		{Name: "projectVersion", Value: config.ProductVersion, Force: true},
   133  		{Name: "productToken", Value: config.ProductToken, OmitIfPresent: "projectToken", Force: true},
   134  		{Name: "userKey", Value: config.UserToken, Force: true},
   135  		{Name: "forceUpdate", Value: true, Force: true},
   136  		{Name: "offline", Value: false, Force: true},
   137  		{Name: "resolveAllDependencies", Value: false, Force: false},
   138  		{Name: "failErrorLevel", Value: "ALL", Force: true},
   139  		{Name: "case.sensitive.glob", Value: false},
   140  		{Name: "followSymbolicLinks", Value: true},
   141  	}...)
   142  
   143  	for _, cOpt := range cOptions {
   144  		*c = append(*c, cOpt)
   145  	}
   146  }
   147  
   148  func (c *ConfigOptions) addBuildToolDefaults(config *ScanOptions, utils Utils) error {
   149  	buildToolDefaults := map[string]ConfigOptions{
   150  		"docker": {
   151  			{Name: "docker.scanImages", Value: true, Force: true},
   152  			{Name: "docker.scanTarFiles", Value: true, Force: true},
   153  			{Name: "docker.includes", Value: ".*.tar", Force: true},
   154  			{Name: "fileSystemScan", Value: true},
   155  			{Name: "ignoreSourceFiles", Value: false},
   156  			{Name: "python.resolveGlobalPackages", Value: true, Force: false},
   157  			{Name: "updateType", Value: "OVERRIDE", Force: true},
   158  			{Name: "docker.excludeBaseImage", Value: "true", Force: false},
   159  		},
   160  		"dub": {
   161  			{Name: "ignoreSourceFiles", Value: true, Force: true},
   162  			{Name: "includes", Value: "**/*.d **/*.di"},
   163  		},
   164  		"dub2": {
   165  			{Name: "fileSystemScan", Value: false, Force: true},
   166  			{Name: "includes", Value: "**/*.d **/*.di"},
   167  		},
   168  		//ToDo: rename to go?
   169  		//ToDo: switch to gomod as dependency manager
   170  		"golang": {
   171  			{Name: "fileSystemScan", Value: false, Force: true},
   172  			{Name: "ignoreSourceFiles", Value: true, Force: true},
   173  			{Name: "go.ignoreSourceFiles", Value: true, Force: true},
   174  			{Name: "go.collectDependenciesAtRuntime", Value: false},
   175  			{Name: "go.modules.resolveDependencies", Value: true, Force: true},
   176  			{Name: "go.modules.ignoreSourceFiles", Value: true, Force: true},
   177  			{Name: "includes", Value: "**/*.lock **/*.y*ml **/*.json **/*.tsv"},
   178  		},
   179  		"gradle": {
   180  			{Name: "fileSystemScan", Value: false, Force: true},
   181  			{Name: "ignoreSourceFiles", Value: true, Force: true},
   182  			{Name: "gradle.resolveDependencies", Value: true, Force: true},
   183  			{Name: "gradle.ignoreSourceFiles", Value: true, Force: true},
   184  			{Name: "gradle.aggregateModules", Value: false, Force: true},
   185  			{Name: "gradle.runAssembleCommand", Value: true},
   186  			{Name: "gradle.runPreStep", Value: true},
   187  			{Name: "gradle.preferredEnvironment", Value: "wrapper"},
   188  			{Name: "resolveAllDependencies", Value: false},
   189  			{Name: "includes", Value: "**/*.jar"},
   190  			{Name: "excludes", Value: "**/*sources.jar **/*javadoc.jar"},
   191  		},
   192  		"maven": {
   193  			{Name: "fileSystemScan", Value: false, Force: true},
   194  			{Name: "ignoreSourceFiles", Value: true, Force: true},
   195  			{Name: "updateEmptyProject", Value: true, Force: true},
   196  			{Name: "maven.resolveDependencies", Value: true, Force: true},
   197  			{Name: "maven.ignoreSourceFiles", Value: true, Force: true},
   198  			{Name: "maven.aggregateModules", Value: false},
   199  			{Name: "maven.ignoredScopes", Value: "test provided"},
   200  			{Name: "maven.ignorePomModules", Value: false},
   201  			{Name: "maven.runPreStep", Value: true},
   202  			// ToDo: check with Klaus since when set to true name will not include groupId any longer
   203  			{Name: "maven.projectNameFromDependencyFile", Value: false},
   204  			{Name: "includes", Value: "**/*.jar"},
   205  			{Name: "excludes", Value: "**/*sources.jar **/*javadoc.jar"},
   206  		},
   207  		"npm": {
   208  			{Name: "fileSystemScan", Value: false, Force: true},
   209  			{Name: "ignoreSourceFiles", Value: true, Force: true},
   210  			{Name: "npm.resolveDependencies", Value: true, Force: true},
   211  			{Name: "npm.ignoreSourceFiles", Value: true, Force: true},
   212  			{Name: "npm.ignoreNpmLsErrors", Value: true},
   213  			{Name: "npm.failOnNpmLsErrors", Value: false},
   214  			{Name: "npm.runPreStep", Value: true},
   215  			{Name: "npm.projectNameFromDependencyFile", Value: true},
   216  			{Name: "npm.resolveLockFile", Value: true},
   217  		},
   218  		"pip": {
   219  			{Name: "fileSystemScan", Value: false, Force: true},
   220  			{Name: "ignoreSourceFiles", Value: true, Force: true},
   221  			{Name: "python.resolveDependencies", Value: true, Force: true},
   222  			{Name: "python.ignoreSourceFiles", Value: true, Force: true},
   223  			{Name: "python.ignorePipInstallErrors", Value: false},
   224  			{Name: "python.installVirtualEnv", Value: true},
   225  			{Name: "python.resolveHierarchyTree", Value: true},
   226  			{Name: "python.requirementsFileIncludes", Value: "requirements.txt"},
   227  			{Name: "python.resolveSetupPyFiles", Value: true},
   228  			{Name: "python.runPipenvPreStep", Value: true},
   229  			{Name: "python.pipenvDevDependencies", Value: true},
   230  			{Name: "python.IgnorePipenvInstallErrors", Value: false},
   231  			{Name: "includes", Value: "**/*.py **/*.txt"},
   232  			{Name: "excludes", Value: "**/*sources.jar **/*javadoc.jar"},
   233  		},
   234  		"sbt": {
   235  			{Name: "fileSystemScan", Value: false, Force: true},
   236  			{Name: "ignoreSourceFiles", Value: true, Force: true},
   237  			{Name: "sbt.resolveDependencies", Value: true, Force: true},
   238  			{Name: "sbt.ignoreSourceFiles", Value: true, Force: true},
   239  			{Name: "sbt.aggregateModules", Value: false, Force: true},
   240  			{Name: "sbt.runPreStep", Value: true},
   241  			{Name: "includes", Value: "**/*.jar"},
   242  			{Name: "excludes", Value: "**/*sources.jar **/*javadoc.jar"},
   243  		},
   244  		"yarn": {
   245  			{Name: "fileSystemScan", Value: false, Force: true},
   246  			{Name: "ignoreSourceFiles", Value: true, Force: true},
   247  			{Name: "npm.resolveDependencies", Value: true, Force: true},
   248  			{Name: "npm.ignoreSourceFiles", Value: true, Force: true},
   249  			{Name: "npm.yarnProject", Value: true, Force: true},
   250  		},
   251  	}
   252  
   253  	if config.BuildTool == "maven" {
   254  		if len(config.M2Path) > 0 {
   255  			*c = append(*c, ConfigOption{Name: "maven.m2RepositoryPath", Value: config.M2Path, Force: true})
   256  		}
   257  
   258  		mvnAdditionalArguments, _ := maven.DownloadAndGetMavenParameters(config.GlobalSettingsFile, config.ProjectSettingsFile, utils)
   259  		mvnAdditionalArguments = append(mvnAdditionalArguments, mvnProjectExcludes(config.BuildDescriptorExcludeList, utils)...)
   260  
   261  		if len(mvnAdditionalArguments) > 0 {
   262  			*c = append(*c, ConfigOption{Name: "maven.additionalArguments", Value: strings.Join(mvnAdditionalArguments, " "), Append: true})
   263  		}
   264  
   265  	}
   266  
   267  	if config.BuildTool == "docker" {
   268  		// for now only support default name of Dockerfile
   269  		// ToDo: evaluate possibilities to allow also non-default Dockerfile names
   270  		dockerFile := "Dockerfile"
   271  		if exists, _ := utils.FileExists("Dockerfile"); exists {
   272  			*c = append(*c, ConfigOption{Name: "docker.dockerfilePath", Value: dockerFile, Force: false})
   273  		}
   274  
   275  	}
   276  
   277  	if cOptions := buildToolDefaults[config.BuildTool]; cOptions != nil {
   278  		for _, cOpt := range cOptions {
   279  			*c = append(*c, cOpt)
   280  		}
   281  		return nil
   282  	}
   283  
   284  	//ToDo: Do we want to auto generate the config via autoGenerateWhitesourceConfig() here?
   285  	// -> try to load original config file -> if not available generate?
   286  
   287  	log.Entry().Infof("Configuration for buildTool: '%v' is not yet hardened, please do a quality assessment of your scan results.", config.BuildTool)
   288  	return fmt.Errorf("configuration not hardened")
   289  }
   290  
   291  // handle modules to exclude based on buildDescriptorExcludeList returning e.g. --projects !integration-tests
   292  func mvnProjectExcludes(buildDescriptorExcludeList []string, utils Utils) []string {
   293  	projectExcludes := []string{}
   294  	for _, buildDescriptor := range buildDescriptorExcludeList {
   295  		exists, _ := utils.FileExists(buildDescriptor)
   296  		if strings.Contains(buildDescriptor, "pom.xml") && exists {
   297  			module, _ := filepath.Split(buildDescriptor)
   298  			projectExcludes = append(projectExcludes, fmt.Sprintf("!%v", strings.TrimSuffix(module, "/")))
   299  		}
   300  	}
   301  	if len(projectExcludes) > 0 {
   302  		return []string{"--projects", strings.Join(projectExcludes, ",")}
   303  	}
   304  	return []string{}
   305  }
   306  
   307  // ToDo: Check if we want to optionally allow auto generation for unknown projects
   308  func autoGenerateWhitesourceConfig(config *ScanOptions, utils Utils) error {
   309  	// TODO: Should we rely on -detect, or set the parameters manually?
   310  	if err := utils.RunExecutable("java", "-jar", config.AgentFileName, "-d", ".", "-detect"); err != nil {
   311  		return err
   312  	}
   313  
   314  	// Rename generated config file to config.ConfigFilePath parameter
   315  	if err := utils.FileRename("wss-generated-file.config", config.ConfigFilePath); err != nil {
   316  		return err
   317  	}
   318  	return nil
   319  }