1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-10-30 23:57:50 +02:00

Support maven params in detect scan (#1855)

Co-authored-by: Florian Wilhelm <florian.wilhelm02@sap.com>
Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
This commit is contained in:
Daniel Kurzynski
2020-07-30 10:35:46 +02:00
committed by GitHub
parent 9009c831fb
commit 8ee0d358b9
15 changed files with 355 additions and 215 deletions

View File

@@ -2,12 +2,15 @@ package cmd
import ( import (
"fmt" "fmt"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/maven"
"strings" "strings"
sliceUtils "github.com/SAP/jenkins-library/pkg/piperutils" sliceUtils "github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/command" "github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry" "github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/versioning" "github.com/SAP/jenkins-library/pkg/versioning"
) )
@@ -17,20 +20,12 @@ func detectExecuteScan(config detectExecuteScanOptions, telemetryData *telemetry
// reroute command output to logging framework // reroute command output to logging framework
c.Stdout(log.Writer()) c.Stdout(log.Writer())
c.Stderr(log.Writer()) c.Stderr(log.Writer())
runDetect(config, &c)
}
func runDetect(config detectExecuteScanOptions, command command.ShellRunner) { fileUtils := piperutils.Files{}
// detect execution details, see https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/88440888/Sample+Synopsys+Detect+Scan+Configuration+Scenarios+for+Black+Duck httpClient := piperhttp.Client{}
args := []string{"bash <(curl -s https://detect.synopsys.com/detect.sh)"} err := runDetect(config, &c, &fileUtils, &httpClient)
args = addDetectArgs(args, config)
script := strings.Join(args, " ")
command.SetDir(".")
command.SetEnv([]string{"BLACKDUCK_SKIP_PHONE_HOME=true"})
err := command.RunShell("/bin/bash", script)
if err != nil { if err != nil {
log.Entry(). log.Entry().
WithError(err). WithError(err).
@@ -38,7 +33,29 @@ func runDetect(config detectExecuteScanOptions, command command.ShellRunner) {
} }
} }
func addDetectArgs(args []string, config detectExecuteScanOptions) []string { func runDetect(config detectExecuteScanOptions, command command.ShellRunner, fileUtils piperutils.FileUtils, httpClient piperhttp.Downloader) error {
// detect execution details, see https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/88440888/Sample+Synopsys+Detect+Scan+Configuration+Scenarios+for+Black+Duck
httpClient.DownloadFile("https://detect.synopsys.com/detect.sh", "detect.sh", nil, nil)
err := fileUtils.Chmod("detect.sh", 0700)
if err != nil {
return err
}
args := []string{"./detect.sh"}
args, err = addDetectArgs(args, config, fileUtils, httpClient)
if err != nil {
return err
}
script := strings.Join(args, " ")
envs := []string{"BLACKDUCK_SKIP_PHONE_HOME=true"}
command.SetDir(".")
command.SetEnv(envs)
return command.RunShell("/bin/bash", script)
}
func addDetectArgs(args []string, config detectExecuteScanOptions, fileUtils piperutils.FileUtils, httpClient piperhttp.Downloader) ([]string, error) {
coordinates := struct { coordinates := struct {
Version string Version string
@@ -80,5 +97,23 @@ func addDetectArgs(args []string, config detectExecuteScanOptions) []string {
if sliceUtils.ContainsString(config.Scanners, "source") { if sliceUtils.ContainsString(config.Scanners, "source") {
args = append(args, fmt.Sprintf("--detect.source.path=%v", config.ScanPaths[0])) args = append(args, fmt.Sprintf("--detect.source.path=%v", config.ScanPaths[0]))
} }
return args
mavenArgs, err := maven.DownloadAndGetMavenParameters(config.GlobalSettingsFile, config.ProjectSettingsFile, fileUtils, httpClient)
if err != nil {
return nil, err
}
if len(config.M2Path) > 0 {
absolutePath, err := fileUtils.Abs(config.M2Path)
if err != nil {
return nil, err
}
mavenArgs = append(mavenArgs, fmt.Sprintf("-Dmaven.repo.local=%v", absolutePath))
}
if len(mavenArgs) > 0 {
args = append(args, fmt.Sprintf("\"--detect.maven.build.command='%v'\"", strings.Join(mavenArgs, " ")))
}
return args, nil
} }

View File

@@ -14,17 +14,20 @@ import (
) )
type detectExecuteScanOptions struct { type detectExecuteScanOptions struct {
APIToken string `json:"apiToken,omitempty"` APIToken string `json:"apiToken,omitempty"`
CodeLocation string `json:"codeLocation,omitempty"` CodeLocation string `json:"codeLocation,omitempty"`
ProjectName string `json:"projectName,omitempty"` ProjectName string `json:"projectName,omitempty"`
Scanners []string `json:"scanners,omitempty"` Scanners []string `json:"scanners,omitempty"`
ScanPaths []string `json:"scanPaths,omitempty"` ScanPaths []string `json:"scanPaths,omitempty"`
ScanProperties []string `json:"scanProperties,omitempty"` ScanProperties []string `json:"scanProperties,omitempty"`
ServerURL string `json:"serverUrl,omitempty"` ServerURL string `json:"serverUrl,omitempty"`
Groups []string `json:"groups,omitempty"` Groups []string `json:"groups,omitempty"`
FailOn []string `json:"failOn,omitempty"` FailOn []string `json:"failOn,omitempty"`
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
VersioningModel string `json:"versioningModel,omitempty"` VersioningModel string `json:"versioningModel,omitempty"`
ProjectSettingsFile string `json:"projectSettingsFile,omitempty"`
GlobalSettingsFile string `json:"globalSettingsFile,omitempty"`
M2Path string `json:"m2Path,omitempty"`
} }
// DetectExecuteScanCommand Executes Synopsys Detect scan // DetectExecuteScanCommand Executes Synopsys Detect scan
@@ -96,6 +99,9 @@ func addDetectExecuteScanFlags(cmd *cobra.Command, stepConfig *detectExecuteScan
cmd.Flags().StringSliceVar(&stepConfig.FailOn, "failOn", []string{`BLOCKER`}, "Mark the current build as fail based on the policy categories applied.") cmd.Flags().StringSliceVar(&stepConfig.FailOn, "failOn", []string{`BLOCKER`}, "Mark the current build as fail based on the policy categories applied.")
cmd.Flags().StringVar(&stepConfig.Version, "version", os.Getenv("PIPER_version"), "Defines the version number of the artifact being build in the pipeline. It is used as source for the Detect version.") cmd.Flags().StringVar(&stepConfig.Version, "version", os.Getenv("PIPER_version"), "Defines the version number of the artifact being build in the pipeline. It is used as source for the Detect version.")
cmd.Flags().StringVar(&stepConfig.VersioningModel, "versioningModel", `major`, "The versioning model used for result reporting (based on the artifact version). Example 1.2.3 using `major` will result in version 1") cmd.Flags().StringVar(&stepConfig.VersioningModel, "versioningModel", `major`, "The versioning model used for result reporting (based on the artifact version). Example 1.2.3 using `major` will result in version 1")
cmd.Flags().StringVar(&stepConfig.ProjectSettingsFile, "projectSettingsFile", os.Getenv("PIPER_projectSettingsFile"), "Path or url to the mvn settings file that should be used as project settings file.")
cmd.Flags().StringVar(&stepConfig.GlobalSettingsFile, "globalSettingsFile", os.Getenv("PIPER_globalSettingsFile"), "Path or url to the mvn settings file that should be used as global settings file")
cmd.Flags().StringVar(&stepConfig.M2Path, "m2Path", os.Getenv("PIPER_m2Path"), "Path to the location of the local repository that should be used.")
cmd.MarkFlagRequired("apiToken") cmd.MarkFlagRequired("apiToken")
cmd.MarkFlagRequired("projectName") cmd.MarkFlagRequired("projectName")
@@ -200,6 +206,30 @@ func detectExecuteScanMetadata() config.StepData {
Mandatory: false, Mandatory: false,
Aliases: []config.Alias{}, Aliases: []config.Alias{},
}, },
{
Name: "projectSettingsFile",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/projectSettingsFile"}},
},
{
Name: "globalSettingsFile",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/globalSettingsFile"}},
},
{
Name: "m2Path",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/m2Path"}},
},
}, },
}, },
}, },

