From 4f577388882f3216673823a9fa268e7c8181dd68 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Fri, 13 Dec 2019 09:55:45 +0000 Subject: [PATCH] Detect: Add golang implementation (#1049) Add detect golang implementation --- cmd/detectExecuteScan.go | 70 +++++++++++++ cmd/detectExecuteScan_generated.go | 128 ++++++++++++++++++++++++ cmd/detectExecuteScan_generated_test.go | 16 +++ cmd/detectExecuteScan_test.go | 92 +++++++++++++++++ cmd/piper.go | 1 + cmd/piper_test.go | 3 +- pkg/generator/helper/helper.go | 17 +++- pkg/generator/helper/helper_test.go | 21 +++- resources/metadata/detect.yaml | 108 ++++++++++++++++++++ 9 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 cmd/detectExecuteScan.go create mode 100644 cmd/detectExecuteScan_generated.go create mode 100644 cmd/detectExecuteScan_generated_test.go create mode 100644 cmd/detectExecuteScan_test.go create mode 100644 resources/metadata/detect.yaml diff --git a/cmd/detectExecuteScan.go b/cmd/detectExecuteScan.go new file mode 100644 index 000000000..3ebd50381 --- /dev/null +++ b/cmd/detectExecuteScan.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/SAP/jenkins-library/pkg/command" + "github.com/SAP/jenkins-library/pkg/log" +) + +func detectExecuteScan(myDetectExecuteScanOptions detectExecuteScanOptions) error { + c := command.Command{} + // reroute command output to logging framework + c.Stdout(log.Entry().Writer()) + c.Stderr(log.Entry().Writer()) + runDetect(myDetectExecuteScanOptions, &c) + return nil +} + +func runDetect(myDetectExecuteScanOptions detectExecuteScanOptions, command shellRunner) { + // detect execution details, see https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/88440888/Sample+Synopsys+Detect+Scan+Configuration+Scenarios+for+Black+Duck + + args := []string{"bash <(curl -s https://detect.synopsys.com/detect.sh)"} + args = addDetectArgs(args, myDetectExecuteScanOptions) + script := strings.Join(args, " ") + + command.Dir(".") + + err := command.RunShell("/bin/bash", script) + if err != nil { + log.Entry(). + WithError(err). + WithField("command", myKarmaExecuteTestsOptions.InstallCommand). + Fatal("failed to execute detect scan") + } +} + +func addDetectArgs(args []string, myDetectExecuteScanOptions detectExecuteScanOptions) []string { + + args = append(args, myDetectExecuteScanOptions.ScanProperties...) + + args = append(args, fmt.Sprintf("--blackduck.url=%v", myDetectExecuteScanOptions.ServerURL)) + args = append(args, fmt.Sprintf("--blackduck.api.token=%v", myDetectExecuteScanOptions.APIToken)) + + args = append(args, fmt.Sprintf("--detect.project.name=%v", myDetectExecuteScanOptions.ProjectName)) + args = append(args, fmt.Sprintf("--detect.project.version.name=%v", myDetectExecuteScanOptions.ProjectVersion)) + codeLocation := myDetectExecuteScanOptions.CodeLocation + if len(codeLocation) == 0 && len(myDetectExecuteScanOptions.ProjectName) > 0 { + codeLocation = fmt.Sprintf("%v/%v", myDetectExecuteScanOptions.ProjectName, myDetectExecuteScanOptions.ProjectVersion) + } + args = append(args, fmt.Sprintf("--detect.code.location.name=%v", codeLocation)) + + if sliceContains(myDetectExecuteScanOptions.Scanners, "signature") { + args = append(args, fmt.Sprintf("--detect.blackduck.signature.scanner.paths=%v", strings.Join(myDetectExecuteScanOptions.ScanPaths, ","))) + } + + if sliceContains(myDetectExecuteScanOptions.Scanners, "source") { + args = append(args, fmt.Sprintf("--detect.source.path=%v", myDetectExecuteScanOptions.ScanPaths[0])) + } + return args +} + +func sliceContains(slice []string, find string) bool { + for _, elem := range slice { + if elem == find { + return true + } + } + return false +} diff --git a/cmd/detectExecuteScan_generated.go b/cmd/detectExecuteScan_generated.go new file mode 100644 index 000000000..de0af02f0 --- /dev/null +++ b/cmd/detectExecuteScan_generated.go @@ -0,0 +1,128 @@ +package cmd + +import ( + "os" + + "github.com/SAP/jenkins-library/pkg/config" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/spf13/cobra" +) + +type detectExecuteScanOptions struct { + APIToken string `json:"apiToken,omitempty"` + CodeLocation string `json:"codeLocation,omitempty"` + ProjectName string `json:"projectName,omitempty"` + ProjectVersion string `json:"projectVersion,omitempty"` + Scanners []string `json:"scanners,omitempty"` + ScanPaths []string `json:"scanPaths,omitempty"` + ScanProperties []string `json:"scanProperties,omitempty"` + ServerURL string `json:"serverUrl,omitempty"` +} + +var myDetectExecuteScanOptions detectExecuteScanOptions +var detectExecuteScanStepConfigJSON string + +// DetectExecuteScanCommand Executes Synopsis Detect scan +func DetectExecuteScanCommand() *cobra.Command { + metadata := detectExecuteScanMetadata() + var createDetectExecuteScanCmd = &cobra.Command{ + Use: "detectExecuteScan", + Short: "Executes Synopsis Detect scan", + Long: `This step executes [Synopsis Detect](https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/62423113/Synopsys+Detect) scans.`, + PreRunE: func(cmd *cobra.Command, args []string) error { + log.SetStepName("detectExecuteScan") + log.SetVerbose(GeneralConfig.Verbose) + return PrepareConfig(cmd, &metadata, "detectExecuteScan", &myDetectExecuteScanOptions, config.OpenPiperFile) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return detectExecuteScan(myDetectExecuteScanOptions) + }, + } + + addDetectExecuteScanFlags(createDetectExecuteScanCmd) + return createDetectExecuteScanCmd +} + +func addDetectExecuteScanFlags(cmd *cobra.Command) { + cmd.Flags().StringVar(&myDetectExecuteScanOptions.APIToken, "apiToken", os.Getenv("PIPER_apiToken"), "Api token to be used for connectivity with Synopsis Detect server.") + cmd.Flags().StringVar(&myDetectExecuteScanOptions.CodeLocation, "codeLocation", os.Getenv("PIPER_codeLocation"), "An override for the name Detect will use for the scan file it creates.") + cmd.Flags().StringVar(&myDetectExecuteScanOptions.ProjectName, "projectName", os.Getenv("PIPER_projectName"), "Name of the Synopsis Detect (formerly BlackDuck) project.") + cmd.Flags().StringVar(&myDetectExecuteScanOptions.ProjectVersion, "projectVersion", os.Getenv("PIPER_projectVersion"), "Version of the Synopsis Detect (formerly BlackDuck) project.") + cmd.Flags().StringSliceVar(&myDetectExecuteScanOptions.Scanners, "scanners", []string{"signature"}, "List of scanners to be used for Synopsis Detect (formerly BlackDuck) scan.") + cmd.Flags().StringSliceVar(&myDetectExecuteScanOptions.ScanPaths, "scanPaths", []string{"."}, "List of paths which should be scanned by the Synopsis Detect (formerly BlackDuck) scan.") + cmd.Flags().StringSliceVar(&myDetectExecuteScanOptions.ScanProperties, "scanProperties", []string{"--blackduck.signature.scanner.memory=4096", "--blackduck.timeout=6000", "--blackduck.trust.cert=true", "--detect.policy.check.fail.on.severities=BLOCKER,CRITICAL,MAJOR", "--detect.report.timeout=4800", "--logging.level.com.synopsys.integration=DEBUG"}, "Properties passed to the Synopsis Detect (formerly BlackDuck) scan. You can find details in the [Synopsis Detect documentation](https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/622846/Using+Synopsys+Detect+Properties)") + cmd.Flags().StringVar(&myDetectExecuteScanOptions.ServerURL, "serverUrl", os.Getenv("PIPER_serverUrl"), "Server url to the Synopsis Detect (formerly BlackDuck) Server.") + + cmd.MarkFlagRequired("apiToken") + cmd.MarkFlagRequired("projectName") + cmd.MarkFlagRequired("projectVersion") +} + +// retrieve step metadata +func detectExecuteScanMetadata() config.StepData { + var theMetaData = config.StepData{ + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Parameters: []config.StepParameters{ + { + Name: "apiToken", + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{{Name: "detect/apiToken"}}, + }, + { + Name: "codeLocation", + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "projectName", + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{{Name: "detect/projectName"}}, + }, + { + Name: "projectVersion", + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{{Name: "detect/projectVersion"}}, + }, + { + Name: "scanners", + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "[]string", + Mandatory: false, + Aliases: []config.Alias{{Name: "detect/scanners"}}, + }, + { + Name: "scanPaths", + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "[]string", + Mandatory: false, + Aliases: []config.Alias{{Name: "detect/scanPaths"}}, + }, + { + Name: "scanProperties", + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "[]string", + Mandatory: false, + Aliases: []config.Alias{{Name: "detect/scanProperties"}}, + }, + { + Name: "serverUrl", + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{{Name: "detect/serverUrl"}}, + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/detectExecuteScan_generated_test.go b/cmd/detectExecuteScan_generated_test.go new file mode 100644 index 000000000..23a4154b6 --- /dev/null +++ b/cmd/detectExecuteScan_generated_test.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDetectExecuteScanCommand(t *testing.T) { + + testCmd := DetectExecuteScanCommand() + + // only high level testing performed - details are tested in step generation procudure + assert.Equal(t, "detectExecuteScan", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/detectExecuteScan_test.go b/cmd/detectExecuteScan_test.go new file mode 100644 index 000000000..c2bda33ff --- /dev/null +++ b/cmd/detectExecuteScan_test.go @@ -0,0 +1,92 @@ +package cmd + +import ( + "fmt" + "testing" + + "github.com/SAP/jenkins-library/pkg/log" + "github.com/stretchr/testify/assert" +) + +func TestRunDetect(t *testing.T) { + + t.Run("success case", func(t *testing.T) { + s := shellMockRunner{} + runDetect(detectExecuteScanOptions{}, &s) + + assert.Equal(t, ".", s.dir, "Wrong execution directory used") + assert.Equal(t, "/bin/bash", s.shell[0], "Bash shell expected") + expectedScript := "bash <(curl -s https://detect.synopsys.com/detect.sh) --blackduck.url= --blackduck.api.token= --detect.project.name= --detect.project.version.name= --detect.code.location.name=" + assert.Equal(t, expectedScript, s.calls[0]) + }) + + t.Run("failure case", func(t *testing.T) { + var hasFailed bool + log.Entry().Logger.ExitFunc = func(int) { hasFailed = true } + + s := shellMockRunner{shouldFailWith: fmt.Errorf("Test Error")} + runDetect(detectExecuteScanOptions{}, &s) + assert.True(t, hasFailed, "expected command to exit with fatal") + }) +} + +func TestAddDetectArgs(t *testing.T) { + testData := []struct { + args []string + options detectExecuteScanOptions + expected []string + }{ + { + args: []string{"--testProp1=1"}, + options: detectExecuteScanOptions{ + ScanProperties: []string{"--scan1=1", "--scan2=2"}, + ServerURL: "https://server.url", + APIToken: "apiToken", + ProjectName: "testName", + ProjectVersion: "1.0", + CodeLocation: "", + Scanners: []string{"signature"}, + ScanPaths: []string{"path1", "path2"}, + }, + expected: []string{ + "--testProp1=1", + "--scan1=1", + "--scan2=2", + "--blackduck.url=https://server.url", + "--blackduck.api.token=apiToken", + "--detect.project.name=testName", + "--detect.project.version.name=1.0", + "--detect.code.location.name=testName/1.0", + "--detect.blackduck.signature.scanner.paths=path1,path2", + }, + }, + { + args: []string{"--testProp1=1"}, + options: detectExecuteScanOptions{ + ServerURL: "https://server.url", + APIToken: "apiToken", + ProjectName: "testName", + ProjectVersion: "1.0", + CodeLocation: "testLocation", + Scanners: []string{"source"}, + ScanPaths: []string{"path1", "path2"}, + }, + expected: []string{ + "--testProp1=1", + "--blackduck.url=https://server.url", + "--blackduck.api.token=apiToken", + "--detect.project.name=testName", + "--detect.project.version.name=1.0", + "--detect.code.location.name=testLocation", + "--detect.source.path=path1", + }, + }, + } + + for k, v := range testData { + t.Run(fmt.Sprintf("run %v", k), func(t *testing.T) { + got := addDetectArgs(v.args, v.options) + assert.Equal(t, v.expected, got) + }) + } +} diff --git a/cmd/piper.go b/cmd/piper.go index a2ef764dc..c48beea2b 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -43,6 +43,7 @@ func Execute() { rootCmd.AddCommand(ConfigCommand()) rootCmd.AddCommand(VersionCommand()) + rootCmd.AddCommand(DetectExecuteScanCommand()) rootCmd.AddCommand(KarmaExecuteTestsCommand()) rootCmd.AddCommand(XsDeployCommand()) rootCmd.AddCommand(GithubPublishReleaseCommand()) diff --git a/cmd/piper_test.go b/cmd/piper_test.go index 2a3afb7a4..776eacc1a 100644 --- a/cmd/piper_test.go +++ b/cmd/piper_test.go @@ -28,6 +28,7 @@ type execCall struct { type shellMockRunner struct { dir string calls []string + shell []string stdout io.Writer stderr io.Writer shouldFailWith error @@ -63,7 +64,7 @@ func (m *shellMockRunner) RunShell(s string, c string) error { if m.shouldFailWith != nil { return m.shouldFailWith } - + m.shell = append(m.shell, s) m.calls = append(m.calls, c) return nil } diff --git a/pkg/generator/helper/helper.go b/pkg/generator/helper/helper.go index 7351efb4e..b00b0d074 100644 --- a/pkg/generator/helper/helper.go +++ b/pkg/generator/helper/helper.go @@ -198,7 +198,7 @@ func setDefaultParameters(stepData *config.StepData) (bool, error) { } param.Default = boolVal case "[]string": - param.Default = fmt.Sprintf("[]string{\"%v\"}", strings.Join(param.Default.([]string), "\", \"")) + param.Default = fmt.Sprintf("[]string{\"%v\"}", strings.Join(getStringSliceFromInterface(param.Default), "\", \"")) default: return false, fmt.Errorf("Meta data type not set or not known: '%v'", param.Type) } @@ -308,3 +308,18 @@ func flagType(paramType string) string { } return theFlagType } + +func getStringSliceFromInterface(iSlice interface{}) []string { + s := []string{} + + t, ok := iSlice.([]interface{}) + if ok { + for _, v := range t { + s = append(s, fmt.Sprintf("%v", v)) + } + } else { + s = append(s, fmt.Sprintf("%v", iSlice)) + } + + return s +} diff --git a/pkg/generator/helper/helper_test.go b/pkg/generator/helper/helper_test.go index dd920aab0..679bad319 100644 --- a/pkg/generator/helper/helper_test.go +++ b/pkg/generator/helper/helper_test.go @@ -89,6 +89,11 @@ func TestProcessMetaFiles(t *testing.T) { func TestSetDefaultParameters(t *testing.T) { t.Run("success case", func(t *testing.T) { + sliceVals := []string{"val4_1", "val4_2"} + stringSliceDefault := make([]interface{}, len(sliceVals)) + for i, v := range sliceVals { + stringSliceDefault[i] = v + } stepData := config.StepData{ Spec: config.StepSpec{ Inputs: config.StepInputs{ @@ -97,7 +102,7 @@ func TestSetDefaultParameters(t *testing.T) { {Name: "param1", Scope: []string{"STEPS"}, Type: "string"}, {Name: "param2", Scope: []string{"STAGES"}, Type: "bool", Default: true}, {Name: "param3", Scope: []string{"PARAMETERS"}, Type: "bool"}, - {Name: "param4", Scope: []string{"ENV"}, Type: "[]string", Default: []string{"val4_1", "val4_2"}}, + {Name: "param4", Scope: []string{"ENV"}, Type: "[]string", Default: stringSliceDefault}, {Name: "param5", Scope: []string{"ENV"}, Type: "[]string"}, }, }, @@ -230,3 +235,17 @@ func TestFlagType(t *testing.T) { assert.Equal(t, v.expected, flagType(v.input), fmt.Sprintf("wrong flag type for run %v", k)) } } + +func TestGetStringSliceFromInterface(t *testing.T) { + tt := []struct { + input interface{} + expected []string + }{ + {input: []interface{}{"Test", 2}, expected: []string{"Test", "2"}}, + {input: "Test", expected: []string{"Test"}}, + } + + for _, v := range tt { + assert.Equal(t, v.expected, getStringSliceFromInterface(v.input), "interface conversion failed") + } +} diff --git a/resources/metadata/detect.yaml b/resources/metadata/detect.yaml new file mode 100644 index 000000000..01270b850 --- /dev/null +++ b/resources/metadata/detect.yaml @@ -0,0 +1,108 @@ +metadata: + name: detectExecuteScan + description: Executes Synopsis Detect scan + longDescription: | + This step executes [Synopsis Detect](https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/62423113/Synopsys+Detect) scans. +spec: + inputs: + resources: + - name: buildDescriptor + type: stash + - name: checkmarx + type: stash + secrets: + - name: apiTokenCredentialsId + description: Jenkins 'Secret text' credentials ID containing the API token used to authenticate with the Synopsis Detect (formerly BlackDuck) Server. + type: jenkins + params: + - name: apiToken + aliases: + - name: detect/apiToken + description: Api token to be used for connectivity with Synopsis Detect server. + type: string + mandatory: true + scope: + - PARAMETERS + - STAGES + - STEPS + - name: codeLocation + description: An override for the name Detect will use for the scan file it creates. + type: string + mandatory: false + scope: + - PARAMETERS + - STAGES + - STEPS + - name: projectName + description: Name of the Synopsis Detect (formerly BlackDuck) project. + aliases: + - name: detect/projectName + type: string + mandatory: true + scope: + - PARAMETERS + - STAGES + - STEPS + - name: projectVersion + description: Version of the Synopsis Detect (formerly BlackDuck) project. + aliases: + - name: detect/projectVersion + type: string + mandatory: true + scope: + - PARAMETERS + - STAGES + - STEPS + - name: scanners + description: List of scanners to be used for Synopsis Detect (formerly BlackDuck) scan. + aliases: + - name: detect/scanners + type: '[]string' + mandatory: false + default: + - signature + possibleValues: + - signature + scope: + - PARAMETERS + - STAGES + - STEPS + - name: scanPaths + description: List of paths which should be scanned by the Synopsis Detect (formerly BlackDuck) scan. + aliases: + - name: detect/scanPaths + type: '[]string' + mandatory: false + default: + - '.' + scope: + - PARAMETERS + - STAGES + - STEPS + - name: scanProperties + description: Properties passed to the Synopsis Detect (formerly BlackDuck) scan. You can find details in the [Synopsis Detect documentation](https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/622846/Using+Synopsys+Detect+Properties) + aliases: + - name: detect/scanProperties + type: '[]string' + mandatory: false + default: + - --blackduck.signature.scanner.memory=4096 + - --blackduck.timeout=6000 + - --blackduck.trust.cert=true + - --detect.policy.check.fail.on.severities=BLOCKER,CRITICAL,MAJOR + - --detect.report.timeout=4800 + - --logging.level.com.synopsys.integration=DEBUG + scope: + - PARAMETERS + - STAGES + - STEPS + - name: serverUrl + description: Server url to the Synopsis Detect (formerly BlackDuck) Server. + aliases: + - name: detect/serverUrl + type: string + mandatory: false + scope: + - PARAMETERS + - STAGES + - STEPS