View File

@@ -2,37 +2,93 @@ package cmd
import ( import (
"fmt" "fmt"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/mock"
"net/http"
"os"
"path/filepath"
"testing" "testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
type httpClientMock struct {
expectedError error
downloadedFiles map[string]string // src, dest
}
func (c *httpClientMock) SetOptions(options piperhttp.ClientOptions) {
}
func (c *httpClientMock) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error {
if c.expectedError != nil {
return c.expectedError
}
if c.downloadedFiles == nil {
c.downloadedFiles = make(map[string]string)
}
c.downloadedFiles[url] = filename
return nil
}
func TestRunDetect(t *testing.T) { func TestRunDetect(t *testing.T) {
t.Run("success case", func(t *testing.T) { t.Run("success case", func(t *testing.T) {
s := mock.ShellMockRunner{} s := mock.ShellMockRunner{}
runDetect(detectExecuteScanOptions{}, &s) fileUtilsMock := mock.FilesMock{}
fileUtilsMock.AddFile("detect.sh", []byte(""))
httpClient := httpClientMock{}
err := runDetect(detectExecuteScanOptions{}, &s, &fileUtilsMock, &httpClient)
assert.Equal(t, httpClient.downloadedFiles["https://detect.synopsys.com/detect.sh"], "detect.sh")
fileStatus, err := fileUtilsMock.Stat("detect.sh")
assert.NoError(t, err)
assert.Equal(t, fileStatus.Mode(), os.FileMode(0700))
assert.NoError(t, err)
assert.Equal(t, ".", s.Dir, "Wrong execution directory used") assert.Equal(t, ".", s.Dir, "Wrong execution directory used")
assert.Equal(t, "/bin/bash", s.Shell[0], "Bash shell expected") 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=\\\"\\\"" expectedScript := "./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]) assert.Equal(t, expectedScript, s.Calls[0])
}) })
t.Run("failure case", func(t *testing.T) { t.Run("failure case", func(t *testing.T) {
var hasFailed bool s := mock.ShellMockRunner{ShouldFailOnCommand: map[string]error{"./detect.sh --blackduck.url= --blackduck.api.token= --detect.project.name=\\\"\\\" --detect.project.version.name=\\\"\\\" --detect.code.location.name=\\\"\\\"": fmt.Errorf("Test Error")}}
log.Entry().Logger.ExitFunc = func(int) { hasFailed = true } fileUtilsMock := mock.FilesMock{}
httpClient := httpClientMock{}
err := runDetect(detectExecuteScanOptions{}, &s, &fileUtilsMock, &httpClient)
assert.NotNil(t, err)
})
s := mock.ShellMockRunner{ShouldFailOnCommand: map[string]error{"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=\\\"\\\"": fmt.Errorf("Test Error")}} t.Run("maven parameters", func(t *testing.T) {
runDetect(detectExecuteScanOptions{}, &s) s := mock.ShellMockRunner{}
assert.True(t, hasFailed, "expected command to exit with fatal") fileUtilsMock := mock.FilesMock{
CurrentDir: "root_folder",
}
fileUtilsMock.AddFile("detect.sh", []byte(""))
httpClient := httpClientMock{}
err := runDetect(detectExecuteScanOptions{
M2Path: ".pipeline/local_repo",
ProjectSettingsFile: "project-settings.xml",
GlobalSettingsFile: "global-settings.xml",
}, &s, &fileUtilsMock, &httpClient)
assert.NoError(t, err)
assert.Equal(t, ".", s.Dir, "Wrong execution directory used")
assert.Equal(t, "/bin/bash", s.Shell[0], "Bash shell expected")
absoluteLocalPath := string(os.PathSeparator) + filepath.Join("root_folder", ".pipeline", "local_repo")
expectedParam := "\"--detect.maven.build.command='--global-settings global-settings.xml --settings project-settings.xml -Dmaven.repo.local=" + absoluteLocalPath + "'\""
assert.Contains(t, s.Calls[0], expectedParam)
}) })
} }
func TestAddDetectArgs(t *testing.T) { func TestAddDetectArgs(t *testing.T) {
httpClient := piperhttp.Client{}
fileUtilsMock := mock.FilesMock{}
testData := []struct { testData := []struct {
args []string args []string
options detectExecuteScanOptions options detectExecuteScanOptions
@@ -119,7 +175,8 @@ func TestAddDetectArgs(t *testing.T) {
for k, v := range testData { for k, v := range testData {
t.Run(fmt.Sprintf("run %v", k), func(t *testing.T) { t.Run(fmt.Sprintf("run %v", k), func(t *testing.T) {
got := addDetectArgs(v.args, v.options) got, err := addDetectArgs(v.args, v.options, &fileUtilsMock, &httpClient)
assert.NoError(t, err)
assert.Equal(t, v.expected, got) assert.Equal(t, v.expected, got)
}) })
} }

View File

@@ -78,6 +78,10 @@ func (f *kanikoFileMock) Abs(path string) (string, error) {
return "", fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.") return "", fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
} }
func (f *kanikoFileMock) Glob(pattern string) (matches []string, err error) {
return nil, fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
}
func TestRunKanikoExecute(t *testing.T) { func TestRunKanikoExecute(t *testing.T) {
t.Run("success case", func(t *testing.T) { t.Run("success case", func(t *testing.T) {

View File

@@ -39,7 +39,7 @@ modules:
build-result: dist` build-result: dist`
// for mocking // for mocking
var getSettingsFile = maven.GetSettingsFile var downloadAndCopySettingsFiles = maven.DownloadAndCopySettingsFiles
// MTABuildTarget ... // MTABuildTarget ...
type MTABuildTarget int type MTABuildTarget int
@@ -348,28 +348,7 @@ func handleSettingsFiles(config mtaBuildOptions,
p piperutils.FileUtils, p piperutils.FileUtils,
httpClient piperhttp.Downloader) error { httpClient piperhttp.Downloader) error {
if len(config.ProjectSettingsFile) > 0 { return downloadAndCopySettingsFiles(config.GlobalSettingsFile, config.ProjectSettingsFile, p, httpClient)
if err := getSettingsFile(maven.ProjectSettingsFile, config.ProjectSettingsFile, p, httpClient); err != nil {
return err
}
} else {
log.Entry().Debugf("Project settings file not provided via configuration.")
}
if len(config.GlobalSettingsFile) > 0 {
if err := getSettingsFile(maven.GlobalSettingsFile, config.GlobalSettingsFile, p, httpClient); err != nil {
return err
}
} else {
log.Entry().Debugf("Global settings file not provided via configuration.")
}
return nil
} }
func generateMta(id, applicationName, version string) (string, error) { func generateMta(id, applicationName, version string) (string, error) {

View File

@@ -281,20 +281,20 @@ func TestMarBuild(t *testing.T) {
t.Run("Settings file releatd tests", func(t *testing.T) { t.Run("Settings file releatd tests", func(t *testing.T) {
var settingsFile string var projectSettingsFile string
var settingsFileType maven.SettingsFileType var globalSettingsFile string
defer func() { defer func() {
getSettingsFile = maven.GetSettingsFile downloadAndCopySettingsFiles = maven.DownloadAndCopySettingsFiles
}() }()
getSettingsFile = func( downloadAndCopySettingsFiles = func(
sfType maven.SettingsFileType, globalSettings string,
src string, projectSettings string,
fileUtilsMock piperutils.FileUtils, fileUtils piperutils.FileUtils,
httpClientMock piperhttp.Downloader) error { httpClient maven.SettingsDownloadUtils) error {
settingsFile = src projectSettingsFile = projectSettings
settingsFileType = sfType globalSettingsFile = globalSettings
return nil return nil
} }
@@ -305,8 +305,8 @@ func TestMarBuild(t *testing.T) {
t.Run("Copy global settings file", func(t *testing.T) { t.Run("Copy global settings file", func(t *testing.T) {
defer func() { defer func() {
settingsFile = "" projectSettingsFile = ""
settingsFileType = -1 globalSettingsFile = ""
}() }()
e := mock.ExecMockRunner{} e := mock.ExecMockRunner{}
@@ -317,15 +317,15 @@ func TestMarBuild(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, settingsFile, "/opt/maven/settings.xml") assert.Equal(t, globalSettingsFile, "/opt/maven/settings.xml")
assert.Equal(t, settingsFileType, maven.GlobalSettingsFile) assert.Equal(t, projectSettingsFile, "")
}) })
t.Run("Copy project settings file", func(t *testing.T) { t.Run("Copy project settings file", func(t *testing.T) {
defer func() { defer func() {
settingsFile = "" projectSettingsFile = ""
settingsFileType = -1 globalSettingsFile = ""
}() }()
e := mock.ExecMockRunner{} e := mock.ExecMockRunner{}
@@ -336,8 +336,8 @@ func TestMarBuild(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "/my/project/settings.xml", settingsFile) assert.Equal(t, "/my/project/settings.xml", projectSettingsFile)
assert.Equal(t, maven.ProjectSettingsFile, settingsFileType) assert.Equal(t, "", globalSettingsFile)
}) })
}) })
} }
@@ -391,6 +391,10 @@ func (f *MtaTestFileUtilsMock) Abs(path string) (string, error) {
return "/root_folder/workspace/" + path, nil return "/root_folder/workspace/" + path, nil
} }
func (f *MtaTestFileUtilsMock) Glob(pattern string) (matches []string, err error) {
return nil, fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
}
func (f *MtaTestFileUtilsMock) Chmod(path string, mode os.FileMode) error { func (f *MtaTestFileUtilsMock) Chmod(path string, mode os.FileMode) error {
return fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.") return fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
} }

View File

@@ -46,6 +46,10 @@ func (f *FileUtilsMock) Abs(path string) (string, error) {
return "", fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.") return "", fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
} }
func (f *FileUtilsMock) Glob(pattern string) (matches []string, err error) {
return nil, fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
}
func TestDeploy(t *testing.T) { func TestDeploy(t *testing.T) {
myXsDeployOptions := xsDeployOptions{ myXsDeployOptions := xsDeployOptions{
APIURL: "https://example.org:12345", APIURL: "https://example.org:12345",

View File

@@ -43,11 +43,8 @@ type mavenExecRunner interface {
} }
type mavenUtils interface { type mavenUtils interface {
FileExists(path string) (bool, error) piperutils.FileUtils
DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
Glob(pattern string) (matches []string, err error)
Getwd() (dir string, err error)
Chdir(dir string) error
} }
type utilsBundle struct { type utilsBundle struct {
@@ -290,20 +287,9 @@ func evaluateStdOut(options *ExecuteOptions) (*bytes.Buffer, io.Writer) {
func getParametersFromOptions(options *ExecuteOptions, utils mavenUtils) ([]string, error) { func getParametersFromOptions(options *ExecuteOptions, utils mavenUtils) ([]string, error) {
var parameters []string var parameters []string
if options.GlobalSettingsFile != "" { parameters, err := DownloadAndGetMavenParameters(options.GlobalSettingsFile, options.ProjectSettingsFile, utils, utils)
globalSettingsFileName, err := downloadSettingsIfURL(options.GlobalSettingsFile, ".pipeline/mavenGlobalSettings.xml", utils) if err != nil {
if err != nil { return nil, err
return nil, err
}
parameters = append(parameters, "--global-settings", globalSettingsFileName)
}
if options.ProjectSettingsFile != "" {
projectSettingsFileName, err := downloadSettingsIfURL(options.ProjectSettingsFile, ".pipeline/mavenProjectSettings.xml", utils)
if err != nil {
return nil, err
}
parameters = append(parameters, "--settings", projectSettingsFileName)
} }
if options.M2Path != "" { if options.M2Path != "" {
@@ -333,33 +319,6 @@ func getParametersFromOptions(options *ExecuteOptions, utils mavenUtils) ([]stri
return parameters, nil return parameters, nil
} }
func downloadSettingsIfURL(settingsFileOption, settingsFile string, utils mavenUtils) (string, error) {
result := settingsFileOption
if strings.HasPrefix(settingsFileOption, "http:") || strings.HasPrefix(settingsFileOption, "https:") {
err := downloadSettingsFromURL(settingsFileOption, settingsFile, utils)
if err != nil {
return "", err
}
result = settingsFile
}
return result, nil
}
// ToDo replace with pkg/maven/settings GetSettingsFile
func downloadSettingsFromURL(url, filename string, utils mavenUtils) error {
exists, _ := utils.FileExists(filename)
if exists {
log.Entry().Infof("Not downloading maven settings file, because it already exists at '%s'", filename)
return nil
}
err := utils.DownloadFile(url, filename, nil, nil)
if err != nil {
return fmt.Errorf("failed to download maven settings from URL '%s' to file '%s': %w",
url, filename, err)
}
return nil
}
func GetTestModulesExcludes() []string { func GetTestModulesExcludes() []string {
return getTestModulesExcludes(newUtils()) return getTestModulesExcludes(newUtils())
} }

View File

@@ -146,19 +146,6 @@ func TestGetParameters(t *testing.T) {
}) })
} }
func TestDownloadSettingsFromURL(t *testing.T) {
t.Run("should pass if download is successful", func(t *testing.T) {
utils := newMockUtils(false)
err := downloadSettingsFromURL("anyURL", "settings.xml", &utils)
assert.NoError(t, err)
})
t.Run("should fail if download fails", func(t *testing.T) {
utils := newMockUtils(true)
err := downloadSettingsFromURL("anyURL", "settings.xml", &utils)
assert.EqualError(t, err, "failed to download maven settings from URL 'anyURL' to file 'settings.xml': something happened")
})
}
func TestGetTestModulesExcludes(t *testing.T) { func TestGetTestModulesExcludes(t *testing.T) {
t.Run("Should return excludes for unit- and integration-tests", func(t *testing.T) { t.Run("Should return excludes for unit- and integration-tests", func(t *testing.T) {
utils := newMockUtils(false) utils := newMockUtils(false)

View File

@@ -1,11 +1,10 @@
package maven package maven
import ( import (
"errors"
"fmt" "fmt"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils" "github.com/SAP/jenkins-library/pkg/piperutils"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -13,44 +12,81 @@ import (
var getenv = os.Getenv var getenv = os.Getenv
// SettingsFileType ... type SettingsDownloadUtils interface {
type SettingsFileType int DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
}
const ( func DownloadAndGetMavenParameters(globalSettingsFile string, projectSettingsFile string, fileUtils piperutils.FileUtils, httpClient SettingsDownloadUtils) ([]string, error) {
// GlobalSettingsFile ... mavenArgs := []string{}
GlobalSettingsFile SettingsFileType = iota if len(globalSettingsFile) > 0 {
// ProjectSettingsFile ... globalSettingsFileName, err := downloadSettingsIfURL(globalSettingsFile, ".pipeline/mavenGlobalSettings.xml", fileUtils, httpClient, false)
ProjectSettingsFile if err != nil {
) return nil, err
}
mavenArgs = append(mavenArgs, "--global-settings", globalSettingsFileName)
} else {
// GetSettingsFile ... log.Entry().Debugf("Global settings file not provided via configuration.")
func GetSettingsFile(settingsFileType SettingsFileType, src string, fileUtils piperutils.FileUtils, httpClient piperhttp.Downloader) error {
var dest string
var err error
switch settingsFileType {
case GlobalSettingsFile:
dest, err = getGlobalSettingsFileDest()
case ProjectSettingsFile:
dest, err = getProjectSettingsFileDest()
default:
return errors.New("Invalid SettingsFileType")
} }
if err != nil { if len(projectSettingsFile) > 0 {
return err projectSettingsFileName, err := downloadSettingsIfURL(projectSettingsFile, ".pipeline/mavenProjectSettings.xml", fileUtils, httpClient, false)
if err != nil {
return nil, err
}
mavenArgs = append(mavenArgs, "--settings", projectSettingsFileName)
} else {
log.Entry().Debugf("Project settings file not provided via configuration.")
}
return mavenArgs, nil
}
func DownloadAndCopySettingsFiles(globalSettingsFile string, projectSettingsFile string, fileUtils piperutils.FileUtils, httpClient SettingsDownloadUtils) error {
if len(projectSettingsFile) > 0 {
destination, err := getProjectSettingsFileDest()
if err != nil {
return err
}
if err := downloadAndCopySettingsFile(projectSettingsFile, destination, fileUtils, httpClient); err != nil {
return err
}
} else {
log.Entry().Debugf("Project settings file not provided via configuration.")
} }
if len(globalSettingsFile) > 0 {
destination, err := getGlobalSettingsFileDest()
if err != nil {
return err
}
if err := downloadAndCopySettingsFile(globalSettingsFile, destination, fileUtils, httpClient); err != nil {
return err
}
} else {
log.Entry().Debugf("Global settings file not provided via configuration.")
}
return nil
}
func downloadAndCopySettingsFile(src string, dest string, fileUtils piperutils.FileUtils, httpClient SettingsDownloadUtils) error {
if len(src) == 0 { if len(src) == 0 {
return fmt.Errorf("Settings file source location not provided") return fmt.Errorf("Settings file source location not provided")
} }
if len(dest) == 0 {
return fmt.Errorf("Settings file destination location not provided")
}
log.Entry().Debugf("Copying file \"%s\" to \"%s\"", src, dest) log.Entry().Debugf("Copying file \"%s\" to \"%s\"", src, dest)
if strings.HasPrefix(src, "http:") || strings.HasPrefix(src, "https:") { if strings.HasPrefix(src, "http:") || strings.HasPrefix(src, "https:") {
err := downloadSettingsFromURL(src, dest, fileUtils, httpClient, true)
if err := httpClient.DownloadFile(src, dest, nil, nil); err != nil { if err != nil {
return err return err
} }
} else { } else {
@@ -79,6 +115,32 @@ func GetSettingsFile(settingsFileType SettingsFileType, src string, fileUtils pi
return nil return nil
} }
func downloadSettingsIfURL(settingsFileOption, settingsFile string, fileUtils piperutils.FileUtils, httpClient SettingsDownloadUtils, overwrite bool) (string, error) {
result := settingsFileOption
if strings.HasPrefix(settingsFileOption, "http:") || strings.HasPrefix(settingsFileOption, "https:") {
err := downloadSettingsFromURL(settingsFileOption, settingsFile, fileUtils, httpClient, overwrite)
if err != nil {
return "", err
}
result = settingsFile
}
return result, nil
}
func downloadSettingsFromURL(url, filename string, fileUtils piperutils.FileUtils, httpClient SettingsDownloadUtils, overwrite bool) error {
exists, _ := fileUtils.FileExists(filename)
if exists && !overwrite {
log.Entry().Infof("Not downloading maven settings file, because it already exists at '%s'", filename)
return nil
}
err := httpClient.DownloadFile(url, filename, nil, nil)
if err != nil {
return fmt.Errorf("failed to download maven settings from URL '%s' to file '%s': %w",
url, filename, err)
}
return nil
}
func getGlobalSettingsFileDest() (string, error) { func getGlobalSettingsFileDest() (string, error) {
m2Home, err := getEnvironmentVariable("M2_HOME") m2Home, err := getEnvironmentVariable("M2_HOME")

View File

@@ -24,64 +24,50 @@ func TestSettings(t *testing.T) {
return "" return ""
} }
t.Run("Invalid settings file type", func(t *testing.T) {
httpClient := httpMock{}
fileUtils := fileUtilsMock{}
err := GetSettingsFile(-1, "/dev/null", &fileUtils, &httpClient)
if assert.Error(t, err) {
assert.Equal(t, "Invalid SettingsFileType", err.Error())
}
})
t.Run("Settings file source location not provided", func(t *testing.T) { t.Run("Settings file source location not provided", func(t *testing.T) {
httpClient := httpMock{} httpClient := httpMock{}
fileUtils := fileUtilsMock{} fileUtils := fileUtilsMock{}
err := GetSettingsFile(1, "", &fileUtils, &httpClient) err := downloadAndCopySettingsFile("", "foo", &fileUtils, &httpClient)
if assert.Error(t, err) { assert.EqualError(t, err, "Settings file source location not provided")
assert.Equal(t, "Settings file source location not provided", err.Error())
}
}) })
t.Run("Retrieve global settings file", func(t *testing.T) { t.Run("Settings file destination location not provided", func(t *testing.T) {
httpClient := httpMock{} httpClient := httpMock{}
fileUtils := fileUtilsMock{existingFiles: map[string]string{"/opt/sap/maven/global-settings.xml": ""}} fileUtils := fileUtilsMock{}
err := GetSettingsFile(GlobalSettingsFile, "/opt/sap/maven/global-settings.xml", &fileUtils, &httpClient) err := downloadAndCopySettingsFile("/opt/sap/maven/global-settings.xml", "", &fileUtils, &httpClient)
assert.EqualError(t, err, "Settings file destination location not provided")
})
t.Run("Retrieve settings files", func(t *testing.T) {
httpClient := httpMock{}
fileUtils := fileUtilsMock{existingFiles: map[string]string{
"/opt/sap/maven/global-settings.xml": "",
"/opt/sap/maven/project-settings.xml": "",
}}
err := DownloadAndCopySettingsFiles("/opt/sap/maven/global-settings.xml", "/opt/sap/maven/project-settings.xml", &fileUtils, &httpClient)
if assert.NoError(t, err) { if assert.NoError(t, err) {
assert.Equal(t, "/usr/share/maven/conf/settings.xml", fileUtils.copiedFiles["/opt/sap/maven/global-settings.xml"]) assert.Equal(t, "/usr/share/maven/conf/settings.xml", fileUtils.copiedFiles["/opt/sap/maven/global-settings.xml"])
}
assert.Empty(t, httpClient.downloadedFiles)
})
t.Run("Retrieve project settings file", func(t *testing.T) {
httpClient := httpMock{}
fileUtils := fileUtilsMock{existingFiles: map[string]string{"/opt/sap/maven/project-settings.xml": ""}}
err := GetSettingsFile(ProjectSettingsFile, "/opt/sap/maven/project-settings.xml", &fileUtils, &httpClient)
if assert.NoError(t, err) {
assert.Equal(t, "/home/me/.m2/settings.xml", fileUtils.copiedFiles["/opt/sap/maven/project-settings.xml"]) assert.Equal(t, "/home/me/.m2/settings.xml", fileUtils.copiedFiles["/opt/sap/maven/project-settings.xml"])
} }
assert.Empty(t, httpClient.downloadedFiles) assert.Empty(t, httpClient.downloadedFiles)
}) })
t.Run("Retrieve global settings file via http", func(t *testing.T) { t.Run("Retrieve settings file via http", func(t *testing.T) {
httpClient := httpMock{} httpClient := httpMock{}
fileUtils := fileUtilsMock{} fileUtils := fileUtilsMock{}
err := GetSettingsFile(GlobalSettingsFile, "https://example.org/maven/global-settings.xml", &fileUtils, &httpClient) err := downloadAndCopySettingsFile("https://example.org/maven/global-settings.xml", "/usr/share/maven/conf/settings.xml", &fileUtils, &httpClient)
if assert.NoError(t, err) { if assert.NoError(t, err) {
assert.Equal(t, "/usr/share/maven/conf/settings.xml", httpClient.downloadedFiles["https://example.org/maven/global-settings.xml"]) assert.Equal(t, "/usr/share/maven/conf/settings.xml", httpClient.downloadedFiles["https://example.org/maven/global-settings.xml"])
@@ -93,22 +79,10 @@ func TestSettings(t *testing.T) {
httpClient := httpMock{expectedError: fmt.Errorf("Download failed")} httpClient := httpMock{expectedError: fmt.Errorf("Download failed")}
fileUtils := fileUtilsMock{} fileUtils := fileUtilsMock{}
err := GetSettingsFile(GlobalSettingsFile, "https://example.org/maven/global-settings.xml", &fileUtils, &httpClient) err := downloadAndCopySettingsFile("https://example.org/maven/global-settings.xml", "/usr/share/maven/conf/settings.xml", &fileUtils, &httpClient)
if assert.Error(t, err) { if assert.Error(t, err) {
assert.Equal(t, "Download failed", err.Error()) assert.Contains(t, err.Error(), "failed to download maven settings from URL")
}
})
t.Run("Retrieve project settings file via http", func(t *testing.T) {
httpClient := httpMock{}
fileUtils := fileUtilsMock{}
err := GetSettingsFile(ProjectSettingsFile, "https://example.org/maven/project-settings.xml", &fileUtils, &httpClient)
if assert.NoError(t, err) {
assert.Equal(t, "/home/me/.m2/settings.xml", httpClient.downloadedFiles["https://example.org/maven/project-settings.xml"])
} }
}) })
@@ -117,7 +91,7 @@ func TestSettings(t *testing.T) {
httpClient := httpMock{} httpClient := httpMock{}
fileUtils := fileUtilsMock{} fileUtils := fileUtilsMock{}
err := GetSettingsFile(ProjectSettingsFile, "/opt/sap/maven/project-settings.xml", &fileUtils, &httpClient) err := downloadAndCopySettingsFile("/opt/sap/maven/project-settings.xml", "/home/me/.m2/settings.xml", &fileUtils, &httpClient)
if assert.Error(t, err) { if assert.Error(t, err) {
assert.Contains(t, err.Error(), "Source file '/opt/sap/maven/project-settings.xml' does not exist") assert.Contains(t, err.Error(), "Source file '/opt/sap/maven/project-settings.xml' does not exist")
@@ -208,3 +182,7 @@ func (f *fileUtilsMock) Chmod(path string, mode os.FileMode) error {
func (f *fileUtilsMock) Abs(path string) (string, error) { func (f *fileUtilsMock) Abs(path string) (string, error) {
return "", fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.") return "", fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
} }
func (f *fileUtilsMock) Glob(pattern string) (matches []string, err error) {
return nil, fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
}

View File

@@ -49,7 +49,7 @@ type FilesMock struct {
files map[string]*fileProperties files map[string]*fileProperties
writtenFiles []string writtenFiles []string
removedFiles []string removedFiles []string
currentDir string CurrentDir string
Separator string Separator string
} }
@@ -67,10 +67,10 @@ func (f *FilesMock) init() {
// Relative segments such as "../" are currently NOT supported. // Relative segments such as "../" are currently NOT supported.
func (f *FilesMock) toAbsPath(path string) string { func (f *FilesMock) toAbsPath(path string) string {
if path == "." { if path == "." {
return f.Separator + f.currentDir return f.Separator + f.CurrentDir
} }
if !strings.HasPrefix(path, f.Separator) { if !strings.HasPrefix(path, f.Separator) {
path = f.Separator + filepath.Join(f.currentDir, path) path = f.Separator + filepath.Join(f.CurrentDir, path)
} }
return path return path
} }
@@ -246,7 +246,7 @@ func (f *FilesMock) FileRemove(path string) error {
leaf := filepath.Base(absPath) leaf := filepath.Base(absPath)
absPath = strings.TrimSuffix(absPath, f.Separator+leaf) absPath = strings.TrimSuffix(absPath, f.Separator+leaf)
if absPath != f.Separator { if absPath != f.Separator {
relPath := strings.TrimPrefix(absPath, f.Separator+f.currentDir+f.Separator) relPath := strings.TrimPrefix(absPath, f.Separator+f.CurrentDir+f.Separator)
dirExists, _ := f.DirExists(relPath) dirExists, _ := f.DirExists(relPath)
if !dirExists { if !dirExists {
f.AddDir(relPath) f.AddDir(relPath)
@@ -304,7 +304,7 @@ func (f *FilesMock) Chdir(path string) error {
return fmt.Errorf("failed to change current directory into '%s': %w", path, os.ErrNotExist) return fmt.Errorf("failed to change current directory into '%s': %w", path, os.ErrNotExist)
} }
f.currentDir = strings.TrimLeft(path, f.Separator) f.CurrentDir = strings.TrimLeft(path, f.Separator)
return nil return nil
} }
@@ -368,5 +368,6 @@ func (f *FilesMock) Chmod(path string, mode os.FileMode) error {
} }
func (f *FilesMock) Abs(path string) (string, error) { func (f *FilesMock) Abs(path string) (string, error) {
f.init()
return f.toAbsPath(path), nil return f.toAbsPath(path), nil
} }

View File

@@ -21,6 +21,7 @@ type FileUtils interface {
FileWrite(path string, content []byte, perm os.FileMode) error FileWrite(path string, content []byte, perm os.FileMode) error
MkdirAll(path string, perm os.FileMode) error MkdirAll(path string, perm os.FileMode) error
Chmod(path string, mode os.FileMode) error Chmod(path string, mode os.FileMode) error
Glob(pattern string) (matches []string, err error)
} }
// Files ... // Files ...

View File

@@ -172,6 +172,39 @@ spec:
- major-minor - major-minor
- semantic - semantic
- full - full
- name: projectSettingsFile
type: string
description: "Path or url to the mvn settings file that should be used as project settings file."
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
mandatory: false
aliases:
- name: maven/projectSettingsFile
- name: globalSettingsFile
type: string
description: "Path or url to the mvn settings file that should be used as global settings file"
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
mandatory: false
aliases:
- name: maven/globalSettingsFile
- name: m2Path
type: string
description: Path to the location of the local repository that should be used.
scope:
- GENERAL
- STEPS
- STAGES
- PARAMETERS
mandatory: false
aliases:
- name: maven/m2Path
containers: containers:
- name: openjdk - name: openjdk
image: openjdk:11 image: openjdk:11

View File

@@ -1,9 +1,15 @@
import com.sap.piper.BuildTool
import com.sap.piper.DownloadCacheUtils
import groovy.transform.Field import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName() @Field String STEP_NAME = getClass().getName()
@Field String METADATA_FILE = 'metadata/detect.yaml' @Field String METADATA_FILE = 'metadata/detect.yaml'
void call(Map parameters = [:]) { void call(Map parameters = [:]) {
final script = checkScript(this, parameters) ?: this
parameters = DownloadCacheUtils.injectDownloadCacheInParameters(script, parameters, BuildTool.MAVEN)
List credentials = [ List credentials = [
[type: 'token', id: 'detectTokenCredentialsId', env: ['PIPER_apiToken']] [type: 'token', id: 'detectTokenCredentialsId', env: ['PIPER_apiToken']]
] ]